remove other sample and change apk to inewme
@@ -1,31 +0,0 @@
|
||||
# Copyright (c) 2021, 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.
|
||||
#
|
||||
|
||||
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 "Hans-Kristian Arntzen"
|
||||
NAME "Buffer device address"
|
||||
DESCRIPTION "Demonstrates use of buffer device address for flexible vertex attribute fetch"
|
||||
SHADER_FILES_GLSL
|
||||
"buffer_device_address/update_vbo.comp"
|
||||
"buffer_device_address/render.vert"
|
||||
"buffer_device_address/render.frag")
|
||||
@@ -1,301 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2021-2024, 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.
|
||||
-
|
||||
////
|
||||
= Buffer device address
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/buffer_device_address[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
Buffer device address is a very powerful and unique feature to Vulkan which is not present in any other modern graphics API.
|
||||
The main gist of it is that it exposes GPU virtual addresses directly to the application, and the application can then use said address to access buffer data freely through pointers rather than descriptors.
|
||||
What makes this feature unique is that we can place these addresses in buffers and load and store to them inside shaders, with full capability to perform pointer arithmetic and other fun tricks.
|
||||
|
||||
== Creating a buffer
|
||||
|
||||
To be able to grab a device address from a `VkBuffer`, we just need to modify our buffer creation slightly.
|
||||
|
||||
First, the buffer must be created with `SHADER_DEVICE_ADDRESS_BIT` usage.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkBufferCreateInfo create_info = vkb::initializers::buffer_create_info(
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, mesh_size);
|
||||
----
|
||||
|
||||
After that, the memory we bind said buffer to must be allocated with a similar flag.
|
||||
This time, it is a `pNext` struct instead.
|
||||
This struct is core in Vulkan 1.1, but otherwise requires the `VK_KHR_device_group` extension.
|
||||
Vulkan 1.1 should be considered a given if buffer device address is supported, so this is more of a technicality than anything else.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkMemoryAllocateFlagsInfoKHR flags_info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR};
|
||||
flags_info.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
|
||||
memory_allocation_info.pNext = &flags_info;
|
||||
----
|
||||
|
||||
Finally, once we have allocated and bound the buffer to the memory, we can query the address.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkBufferDeviceAddressInfoKHR address_info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR};
|
||||
address_info.buffer = buffer.buffer;
|
||||
buffer.gpu_address = vkGetBufferDeviceAddressKHR(device, &address_info);
|
||||
----
|
||||
|
||||
This address works similarly to a normal addresses, and you can freely offset the `VkDeviceAddress` value as you see fit, as it is really just a `uint64_t`.
|
||||
There is no alignment requirement on the host.
|
||||
When using this pointer, you need to specify the alignment yourself, since unlike descriptors, the shader compiler won't be able to infer anything about a raw pointer that you load from somewhere.
|
||||
|
||||
You can now place this pointer inside another buffer and have fun.
|
||||
|
||||
== Vulkan GLSL
|
||||
|
||||
In Vulkan GLSL, we get the `GL_EXT_buffer_reference` extension which allows us to declare buffer blocks not as SSBOs, but faux pointer types instead.
|
||||
GLSL does not have true pointer types, but this is a way to introduce pointers without completely changing the language.
|
||||
E.g.:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
#extension GL_EXT_buffer_reference : require
|
||||
----
|
||||
|
||||
We can forward-declare types, which is nice for data structures like linked lists.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(buffer_reference) buffer Position;
|
||||
----
|
||||
|
||||
We can declare a buffer type as well.
|
||||
This is not an SSBO declaration, but it basically declares a pointer to struct.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(std430, buffer_reference, buffer_reference_align = 8) writeonly buffer Position
|
||||
{
|
||||
vec2 positions[];
|
||||
};
|
||||
----
|
||||
|
||||
`buffer_reference` tags the type accordingly, and `buffer_reference_align` is used to mark that any pointer which is of this type is at least 8 byte aligned.
|
||||
This is required since the compiler has no idea what alignment a random pointer has.
|
||||
It is possible to use scalar alignments here if you need it.
|
||||
|
||||
We can now place the `Position` type inside another buffer, or another buffer reference type, e.g.:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(std430, buffer_reference, buffer_reference_align = 8) readonly buffer PositionReferences
|
||||
{
|
||||
Position buffers[];
|
||||
};
|
||||
----
|
||||
|
||||
Now we have a pointer to array of pointers ...
|
||||
spicy!
|
||||
|
||||
Finally, we could place a buffer reference inside push constants, an SSBO or a UBO.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(std430, set = 0, binding = 0) readonly buffer Pointers
|
||||
{
|
||||
Positions positions[];
|
||||
};
|
||||
|
||||
layout(std430, push_constant) uniform Registers
|
||||
{
|
||||
PositionReferences references;
|
||||
} registers;
|
||||
----
|
||||
|
||||
The size and alignment of a buffer reference is 8 bytes (64-bit).
|
||||
Placing pointers in push constants is an attractive way of getting direct access to buffer data since we do not require a descriptor set to access buffer data.
|
||||
|
||||
=== Lack of robustness
|
||||
|
||||
A critical thing to note is that a raw pointer has no idea of how much memory is safe to access.
|
||||
Unlike SSBOs when robustness feature is enabled, you must either do range checks yourself or just not write code that relies on out-of-bounds access :)
|
||||
|
||||
== SPIR-V
|
||||
|
||||
In SPIR-V we get a new storage class `PhysicalStorageBuffer` which represents a raw pointer to storage buffer memory.
|
||||
The use of physical pointers in SPIR-V can be quite useful for use cases which emit SPIR-V directly, so it is useful to know how to use this feature in the IR.
|
||||
|
||||
=== Addressing mode
|
||||
|
||||
Rather than the `Logical` addressing model, we now use the `PhysicalStorageBuffer64` model, where we allow physical pointers only for the `PhysicalStorageBuffer` storage class.
|
||||
Otherwise, everything stays `Logical` where pointers are completely abstract.
|
||||
|
||||
----
|
||||
OpCapability PhysicalStorageBufferAddresses
|
||||
OpExtension "SPV_KHR_physical_storage_buffer"
|
||||
OpMemoryModel PhysicalStorageBuffer64 GLSL450
|
||||
----
|
||||
|
||||
=== Alignment tags
|
||||
|
||||
Explicit alignment is generally not a thing in SPIR-V, but physical storage buffer is an exception.
|
||||
Since the compiler has no idea what alignment a random pointer has, all uses of `OpLoad` or `OpStore` must be tagged with `Aligned`, e.g.:
|
||||
|
||||
----
|
||||
%57 = OpLoad %_ptr_PhysicalStorageBuffer_Position %56 Aligned 8
|
||||
OpStore %164 %162 Aligned 8
|
||||
----
|
||||
|
||||
When using Vulkan GLSL, these `Aligned` values are inferred from `buffer_reference_align` and the `Offset` decorations.
|
||||
|
||||
== Casting pointers
|
||||
|
||||
A key aspect of buffer device address is that we gain the capability to cast pointers freely.
|
||||
|
||||
While it is technically possible (and useful in some cases!) to "cast pointers" with SSBOs with clever use of aliased declarations like so:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(set = 0, binding = 0) buffer SSBO { float v1[]; };
|
||||
layout(set = 0, binding = 0) buffer SSBO2 { vec4 v4[]; };
|
||||
----
|
||||
|
||||
it gets kind of hairy quickly, and not as flexible when dealing with composite types.
|
||||
|
||||
=== Casting to and from integers, pointer arithmetic
|
||||
|
||||
When we have casts between integers and pointers, we get the full madness that is pointer arithmetic.
|
||||
Nothing stops us from doing:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
#extension GL_EXT_buffer_reference : require
|
||||
layout(buffer_reference) buffer PointerToFloat { float v; };
|
||||
|
||||
PointerToFloat pointer = load_pointer();
|
||||
uint64_t int_pointer = uint64_t(pointer);
|
||||
int_pointer += offset;
|
||||
pointer = PointerToFloat(int_pointer);
|
||||
pointer.v = 42.0;
|
||||
----
|
||||
|
||||
In SPIR-V, this is a simple `OpBitcast`.
|
||||
|
||||
Not all GPUs support 64-bit integers, so it is also possible to use `uvec2` to represent pointers.
|
||||
This way, we can do raw pointer arithmetic in 32-bit, which might be more optimal anyways.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
#extension GL_EXT_buffer_reference_uvec2 : require
|
||||
layout(buffer_reference) buffer PointerToFloat { float v; };
|
||||
PointerToFloat pointer = load_pointer();
|
||||
uvec2 int_pointer = uvec2(pointer);
|
||||
uint carry;
|
||||
uint lo = uaddCarry(int_pointer.x, offset, carry);
|
||||
uint hi = int_pointer.y + carry;
|
||||
pointer = PointerToFloat(uvec2(lo, hi));
|
||||
pointer.v = 42.0;
|
||||
----
|
||||
|
||||
== The sample
|
||||
|
||||
image::./images/sample.png[Sample]
|
||||
|
||||
The sample is a distilled demonstration of how buffer device addressing could be used to enable a more flexible vertex attribute fetch scheme.
|
||||
Rather than using fixed function VBOs which cannot be rebound on GPU, we could make use of buffer device address to enable a "meshlet" style of rendering, which is characterized by a rendering style where we chop up meshes into smaller chunks which can be culled and rendered individually.
|
||||
This is an attractive way of doing GPU-driven rendering.
|
||||
|
||||
Essentially, we create a bunch of VkBuffers, where each buffer represents a separate mesh (not the most optimal approach, but a useful demonstration).
|
||||
A mesh has a device address (`VkDeviceAddress`) which we place in a separate array, which serves as a nifty way of stitching together unrelated buffers.
|
||||
The mesh buffers are updated in a compute shader, and subsequently read in the vertex shader.
|
||||
|
||||
This kind of flexibility could be awkward to achieve with normal SSBOs unless everything is backed by a single `VkBuffer`.
|
||||
|
||||
Of course, this is just one of many use cases for buffer device address, and was deemed to be the simplest meaningful way to demonstrate this feature.
|
||||
|
||||
In compute, we pass down a pointer in push constants, which is a very fast way of providing shaders with a buffer as there is no descriptor set required!
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(std430, buffer_reference, buffer_reference_align = 8) writeonly buffer Position
|
||||
{
|
||||
vec2 positions[];
|
||||
};
|
||||
|
||||
layout(std430, buffer_reference, buffer_reference_align = 8) readonly buffer PositionReferences
|
||||
{
|
||||
Position buffers[];
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Registers
|
||||
{
|
||||
PositionReferences references;
|
||||
float fract_time;
|
||||
} registers;
|
||||
----
|
||||
|
||||
As we can see, push constants contain a pointer which points to an array of pointers, which then point to a "VBO" block.
|
||||
We perform one large dispatch to update N different "VBOs" here, where each 16x16 group works with its own base pointer.
|
||||
|
||||
The position that is computed forms a simple procedural wave pattern.
|
||||
The actual implementation details are not very interesting.
|
||||
|
||||
In the vertex shader, we do our faux "meshlet" rendering by assigning one VBO block per `gl_InstanceIndex`.
|
||||
For multi-draw-indirect use cases, it would be natural to use `gl_DrawID` perhaps.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(std430, buffer_reference, buffer_reference_align = 8) readonly buffer Position
|
||||
{
|
||||
vec2 positions[];
|
||||
};
|
||||
|
||||
layout(std430, buffer_reference, buffer_reference_align = 8) readonly buffer PositionReferences
|
||||
{
|
||||
Position buffers[];
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Registers
|
||||
{
|
||||
mat4 view_projection;
|
||||
PositionReferences references;
|
||||
} registers;
|
||||
|
||||
void main()
|
||||
{
|
||||
int slice = gl_InstanceIndex;
|
||||
// Load pointer to VBO
|
||||
restrict Position ptr_positions = registers.references.buffers[slice];
|
||||
// Load attribute based on gl_VertexIndex.
|
||||
// No fixed function here!
|
||||
vec2 pos = ptr_positions.positions[gl_VertexIndex];
|
||||
}
|
||||
----
|
||||
|
||||
== Debugging notes
|
||||
|
||||
When debugging or capturing an application that uses buffer device addresses, there are some special driver requirements that are not universally supported.
|
||||
Essentially, to be able to capture application buffers which contain raw pointers, we must ensure that the device address for a given buffer remains stable when the capture is replayed in a new process.
|
||||
Applications do not have to do anything here, since tools like RenderDoc will enable the `bufferDeviceAddressCaptureReplay` feature for you, and deal with all the magic associated with address capture behind the scenes.
|
||||
If the `bufferDeviceAddressCaptureReplay` is not present however, tools like RenderDoc will mask out the `bufferDeviceAddress` feature, so beware.
|
||||
|
||||
== Conclusion
|
||||
|
||||
Buffer device address is an extremely powerful feature which enables various use cases which were either impossible or very impractical before.
|
||||
@@ -1,441 +0,0 @@
|
||||
/* 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 "buffer_device_address.h"
|
||||
|
||||
BufferDeviceAddress::BufferDeviceAddress()
|
||||
{
|
||||
title = "Buffer device address";
|
||||
|
||||
// Need to enable buffer device address extension.
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
|
||||
|
||||
// Provides support for VkAllocateMemoryFlagsInfo. Otherwise, core in Vulkan 1.1.
|
||||
add_device_extension(VK_KHR_DEVICE_GROUP_EXTENSION_NAME);
|
||||
// Required by VK_KHR_device_group.
|
||||
add_instance_extension(VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
BufferDeviceAddress::~BufferDeviceAddress()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
VkDevice vk_device = get_device().get_handle();
|
||||
vkDestroyPipelineLayout(vk_device, pipelines.compute_pipeline_layout, nullptr);
|
||||
vkDestroyPipelineLayout(vk_device, pipelines.graphics_pipeline_layout, nullptr);
|
||||
vkDestroyPipeline(vk_device, pipelines.bindless_vbo_pipeline, nullptr);
|
||||
vkDestroyPipeline(vk_device, pipelines.compute_update_pipeline, nullptr);
|
||||
|
||||
for (auto &buffer : test_buffers)
|
||||
{
|
||||
vkDestroyBuffer(vk_device, buffer.buffer, nullptr);
|
||||
vkFreeMemory(vk_device, buffer.memory, nullptr);
|
||||
}
|
||||
vkDestroyBuffer(vk_device, pointer_buffer.buffer, nullptr);
|
||||
vkFreeMemory(vk_device, pointer_buffer.memory, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::build_command_buffers()
|
||||
{
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::on_update_ui_overlay(vkb::Drawer &)
|
||||
{
|
||||
}
|
||||
|
||||
bool BufferDeviceAddress::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
create_vbo_buffers();
|
||||
index_buffer = create_index_buffer();
|
||||
create_pipelines();
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct PushCompute
|
||||
{
|
||||
// This type is 8 bytes, and maps to a buffer_reference in Vulkan GLSL.
|
||||
VkDeviceAddress table;
|
||||
float fract_time;
|
||||
};
|
||||
|
||||
struct PushVertex
|
||||
{
|
||||
glm::mat4 view_projection;
|
||||
VkDeviceAddress table;
|
||||
};
|
||||
|
||||
VkPipelineLayout BufferDeviceAddress::create_pipeline_layout(bool graphics)
|
||||
{
|
||||
// For simplicity, we avoid any use of descriptor sets here.
|
||||
// We can just push a single pointer instead, which references all the buffers we need to work with.
|
||||
VkPipelineLayout layout{};
|
||||
|
||||
VkPipelineLayoutCreateInfo layout_create_info = vkb::initializers::pipeline_layout_create_info(nullptr, 0);
|
||||
|
||||
const std::vector<VkPushConstantRange> ranges = {
|
||||
vkb::initializers::push_constant_range(graphics ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
graphics ? sizeof(PushVertex) : sizeof(PushCompute), 0),
|
||||
};
|
||||
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, &layout));
|
||||
return layout;
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::create_compute_pipeline()
|
||||
{
|
||||
pipelines.compute_pipeline_layout = create_pipeline_layout(false);
|
||||
VkComputePipelineCreateInfo info = vkb::initializers::compute_pipeline_create_info(pipelines.compute_pipeline_layout);
|
||||
info.stage = load_shader("buffer_device_address/update_vbo.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
VK_CHECK(vkCreateComputePipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &info, nullptr, &pipelines.compute_update_pipeline));
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::create_graphics_pipeline()
|
||||
{
|
||||
pipelines.graphics_pipeline_layout = create_pipeline_layout(true);
|
||||
VkGraphicsPipelineCreateInfo info = vkb::initializers::pipeline_create_info(pipelines.graphics_pipeline_layout, render_pass);
|
||||
|
||||
// No VBOs, everything is fetched from buffer device addresses.
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
|
||||
// Going to render a simple quad mesh here with index buffer strip and primitive restart,
|
||||
// otherwise nothing interesting here.
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0, VK_TRUE);
|
||||
|
||||
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;
|
||||
|
||||
VkPipelineShaderStageCreateInfo stages[2];
|
||||
info.pStages = stages;
|
||||
info.stageCount = 2;
|
||||
|
||||
stages[0] = load_shader("buffer_device_address/render.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
stages[1] = load_shader("buffer_device_address/render.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &info, nullptr, &pipelines.bindless_vbo_pipeline));
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::create_pipelines()
|
||||
{
|
||||
create_compute_pipeline();
|
||||
create_graphics_pipeline();
|
||||
}
|
||||
|
||||
// A straight forward way of creating a "tessellated" quad mesh.
|
||||
// Choose a low resolution per mesh so it's more visible in the vertex shader what is happening.
|
||||
static constexpr unsigned mesh_width = 16;
|
||||
static constexpr unsigned mesh_height = 16;
|
||||
static constexpr unsigned mesh_strips = mesh_height - 1;
|
||||
static constexpr unsigned mesh_indices_per_strip = 2 * mesh_width;
|
||||
static constexpr unsigned mesh_num_indices = mesh_strips * (mesh_indices_per_strip + 1); // Add one index to handle primitive restart.
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> BufferDeviceAddress::create_index_buffer()
|
||||
{
|
||||
constexpr size_t size = mesh_num_indices * sizeof(uint16_t);
|
||||
|
||||
// Build a simple subdivided quad mesh. We can tweak the vertices later in compute to create a simple cloth-y/wave-like effect.
|
||||
|
||||
auto index_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
size,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
auto staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), size, nullptr);
|
||||
auto *buffer = reinterpret_cast<uint16_t *>(staging_buffer.map());
|
||||
for (unsigned strip = 0; strip < mesh_strips; strip++)
|
||||
{
|
||||
for (unsigned x = 0; x < mesh_width; x++)
|
||||
{
|
||||
*buffer++ = strip * mesh_width + x;
|
||||
*buffer++ = (strip + 1) * mesh_width + x;
|
||||
}
|
||||
*buffer++ = 0xffff;
|
||||
}
|
||||
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);
|
||||
cmd->copy_buffer(staging_buffer, *index_buffer, size);
|
||||
|
||||
vkb::BufferMemoryBarrier memory_barrier;
|
||||
memory_barrier.src_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
memory_barrier.dst_access_mask = VK_ACCESS_INDEX_READ_BIT;
|
||||
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||
cmd->buffer_memory_barrier(*index_buffer, 0, VK_WHOLE_SIZE, memory_barrier);
|
||||
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 index_buffer;
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::create_vbo_buffers()
|
||||
{
|
||||
test_buffers.resize(64);
|
||||
for (auto &buffer : test_buffers)
|
||||
{
|
||||
buffer = create_vbo_buffer();
|
||||
}
|
||||
|
||||
pointer_buffer = create_pointer_buffer();
|
||||
}
|
||||
|
||||
BufferDeviceAddress::TestBuffer BufferDeviceAddress::create_vbo_buffer()
|
||||
{
|
||||
TestBuffer buffer;
|
||||
|
||||
// Here we represent each "meshlet" as its own buffer to demonstrate maximum allocation flexibility.
|
||||
VkDevice device = get_device().get_handle();
|
||||
constexpr size_t mesh_size = mesh_width * mesh_height * sizeof(glm::vec2);
|
||||
|
||||
// To be able to query the buffer device address, we must use the SHADER_DEVICE_ADDRESS_BIT usage flag.
|
||||
// STORAGE_BUFFER is also required.
|
||||
VkBufferCreateInfo create_info = vkb::initializers::buffer_create_info(
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, mesh_size);
|
||||
|
||||
VK_CHECK(vkCreateBuffer(device, &create_info, nullptr, &buffer.buffer));
|
||||
|
||||
VkMemoryAllocateInfo memory_allocation_info = vkb::initializers::memory_allocate_info();
|
||||
VkMemoryRequirements memory_requirements;
|
||||
vkGetBufferMemoryRequirements(device, buffer.buffer, &memory_requirements);
|
||||
|
||||
// Another change is that the memory we allocate must be marked as buffer device address capable.
|
||||
VkMemoryAllocateFlagsInfoKHR flags_info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR};
|
||||
flags_info.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
|
||||
memory_allocation_info.pNext = &flags_info;
|
||||
|
||||
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, &buffer.memory));
|
||||
VK_CHECK(vkBindBufferMemory(get_device().get_handle(), buffer.buffer, buffer.memory, 0));
|
||||
|
||||
// Once we've bound the buffer, we query the buffer device address.
|
||||
// We can now place this address (or any offset of said address) into a buffer and access data as a raw pointer in shaders.
|
||||
VkBufferDeviceAddressInfoKHR address_info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR};
|
||||
address_info.buffer = buffer.buffer;
|
||||
buffer.gpu_address = vkGetBufferDeviceAddressKHR(device, &address_info);
|
||||
|
||||
// The buffer content will be computed at runtime, so don't upload anything.
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
BufferDeviceAddress::TestBuffer BufferDeviceAddress::create_pointer_buffer()
|
||||
{
|
||||
// Just like create_vbo_buffer(), we create a buffer which holds other pointers.
|
||||
TestBuffer buffer;
|
||||
|
||||
VkDevice device = get_device().get_handle();
|
||||
size_t buffer_size = test_buffers.size() * sizeof(VkDeviceAddress);
|
||||
|
||||
// We use TRANSFER_DST since we will upload to the buffer later.
|
||||
VkBufferCreateInfo create_info = vkb::initializers::buffer_create_info(
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, buffer_size);
|
||||
VK_CHECK(vkCreateBuffer(device, &create_info, nullptr, &buffer.buffer));
|
||||
|
||||
VkMemoryAllocateInfo memory_allocation_info = vkb::initializers::memory_allocate_info();
|
||||
VkMemoryAllocateFlagsInfoKHR flags_info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR};
|
||||
VkMemoryRequirements memory_requirements;
|
||||
vkGetBufferMemoryRequirements(device, buffer.buffer, &memory_requirements);
|
||||
|
||||
flags_info.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
|
||||
memory_allocation_info.pNext = &flags_info;
|
||||
|
||||
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, &buffer.memory));
|
||||
VK_CHECK(vkBindBufferMemory(get_device().get_handle(), buffer.buffer, buffer.memory, 0));
|
||||
|
||||
VkBufferDeviceAddressInfoKHR address_info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR};
|
||||
address_info.buffer = buffer.buffer;
|
||||
buffer.gpu_address = vkGetBufferDeviceAddressKHR(device, &address_info);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::update_pointer_buffer(VkCommandBuffer cmd)
|
||||
{
|
||||
// Wait with updating the pointer buffer until previous frame's vertex shading is complete.
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||
0, nullptr, 0, nullptr, 0, nullptr);
|
||||
|
||||
std::vector<VkDeviceAddress> pointers;
|
||||
pointers.reserve(test_buffers.size());
|
||||
for (auto &test_buffer : test_buffers)
|
||||
{
|
||||
pointers.push_back(test_buffer.gpu_address);
|
||||
}
|
||||
// Simple approach. A proxy for a compute shader which culls meshlets.
|
||||
vkCmdUpdateBuffer(cmd, pointer_buffer.buffer, 0, test_buffers.size() * sizeof(VkDeviceAddress), pointers.data());
|
||||
|
||||
VkMemoryBarrier global_memory_barrier = vkb::initializers::memory_barrier();
|
||||
global_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
global_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, 0,
|
||||
1, &global_memory_barrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::update_meshlets(VkCommandBuffer cmd)
|
||||
{
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipelines.compute_update_pipeline);
|
||||
|
||||
PushCompute push_compute{};
|
||||
|
||||
// Here we push a pointer to a buffer, which holds pointers to all the VBO "meshlets".
|
||||
push_compute.table = pointer_buffer.gpu_address;
|
||||
|
||||
// So we can create a wave-like animation.
|
||||
push_compute.fract_time = accumulated_time;
|
||||
|
||||
vkCmdPushConstants(cmd, pipelines.compute_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
0, sizeof(push_compute), &push_compute);
|
||||
|
||||
// Write-after-read hazard is implicitly handled by the earlier pointer buffer update where
|
||||
// we did VERTEX -> TRANSFER -> COMPUTE chain of barriers.
|
||||
|
||||
// Update all meshlets.
|
||||
vkCmdDispatch(cmd, mesh_width / 8, mesh_height / 8, static_cast<uint32_t>(test_buffers.size()));
|
||||
|
||||
VkMemoryBarrier global_memory_barrier = vkb::initializers::memory_barrier();
|
||||
global_memory_barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
global_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
|
||||
0, 1, &global_memory_barrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
void BufferDeviceAddress::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);
|
||||
|
||||
// First thing is to update the pointer buffer.
|
||||
// We could use a compute shader here if we're doing
|
||||
// GPU-driven rendering for example.
|
||||
update_pointer_buffer(cmd);
|
||||
|
||||
// Arbitrary value between 0 and 1 to create some animation.
|
||||
accumulated_time += 0.2f * delta_time;
|
||||
accumulated_time = glm::fract(accumulated_time);
|
||||
|
||||
// Update VBOs through buffer_device_address.
|
||||
update_meshlets(cmd);
|
||||
|
||||
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);
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bindless_vbo_pipeline);
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
PushVertex push_vertex{};
|
||||
|
||||
// Create an ad-hoc perspective matrix.
|
||||
push_vertex.view_projection =
|
||||
glm::perspective(0.5f * glm::pi<float>(), static_cast<float>(width) / static_cast<float>(height), 1.0f, 100.0f) *
|
||||
glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
// Push pointer to array of meshlets.
|
||||
// Every instance renders its own meshlet.
|
||||
push_vertex.table = pointer_buffer.gpu_address;
|
||||
vkCmdPushConstants(cmd, pipelines.graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_vertex), &push_vertex);
|
||||
|
||||
vkCmdBindIndexBuffer(cmd, index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT16);
|
||||
vkCmdDrawIndexed(cmd, mesh_num_indices, static_cast<uint32_t>(test_buffers.size()), 0, 0, 0);
|
||||
|
||||
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 BufferDeviceAddress::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// Need to enable the bufferDeviceAddress feature.
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceBufferDeviceAddressFeaturesKHR,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR,
|
||||
bufferDeviceAddress);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_buffer_device_address()
|
||||
{
|
||||
return std::make_unique<BufferDeviceAddress>();
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/* Copyright (c) 2021-2024, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class BufferDeviceAddress : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
BufferDeviceAddress();
|
||||
~BufferDeviceAddress();
|
||||
|
||||
private:
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual void build_command_buffers() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
void create_pipelines();
|
||||
VkPipelineLayout create_pipeline_layout(bool graphics);
|
||||
void create_compute_pipeline();
|
||||
void create_graphics_pipeline();
|
||||
|
||||
void update_pointer_buffer(VkCommandBuffer cmd);
|
||||
void update_meshlets(VkCommandBuffer cmd);
|
||||
|
||||
struct Pipelines
|
||||
{
|
||||
VkPipelineLayout compute_pipeline_layout{};
|
||||
VkPipelineLayout graphics_pipeline_layout{};
|
||||
VkPipeline bindless_vbo_pipeline{};
|
||||
VkPipeline compute_update_pipeline{};
|
||||
} pipelines;
|
||||
|
||||
struct TestBuffer
|
||||
{
|
||||
VkBuffer buffer{};
|
||||
VkDeviceMemory memory{};
|
||||
VkDeviceAddress gpu_address{};
|
||||
};
|
||||
std::vector<TestBuffer> test_buffers;
|
||||
void create_vbo_buffers();
|
||||
TestBuffer create_vbo_buffer();
|
||||
TestBuffer create_pointer_buffer();
|
||||
TestBuffer pointer_buffer;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> create_index_buffer();
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer;
|
||||
|
||||
std::default_random_engine rnd{42};
|
||||
std::uniform_real_distribution<float> distribution{0.0f, 0.1f};
|
||||
uint32_t descriptor_offset{};
|
||||
float accumulated_time{};
|
||||
uint32_t num_indices_per_mesh{};
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_buffer_device_address();
|
||||
|
Before Width: | Height: | Size: 334 KiB |
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2023-2024, 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.
|
||||
#
|
||||
|
||||
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 "Holochip Corporation"
|
||||
NAME "Calibrated Timestamps"
|
||||
DESCRIPTION "Demonstrates and showcases calibrated timestamps related functionalities."
|
||||
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,49 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, 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.
|
||||
-
|
||||
////
|
||||
= Calibrated Timestamps
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/calibrated_timestamps[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
This sample demonstrates the extension `VK_EXT_calibrated_timestamps`.
|
||||
The calibrated timestamps profiles any given portion of code, unlike timestamp queries, which only profiles an entire graphic queue.
|
||||
|
||||
== Overview
|
||||
|
||||
To enable the `VK_EXT_calibrated_timestamps` extension, `VK_KHR_get_physical_device_properties2` must be enabled.
|
||||
|
||||
== Introduction
|
||||
|
||||
This sample is built upon the framework of the Vulkan Sample `HDR`.
|
||||
We demonstrate using calibrated timestamps over the build_command_buffers function.
|
||||
|
||||
== * Time domain, timestamp, timestamp period, and max deviation
|
||||
|
||||
A timestamp is being sampled via the calibrated timestamp extension.
|
||||
In general, one must take two timestamps in order to measure the time elapsed within a block of code.
|
||||
|
||||
Each time domain is different, and the measurement of their associated timestamp periods may vary.
|
||||
The precision of timestamps is calibrated by max deviations.
|
||||
|
||||
== Get time domain and timestamps
|
||||
|
||||
A list of time domains can be extracted by using `vkGetPhysicalDeviceCalibrateableTimeDomainsEXT`.
|
||||
And the Vulkan time domain is defined by the enum `VkTimeDomainEXT`
|
||||
@@ -1,922 +0,0 @@
|
||||
/* Copyright (c) 2023-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 "calibrated_timestamps.h"
|
||||
|
||||
std::string time_domain_to_string(VkTimeDomainEXT input_time_domain)
|
||||
{
|
||||
switch (input_time_domain)
|
||||
{
|
||||
case VK_TIME_DOMAIN_DEVICE_EXT:
|
||||
return "device time domain";
|
||||
case VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT:
|
||||
return "clock monotonic time domain";
|
||||
case VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_EXT:
|
||||
return "clock monotonic raw time domain";
|
||||
case VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_EXT:
|
||||
return "query performance time domain";
|
||||
default:
|
||||
return "unknown time domain";
|
||||
}
|
||||
}
|
||||
|
||||
CalibratedTimestamps::CalibratedTimestamps() :
|
||||
is_time_domain_init(false)
|
||||
{
|
||||
title = "Calibrated Timestamps";
|
||||
|
||||
// Add instance extensions required for calibrated timestamps
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
// NOTICE THAT: calibrated timestamps is a DEVICE extension!
|
||||
add_device_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
CalibratedTimestamps::~CalibratedTimestamps()
|
||||
{
|
||||
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.environment_map.sampler, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::build_command_buffers()
|
||||
{
|
||||
timestamps_begin("Build_Command_Buffers");
|
||||
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
std::array<VkClearValue, 3> offscreen_clear_values{};
|
||||
VkClearValue filter_clear_value{};
|
||||
std::array<VkClearValue, 2> bloom_clear_values{};
|
||||
|
||||
VkRenderPassBeginInfo offscreen_render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
offscreen_render_pass_begin_info.renderPass = offscreen.render_pass;
|
||||
offscreen_render_pass_begin_info.framebuffer = offscreen.framebuffer;
|
||||
offscreen_render_pass_begin_info.renderArea.extent.width = offscreen.width;
|
||||
offscreen_render_pass_begin_info.renderArea.extent.height = offscreen.height;
|
||||
offscreen_render_pass_begin_info.clearValueCount = static_cast<uint32_t>(offscreen_clear_values.size());
|
||||
offscreen_render_pass_begin_info.pClearValues = offscreen_clear_values.data();
|
||||
VkRenderPassBeginInfo filter_render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
filter_render_pass_begin_info.framebuffer = filter_pass.framebuffer;
|
||||
filter_render_pass_begin_info.renderPass = filter_pass.render_pass;
|
||||
filter_render_pass_begin_info.clearValueCount = 1;
|
||||
filter_render_pass_begin_info.renderArea.extent.width = filter_pass.width;
|
||||
filter_render_pass_begin_info.renderArea.extent.height = filter_pass.height;
|
||||
filter_render_pass_begin_info.pClearValues = &filter_clear_value;
|
||||
VkRenderPassBeginInfo bloom_render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
bloom_render_pass_begin_info.renderPass = render_pass;
|
||||
bloom_render_pass_begin_info.clearValueCount = static_cast<uint32_t>(bloom_clear_values.size());
|
||||
bloom_render_pass_begin_info.renderArea.extent.width = width;
|
||||
bloom_render_pass_begin_info.renderArea.extent.height = height;
|
||||
bloom_render_pass_begin_info.pClearValues = bloom_clear_values.data();
|
||||
|
||||
VkViewport offscreen_viewport = vkb::initializers::viewport(static_cast<float>(offscreen.width), static_cast<float>(offscreen.height), 0.0f, 1.0f);
|
||||
VkViewport filter_viewport = vkb::initializers::viewport(static_cast<float>(filter_pass.width), static_cast<float>(filter_pass.height), 0.0f, 1.0f);
|
||||
VkViewport bloom_viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
|
||||
VkRect2D offscreen_scissor = vkb::initializers::rect2D(offscreen.width, offscreen.height, 0, 0);
|
||||
VkRect2D filter_scissor = vkb::initializers::rect2D(filter_pass.width, filter_pass.height, 0, 0);
|
||||
VkRect2D bloom_scissor = vkb::initializers::rect2D(static_cast<int>(width), static_cast<int>(height), 0, 0);
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
{
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &offscreen_render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &offscreen_viewport);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &offscreen_scissor);
|
||||
|
||||
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, nullptr);
|
||||
draw_model(models.skybox, draw_cmd_buffers[i]);
|
||||
}
|
||||
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, nullptr);
|
||||
draw_model(models.objects[models.object_index], draw_cmd_buffers[i]);
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
if (bloom)
|
||||
{
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &filter_render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &filter_viewport);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &filter_scissor);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.bloom_filter, 0, 1, &descriptor_sets.bloom_filter, 0, nullptr);
|
||||
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]);
|
||||
}
|
||||
|
||||
{
|
||||
bloom_render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &bloom_render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &bloom_viewport);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &bloom_scissor);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.composition, 0, 1, &descriptor_sets.composition, 0, nullptr);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
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]));
|
||||
}
|
||||
|
||||
timestamps_end("Build_Command_Buffers");
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::create_attachment(VkFormat format, VkImageUsageFlagBits usage, FrameBufferAttachment *attachment)
|
||||
{
|
||||
VkImageAspectFlags aspect_mask = 0;
|
||||
|
||||
attachment->format = format;
|
||||
|
||||
if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
{
|
||||
aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
if (format >= VK_FORMAT_D16_UNORM_S8_UINT)
|
||||
{
|
||||
aspect_mask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::prepare_offscreen_buffer()
|
||||
{
|
||||
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 = static_cast<int32_t>(width);
|
||||
offscreen.height = static_cast<int32_t>(height);
|
||||
|
||||
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]);
|
||||
create_attachment(depth_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &offscreen.depth);
|
||||
|
||||
std::array<VkAttachmentDescription, 3> attachment_descriptions = {};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 = static_cast<uint32_t>(color_references.size());
|
||||
subpass.pDepthStencilAttachment = &depth_reference;
|
||||
|
||||
std::array<VkSubpassDependency, 2> dependencies{};
|
||||
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_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 = static_cast<uint32_t>(dependencies.size());
|
||||
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 = nullptr;
|
||||
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));
|
||||
|
||||
VkSamplerCreateInfo sampler = vkb::initializers::sampler_create_info();
|
||||
sampler.magFilter = VK_FILTER_NEAREST;
|
||||
sampler.minFilter = VK_FILTER_NEAREST;
|
||||
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
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));
|
||||
}
|
||||
|
||||
{
|
||||
filter_pass.width = static_cast<int32_t>(width);
|
||||
filter_pass.height = static_cast<int32_t>(height);
|
||||
|
||||
create_attachment(color_format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &filter_pass.color[0]);
|
||||
|
||||
VkAttachmentDescription attachment_description{};
|
||||
|
||||
attachment_description.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachment_description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment_description.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachment_description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachment_description.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
attachment_description.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 = static_cast<uint32_t>(color_references.size());
|
||||
|
||||
std::array<VkSubpassDependency, 2> dependencies{};
|
||||
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
VkRenderPassCreateInfo render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_create_info.pAttachments = &attachment_description;
|
||||
render_pass_create_info.attachmentCount = 1;
|
||||
render_pass_create_info.subpassCount = 1;
|
||||
render_pass_create_info.pSubpasses = &subpass;
|
||||
render_pass_create_info.dependencyCount = static_cast<uint32_t>(dependencies.size());
|
||||
render_pass_create_info.pDependencies = dependencies.data();
|
||||
|
||||
VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &filter_pass.render_pass));
|
||||
|
||||
VkImageView attachment = filter_pass.color[0].view;
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = {};
|
||||
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebuffer_create_info.pNext = nullptr;
|
||||
framebuffer_create_info.renderPass = filter_pass.render_pass;
|
||||
framebuffer_create_info.pAttachments = &attachment;
|
||||
framebuffer_create_info.attachmentCount = 1;
|
||||
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));
|
||||
|
||||
VkSamplerCreateInfo sampler = vkb::initializers::sampler_create_info();
|
||||
sampler.magFilter = VK_FILTER_NEAREST;
|
||||
sampler.minFilter = VK_FILTER_NEAREST;
|
||||
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
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 CalibratedTimestamps::load_assets()
|
||||
{
|
||||
timestamps_begin("loadAssets");
|
||||
models.skybox = load_model("scenes/cube.gltf");
|
||||
std::vector<std::string> filenames = {"geosphere.gltf", "teapot.gltf", "torusknot.gltf"};
|
||||
object_names = {"Sphere", "Teapot", "Torusknot"};
|
||||
for (const auto &file : filenames)
|
||||
{
|
||||
auto object = load_model("scenes/" + file);
|
||||
models.objects.emplace_back(std::move(object));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
textures.environment_map = load_texture_cubemap("textures/uffizi_rgba16f_cube.ktx", vkb::sg::Image::Color);
|
||||
timestamps_end("loadAssets");
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::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 CalibratedTimestamps::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));
|
||||
|
||||
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));
|
||||
|
||||
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 CalibratedTimestamps::setup_descriptor_sets()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layouts.models, 1);
|
||||
|
||||
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.environment_map);
|
||||
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, nullptr);
|
||||
|
||||
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.environment_map);
|
||||
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, nullptr);
|
||||
|
||||
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, nullptr);
|
||||
|
||||
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, nullptr);
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::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);
|
||||
|
||||
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);
|
||||
|
||||
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{};
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo empty_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
pipeline_create_info.pVertexInputState = &empty_input_state;
|
||||
|
||||
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_state;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.composition));
|
||||
|
||||
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;
|
||||
|
||||
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]));
|
||||
|
||||
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]));
|
||||
|
||||
rasterization_state.cullMode = VK_CULL_MODE_BACK_BIT;
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
|
||||
std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3)};
|
||||
|
||||
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;
|
||||
|
||||
blend_attachment_state.blendEnable = VK_FALSE;
|
||||
pipeline_create_info.layout = pipeline_layouts.models;
|
||||
pipeline_create_info.renderPass = offscreen.render_pass;
|
||||
color_blend_state.attachmentCount = static_cast<uint32_t>(blend_attachment_states.size());
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
shadertype = 1;
|
||||
|
||||
depth_stencil_state.depthWriteEnable = VK_TRUE;
|
||||
depth_stencil_state.depthTestEnable = VK_TRUE;
|
||||
rasterization_state.cullMode = VK_CULL_MODE_FRONT_BIT;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.reflect));
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::prepare_uniform_buffers()
|
||||
{
|
||||
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);
|
||||
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();
|
||||
uniform_buffers.params->convert_and_update(ubo_params);
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::update_uniform_buffers()
|
||||
{
|
||||
timestamps_begin("update ubo");
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.model_view = camera.matrices.view * models.transforms[models.object_index];
|
||||
ubo_vs.skybox_model_view = camera.matrices.view;
|
||||
ubo_vs.inverse_modelview = glm::inverse(camera.matrices.view);
|
||||
uniform_buffers.matrices->convert_and_update(ubo_vs);
|
||||
timestamps_end("update ubo");
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::draw()
|
||||
{
|
||||
timestamps_begin("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();
|
||||
timestamps_end("draw");
|
||||
}
|
||||
|
||||
bool CalibratedTimestamps::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));
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 256.0f, 0.1f);
|
||||
|
||||
// Get the optimal time domain as soon as possible
|
||||
get_device_time_domain();
|
||||
|
||||
// Preparations
|
||||
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 CalibratedTimestamps::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::get_time_domains()
|
||||
{
|
||||
// Initialize time domain count:
|
||||
uint32_t time_domain_count = 0;
|
||||
// Update time domain count:
|
||||
VkResult result = vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(get_device().get_gpu().get_handle(), &time_domain_count, nullptr);
|
||||
|
||||
if (result == VK_SUCCESS)
|
||||
{
|
||||
// Resize time domains vector:
|
||||
time_domains.resize(time_domain_count);
|
||||
// Update time_domain vector:
|
||||
result = vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(get_device().get_gpu().get_handle(), &time_domain_count, time_domains.data());
|
||||
}
|
||||
|
||||
if (time_domain_count > 0)
|
||||
{
|
||||
for (VkTimeDomainEXT time_domain : time_domains)
|
||||
{
|
||||
// Initialize in-scope time stamp info variable:
|
||||
VkCalibratedTimestampInfoEXT timestamp_info{};
|
||||
|
||||
// Configure timestamp info variable:
|
||||
timestamp_info.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT;
|
||||
timestamp_info.pNext = nullptr;
|
||||
timestamp_info.timeDomain = time_domain;
|
||||
|
||||
// Push-back timestamp info to timestamps info vector:
|
||||
timestamps_info.push_back(timestamp_info);
|
||||
}
|
||||
|
||||
// Resize time stamps vector
|
||||
timestamps.resize(time_domain_count);
|
||||
// Resize max deviations vector
|
||||
max_deviations.resize(time_domain_count);
|
||||
}
|
||||
|
||||
is_time_domain_init = ((result == VK_SUCCESS) && (time_domain_count > 0));
|
||||
}
|
||||
|
||||
VkResult CalibratedTimestamps::get_timestamps()
|
||||
{
|
||||
// Ensures that time domain exists
|
||||
if (is_time_domain_init)
|
||||
{
|
||||
// Get calibrated timestamps:
|
||||
return vkGetCalibratedTimestampsEXT(get_device().get_handle(), static_cast<uint32_t>(time_domains.size()), timestamps_info.data(), timestamps.data(), max_deviations.data());
|
||||
}
|
||||
return VK_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::get_device_time_domain()
|
||||
{
|
||||
get_time_domains();
|
||||
|
||||
if (is_time_domain_init && (time_domains.size() > 1))
|
||||
{
|
||||
auto iterator_device = std::ranges::find(time_domains, VK_TIME_DOMAIN_DEVICE_EXT);
|
||||
|
||||
int device_index = static_cast<int>(iterator_device - time_domains.begin());
|
||||
|
||||
selected_time_domain.index = device_index;
|
||||
selected_time_domain.timeDomainEXT = time_domains[device_index];
|
||||
}
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::timestamps_begin(const std::string &input_tag)
|
||||
{
|
||||
// Now to get timestamps
|
||||
if (get_timestamps() == VK_SUCCESS)
|
||||
{
|
||||
std::string tag = input_tag;
|
||||
if (input_tag.empty())
|
||||
{
|
||||
tag = "Unknown";
|
||||
}
|
||||
delta_timestamps[tag].tag = input_tag;
|
||||
delta_timestamps[tag].begin = timestamps[selected_time_domain.index];
|
||||
}
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::timestamps_end(const std::string &input_tag)
|
||||
{
|
||||
if (delta_timestamps.empty())
|
||||
{
|
||||
LOGE("Timestamps begin-to-end Fatal Error: Timestamps end is not tagged the same as its begin!")
|
||||
return; // exits the function here, further calculation is meaningless
|
||||
}
|
||||
|
||||
auto result = get_timestamps();
|
||||
if (result == VK_SUCCESS)
|
||||
{
|
||||
if (delta_timestamps.find(input_tag) != delta_timestamps.end())
|
||||
{
|
||||
// Add this data to the last term of delta_timestamps vector
|
||||
delta_timestamps[input_tag].end = timestamps[selected_time_domain.index];
|
||||
delta_timestamps[input_tag].delta = delta_timestamps[input_tag].end - delta_timestamps[input_tag].begin;
|
||||
return;
|
||||
}
|
||||
LOGE("timestamps_end called without a found corresponding begin timestamp for {}.", input_tag.c_str())
|
||||
return;
|
||||
}
|
||||
LOGE("get_timestamps failed with {}", vkb::to_string(result))
|
||||
}
|
||||
|
||||
void CalibratedTimestamps::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
// Timestamps period extracted in runtime
|
||||
float timestamp_period = get_device().get_gpu().get_properties().limits.timestampPeriod;
|
||||
drawer.text("Timestamps Period:\n %.1f Nanoseconds", timestamp_period);
|
||||
|
||||
// Adjustment Handles:
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.combo_box("Object type", &models.object_index, object_names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Bloom", &bloom))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Skybox", &display_skybox))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
if (!delta_timestamps.empty())
|
||||
{
|
||||
drawer.text("Time Domain Selected:\n %s", time_domain_to_string(selected_time_domain.timeDomainEXT).c_str());
|
||||
|
||||
for (const auto &delta_timestamp : delta_timestamps)
|
||||
{
|
||||
drawer.text("%s:\n %.1f Microseconds", delta_timestamp.second.tag.c_str(), static_cast<float>(delta_timestamp.second.delta) * timestamp_period * 0.001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CalibratedTimestamps::resize(uint32_t width, uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_calibrated_timestamps()
|
||||
{
|
||||
return std::make_unique<CalibratedTimestamps>();
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Demonstrates and showcase calibrated timestamps extension related functionalities
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
static std::string time_domain_to_string(VkTimeDomainEXT input_time_domain); // this returns a string the input time domain Enum value
|
||||
|
||||
class CalibratedTimestamps : public ApiVulkanSample
|
||||
{
|
||||
private:
|
||||
bool is_time_domain_init = false; // this is just to tell if time domain update has a VK_SUCCESS in the end
|
||||
std::vector<VkTimeDomainEXT> time_domains{}; // this holds all time domains extracted from the current Instance
|
||||
std::vector<uint64_t> timestamps{}; // timestamps vector
|
||||
std::vector<uint64_t> max_deviations{}; // max deviations vector
|
||||
|
||||
struct
|
||||
{
|
||||
int index = 0;
|
||||
VkTimeDomainEXT timeDomainEXT{};
|
||||
} selected_time_domain{};
|
||||
|
||||
struct DeltaTimestamp
|
||||
{
|
||||
uint64_t begin = 0;
|
||||
uint64_t end = 0;
|
||||
uint64_t delta = 0;
|
||||
std::string tag = "Untagged";
|
||||
};
|
||||
std::vector<VkCalibratedTimestampInfoEXT> timestamps_info{}; // This vector is essential to vkGetCalibratedTimestampsEXT, and only need to be registered once.
|
||||
std::unordered_map<std::string, DeltaTimestamp> delta_timestamps{};
|
||||
|
||||
public:
|
||||
bool bloom = true;
|
||||
bool display_skybox = true;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture environment_map{};
|
||||
} textures{};
|
||||
|
||||
struct
|
||||
{
|
||||
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
|
||||
{
|
||||
glm::mat4 projection{};
|
||||
glm::mat4 model_view{};
|
||||
glm::mat4 skybox_model_view{};
|
||||
glm::mat4 inverse_modelview{};
|
||||
float model_scale = 0.05f;
|
||||
} ubo_vs{};
|
||||
|
||||
struct
|
||||
{
|
||||
float exposure = 1.0f;
|
||||
} ubo_params{};
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipeline skybox = VK_NULL_HANDLE;
|
||||
VkPipeline reflect = VK_NULL_HANDLE;
|
||||
VkPipeline composition = VK_NULL_HANDLE;
|
||||
VkPipeline bloom[2]{VK_NULL_HANDLE, VK_NULL_HANDLE};
|
||||
} pipelines{};
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout models = VK_NULL_HANDLE;
|
||||
VkPipelineLayout composition = VK_NULL_HANDLE;
|
||||
VkPipelineLayout bloom_filter = VK_NULL_HANDLE;
|
||||
} pipeline_layouts{};
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet object = VK_NULL_HANDLE;
|
||||
VkDescriptorSet skybox = VK_NULL_HANDLE;
|
||||
VkDescriptorSet composition = VK_NULL_HANDLE;
|
||||
VkDescriptorSet bloom_filter = VK_NULL_HANDLE;
|
||||
} descriptor_sets{};
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSetLayout models = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout composition = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout bloom_filter = VK_NULL_HANDLE;
|
||||
} descriptor_set_layouts{};
|
||||
|
||||
struct FrameBufferAttachment
|
||||
{
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory mem = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
void destroy(VkDevice device) const
|
||||
{
|
||||
vkDestroyImageView(device, view, nullptr);
|
||||
vkDestroyImage(device, image, nullptr);
|
||||
vkFreeMemory(device, mem, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
FrameBufferAttachment color[2]{};
|
||||
FrameBufferAttachment depth{};
|
||||
VkRenderPass render_pass = VK_NULL_HANDLE;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
} offscreen{};
|
||||
|
||||
struct
|
||||
{
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
FrameBufferAttachment color[1]{};
|
||||
VkRenderPass render_pass = VK_NULL_HANDLE;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
} filter_pass{};
|
||||
|
||||
std::vector<std::string> object_names{};
|
||||
|
||||
private:
|
||||
void get_time_domains(); // this extracts total number of time domain the (physical device has, and then sync the time domain EXT data to its vector
|
||||
VkResult get_timestamps(); // this creates local timestamps information vector, update timestamps vector and deviation vector
|
||||
void get_device_time_domain(); // this gets the optimal time domain which has the minimal value on its max deviation.
|
||||
void timestamps_begin(const std::string &input_tag = "Untagged"); // this marks the timestamp begin and partially updates the delta_timestamps
|
||||
void timestamps_end(const std::string &input_tag = "Untagged"); // this marks the timestamp ends and updates the delta_timestamps
|
||||
|
||||
public:
|
||||
CalibratedTimestamps();
|
||||
~CalibratedTimestamps() override;
|
||||
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 draw();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void render(float delta_time) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
bool resize(uint32_t width, uint32_t height) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_calibrated_timestamps();
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2023-2024, Mobica Limited
|
||||
#
|
||||
# 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 "Mobica"
|
||||
NAME "ColorWriteEnableEXT sample"
|
||||
DESCRIPTION "Dynamic toggle of a color blend attachment display."
|
||||
SHADER_FILES_GLSL
|
||||
"color_write_enable/glsl/triangle_separate_channels.vert"
|
||||
"color_write_enable/glsl/triangle_separate_channels.frag"
|
||||
"color_write_enable/glsl/composition.vert"
|
||||
"color_write_enable/glsl/composition.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"color_write_enable/hlsl/triangle_separate_channels.vert.hlsl"
|
||||
"color_write_enable/hlsl/triangle_separate_channels.frag.hlsl"
|
||||
"color_write_enable/hlsl/composition.vert.hlsl"
|
||||
"color_write_enable/hlsl/composition.frag.hlsl")
|
||||
@@ -1,69 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, Mobica Limited
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
= Color write enable
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/color_write_enable[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates how to use the `VK_EXT_color_write_enable` extension.
|
||||
This extension allows to toggle the output color attachments using a pipeline dynamic state.
|
||||
It allows the program to prepare an additional framebuffer populated with the data from a defined color blend attachment which can be blended dynamically to the final scene.
|
||||
|
||||
The final results are comparable to those obtained with `vkCmdSetColorWriteMaskEXT`, but it does not require the GPU driver to support `VK_EXT_extended_dynamic_state3`.
|
||||
|
||||
== How to use in Vulkan
|
||||
|
||||
To use this feature, the device extension VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME has to be enabled.
|
||||
Support of this feature can be queried by extending the struct VkPhysicalDeviceFeatures2 in the vkGetPhysicalDeviceFeatures2 call by a VkPhysicalDeviceColorWriteEnableFeaturesEXT struct.
|
||||
`VkPipelineColorWriteCreateInfoEXT` contains an array of Boolean values that serve as toggles for the corresponding `VkPipelineColorBlendAttachmentState`.
|
||||
This array can be overwritten dynamically with the `vkCmdSetColorWriteEnableEXT` function.
|
||||
|
||||
Two subpasses are performed in the sample.
|
||||
In the first subpass, three attachments are used.
|
||||
Each attachment has only one color component bit enabled - R, G and B.
|
||||
A triangle is drawn on each of them separately.
|
||||
The second subpass combines three images created in the previous pass.
|
||||
Checkboxes are used to toggle the `vkCmdSetColorWriteEnableEXT` function disabling each attachment.
|
||||
As a result of its disabling, the value of a given channel is set as the value of that channel in the background color.
|
||||
Sliders are used to set the background color.
|
||||
|
||||
== The sample
|
||||
|
||||
The sample shows how to setup an application to work with this extension:
|
||||
|
||||
* How to enable the extension.
|
||||
* How to set up multiple color attachments in the color blend state.
|
||||
* How to set up the render subpass and framebuffers for multiple color attachments.
|
||||
* How to write a fragment shader with multiple outputs.
|
||||
|
||||
== Documentation links
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_color_write_enable.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSubpassDescription.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkFramebufferCreateInfo.html
|
||||
|
||||
== See also
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetColorWriteMaskEXT.html
|
||||
@@ -1,587 +0,0 @@
|
||||
/* Copyright (c) 2023-2025, Mobica Limited
|
||||
*
|
||||
* 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 "color_write_enable.h"
|
||||
|
||||
ColorWriteEnable::ColorWriteEnable()
|
||||
{
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
ColorWriteEnable::~ColorWriteEnable()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.color, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.composition, nullptr);
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.color, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.composition, nullptr);
|
||||
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.color, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.composition, nullptr);
|
||||
|
||||
vkDestroySampler(get_device().get_handle(), samplers.red, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), samplers.green, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), samplers.blue, nullptr);
|
||||
|
||||
attachments.red.destroy(get_device().get_handle());
|
||||
attachments.green.destroy(get_device().get_handle());
|
||||
attachments.blue.destroy(get_device().get_handle());
|
||||
}
|
||||
}
|
||||
|
||||
bool ColorWriteEnable::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_set();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ColorWriteEnable::prepare_gui()
|
||||
{
|
||||
create_gui(*window, nullptr, 15.0f, true);
|
||||
get_gui().set_subpass(1);
|
||||
get_gui().prepare(pipeline_cache, render_pass,
|
||||
{load_shader("uioverlay/uioverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
|
||||
load_shader("uioverlay/uioverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)});
|
||||
}
|
||||
|
||||
void ColorWriteEnable::prepare_pipelines()
|
||||
{
|
||||
{
|
||||
// Create a pipeline for dynamic color attachments.
|
||||
VkPipelineLayoutCreateInfo layout_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.color, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &layout_info, nullptr, &pipeline_layouts.color));
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
|
||||
// Specify we will use triangle lists to draw geometry.
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly = vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
|
||||
|
||||
// Specify rasterization state.
|
||||
VkPipelineRasterizationStateCreateInfo raster = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
||||
|
||||
// Prepare separate attachment for each color channel.
|
||||
std::array<VkPipelineColorBlendAttachmentState, 3> blend_attachment = {
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(VK_COLOR_COMPONENT_R_BIT, VK_FALSE),
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(VK_COLOR_COMPONENT_G_BIT, VK_FALSE),
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(VK_COLOR_COMPONENT_B_BIT, VK_FALSE)};
|
||||
|
||||
// Define separate color_write_enable toggle for each color attachment.
|
||||
VkPipelineColorWriteCreateInfoEXT color_write_info = {VK_STRUCTURE_TYPE_PIPELINE_COLOR_WRITE_CREATE_INFO_EXT};
|
||||
std::array<VkBool32, 3> color_write_enables = {r_bit_enabled, g_bit_enabled, b_bit_enabled};
|
||||
color_write_info.attachmentCount = static_cast<uint32_t>(color_write_enables.size());
|
||||
color_write_info.pColorWriteEnables = color_write_enables.data();
|
||||
|
||||
// Define color blend with an attachment for each color. Chain it with color_write_info.
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state = vkb::initializers::pipeline_color_blend_state_create_info(blend_attachment.size(), blend_attachment.data());
|
||||
|
||||
color_blend_state.pNext = &color_write_info;
|
||||
|
||||
// We will have one viewport and scissor box.
|
||||
VkPipelineViewportStateCreateInfo viewport = vkb::initializers::pipeline_viewport_state_create_info(1, 1);
|
||||
|
||||
// No multisampling.
|
||||
VkPipelineMultisampleStateCreateInfo multisample = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
// Specify that these states will be dynamic, i.e. not part of pipeline state object.
|
||||
std::array<VkDynamicState, 3> dynamics{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT};
|
||||
VkPipelineDynamicStateCreateInfo dynamic = vkb::initializers::pipeline_dynamic_state_create_info(dynamics.data(), vkb::to_u32(dynamics.size()));
|
||||
|
||||
// Load our SPIR-V shaders.
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {};
|
||||
|
||||
// Vertex stage of the pipeline
|
||||
shader_stages[0] = load_shader("color_write_enable", "triangle_separate_channels.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("color_write_enable", "triangle_separate_channels.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// We need to specify the pipeline layout and the render pass description up front as well.
|
||||
VkGraphicsPipelineCreateInfo pipe = vkb::initializers::pipeline_create_info(pipeline_layouts.color, render_pass);
|
||||
pipe.stageCount = vkb::to_u32(shader_stages.size());
|
||||
pipe.pStages = shader_stages.data();
|
||||
pipe.pVertexInputState = &vertex_input;
|
||||
pipe.pInputAssemblyState = &input_assembly;
|
||||
pipe.pRasterizationState = &raster;
|
||||
pipe.pColorBlendState = &color_blend_state;
|
||||
pipe.pMultisampleState = &multisample;
|
||||
pipe.pViewportState = &viewport;
|
||||
pipe.pDynamicState = &dynamic;
|
||||
pipe.subpass = 0;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipe, nullptr, &pipelines.color));
|
||||
}
|
||||
{
|
||||
// Create a pipeline for the composition of inputs generated in the previous pipeline.
|
||||
VkPipelineLayoutCreateInfo composition_layout_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.composition, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &composition_layout_info, nullptr, &pipeline_layouts.composition));
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
|
||||
// Load our SPIR-V shaders.
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {};
|
||||
|
||||
// Vertex stage of the pipeline
|
||||
shader_stages[0] = load_shader("color_write_enable", "composition.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("color_write_enable", "composition.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Specify we will use triangle lists to draw geometry.
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly = vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
|
||||
|
||||
// Specify rasterization state.
|
||||
VkPipelineRasterizationStateCreateInfo raster = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
||||
|
||||
// Prepare separate attachment for each color channel.
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state = vkb::initializers::pipeline_color_blend_attachment_state(VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, VK_FALSE);
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state = vkb::initializers::pipeline_color_blend_state_create_info(1, &blend_attachment_state);
|
||||
|
||||
// We will have one viewport and scissor box.
|
||||
VkPipelineViewportStateCreateInfo viewport = vkb::initializers::pipeline_viewport_state_create_info(1, 1);
|
||||
|
||||
// No multisampling.
|
||||
VkPipelineMultisampleStateCreateInfo multisample = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
std::array<VkDynamicState, 2> dynamics{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
VkPipelineDynamicStateCreateInfo dynamic = vkb::initializers::pipeline_dynamic_state_create_info(dynamics.data(), vkb::to_u32(dynamics.size()));
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipe = vkb::initializers::pipeline_create_info(pipeline_layouts.color, render_pass);
|
||||
pipe.stageCount = vkb::to_u32(shader_stages.size());
|
||||
pipe.pStages = shader_stages.data();
|
||||
pipe.pVertexInputState = &vertex_input;
|
||||
pipe.pInputAssemblyState = &input_assembly;
|
||||
pipe.pRasterizationState = &raster;
|
||||
pipe.pColorBlendState = &color_blend_state;
|
||||
pipe.pMultisampleState = &multisample;
|
||||
pipe.pViewportState = &viewport;
|
||||
pipe.pDynamicState = &dynamic;
|
||||
pipe.subpass = 1;
|
||||
|
||||
width = get_render_context().get_surface_extent().width;
|
||||
height = get_render_context().get_surface_extent().height;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipe, nullptr, &pipelines.composition));
|
||||
}
|
||||
}
|
||||
|
||||
// Create attachment that will be used in a framebuffer.
|
||||
void ColorWriteEnable::create_attachment(VkFormat format, FrameBufferAttachment *attachment)
|
||||
{
|
||||
attachment->format = format;
|
||||
|
||||
VkImageCreateInfo image = vkb::initializers::image_create_info();
|
||||
image.imageType = VK_IMAGE_TYPE_2D;
|
||||
image.format = format;
|
||||
image.extent.width = get_render_context().get_surface_extent().width;
|
||||
image.extent.height = get_render_context().get_surface_extent().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 = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_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 = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_view_create_info.subresourceRange.baseMipLevel = 0;
|
||||
image_view_create_info.subresourceRange.levelCount = 1;
|
||||
image_view_create_info.subresourceRange.baseArrayLayer = 0;
|
||||
image_view_create_info.subresourceRange.layerCount = 1;
|
||||
image_view_create_info.image = attachment->image;
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view_create_info, nullptr, &attachment->view));
|
||||
}
|
||||
|
||||
// Create attachments for each framebuffer used in the first pipeline
|
||||
void ColorWriteEnable::create_attachments()
|
||||
{
|
||||
VkFormat format = get_render_context().get_format();
|
||||
create_attachment(format, &attachments.red);
|
||||
create_attachment(format, &attachments.green);
|
||||
create_attachment(format, &attachments.blue);
|
||||
}
|
||||
|
||||
void ColorWriteEnable::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceColorWriteEnableFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT,
|
||||
colorWriteEnable);
|
||||
|
||||
{
|
||||
auto &features = gpu.get_mutable_requested_features();
|
||||
features.independentBlend = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void ColorWriteEnable::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> poolSizes =
|
||||
{
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 3)};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(poolSizes.size()),
|
||||
poolSizes.data(),
|
||||
1);
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptorPoolInfo, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
//
|
||||
void ColorWriteEnable::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 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.color));
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layouts.composition));
|
||||
}
|
||||
|
||||
void ColorWriteEnable::setup_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo 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));
|
||||
|
||||
VkDescriptorImageInfo red = vkb::initializers::descriptor_image_info(samplers.red, attachments.red.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
VkDescriptorImageInfo green = vkb::initializers::descriptor_image_info(samplers.green, attachments.green.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
VkDescriptorImageInfo blue = vkb::initializers::descriptor_image_info(samplers.blue, attachments.blue.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 0, &red),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1, &green),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 2, &blue),
|
||||
|
||||
};
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
void ColorWriteEnable::setup_render_pass()
|
||||
{
|
||||
attachments.width = width;
|
||||
attachments.height = height;
|
||||
|
||||
create_attachments();
|
||||
|
||||
// Create color attachments:
|
||||
// - attachment 0 is for the composition image,
|
||||
// - attachments 1 to 3 are for each blend attachment.
|
||||
std::array<VkAttachmentDescription, 4> attachments = {};
|
||||
|
||||
for (uint32_t i = 0; i < attachments.size(); ++i)
|
||||
{
|
||||
attachments[i].format = get_render_context().get_format();
|
||||
attachments[i].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[i].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
}
|
||||
|
||||
std::array<VkAttachmentReference, 4> color_references = {
|
||||
VkAttachmentReference{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
|
||||
VkAttachmentReference{1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
|
||||
VkAttachmentReference{2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
|
||||
VkAttachmentReference{3, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}};
|
||||
|
||||
// Two subpasses are defined:
|
||||
std::array<VkSubpassDescription, 2> subpass_descriptions{};
|
||||
|
||||
// - first, with 3 color attachments for each blend attachment,
|
||||
subpass_descriptions[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass_descriptions[0].colorAttachmentCount = 3;
|
||||
subpass_descriptions[0].pColorAttachments = &color_references.data()[1];
|
||||
subpass_descriptions[0].pDepthStencilAttachment = nullptr;
|
||||
|
||||
// - second, with a single attachment. It takes input from the attachments 1 to 3.
|
||||
std::array<VkAttachmentReference, 3> input_references = {
|
||||
VkAttachmentReference{1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
|
||||
VkAttachmentReference{2, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
|
||||
VkAttachmentReference{3, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}};
|
||||
|
||||
subpass_descriptions[1].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass_descriptions[1].colorAttachmentCount = 1;
|
||||
subpass_descriptions[1].pColorAttachments = color_references.data();
|
||||
subpass_descriptions[1].pDepthStencilAttachment = nullptr;
|
||||
subpass_descriptions[1].inputAttachmentCount = static_cast<uint32_t>(input_references.size());
|
||||
subpass_descriptions[1].pInputAttachments = input_references.data();
|
||||
|
||||
// Subpass dependencies for layout transitions.
|
||||
std::array<VkSubpassDependency, 3> dependencies = {};
|
||||
|
||||
// External to color pass.
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
// Color pass to composition pass.
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = 1;
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
// Composition pass to external.
|
||||
dependencies[2].srcSubpass = 1;
|
||||
dependencies[2].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[2].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[2].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[2].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[2].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[2].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
// Create render pass.
|
||||
VkRenderPassCreateInfo render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_create_info.attachmentCount = static_cast<uint32_t>(attachments.size());
|
||||
render_pass_create_info.pAttachments = attachments.data();
|
||||
render_pass_create_info.subpassCount = static_cast<uint32_t>(subpass_descriptions.size());
|
||||
render_pass_create_info.pSubpasses = subpass_descriptions.data();
|
||||
render_pass_create_info.dependencyCount = static_cast<uint32_t>(dependencies.size());
|
||||
render_pass_create_info.pDependencies = dependencies.data();
|
||||
|
||||
VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &render_pass));
|
||||
|
||||
// Create sampler for each color attachment.
|
||||
VkSamplerCreateInfo sampler = vkb::initializers::sampler_create_info();
|
||||
sampler.magFilter = VK_FILTER_NEAREST;
|
||||
sampler.minFilter = VK_FILTER_NEAREST;
|
||||
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
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, &samplers.red));
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler, nullptr, &samplers.green));
|
||||
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler, nullptr, &samplers.blue));
|
||||
}
|
||||
|
||||
void ColorWriteEnable::setup_framebuffer()
|
||||
{
|
||||
// Regenerate attachments on window resize.
|
||||
if (attachments.width != width || attachments.height != height)
|
||||
{
|
||||
attachments.width = width;
|
||||
attachments.height = height;
|
||||
|
||||
attachments.red.destroy(get_device().get_handle());
|
||||
attachments.green.destroy(get_device().get_handle());
|
||||
attachments.blue.destroy(get_device().get_handle());
|
||||
|
||||
create_attachments();
|
||||
|
||||
VkDescriptorImageInfo red = vkb::initializers::descriptor_image_info(samplers.red, attachments.red.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
VkDescriptorImageInfo green = vkb::initializers::descriptor_image_info(samplers.green, attachments.green.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
VkDescriptorImageInfo blue = vkb::initializers::descriptor_image_info(samplers.blue, attachments.blue.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 0, &red),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1, &green),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 2, &blue),
|
||||
};
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
std::array<VkImageView, 4> attachments;
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = {};
|
||||
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebuffer_create_info.pNext = NULL;
|
||||
framebuffer_create_info.renderPass = render_pass;
|
||||
framebuffer_create_info.attachmentCount = static_cast<uint32_t>(attachments.size());
|
||||
framebuffer_create_info.pAttachments = attachments.data();
|
||||
framebuffer_create_info.width = get_render_context().get_surface_extent().width;
|
||||
framebuffer_create_info.height = get_render_context().get_surface_extent().height;
|
||||
framebuffer_create_info.layers = 1;
|
||||
|
||||
// Set attachments to the corresponding framebuffers.
|
||||
attachments[1] = this->attachments.red.view;
|
||||
attachments[2] = this->attachments.green.view;
|
||||
attachments[3] = this->attachments.blue.view;
|
||||
|
||||
framebuffers.resize(get_render_context().get_render_frames().size());
|
||||
for (uint32_t i = 0; i < framebuffers.size(); i++)
|
||||
{
|
||||
attachments[0] = swapchain_buffers[i].view;
|
||||
VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, &framebuffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void ColorWriteEnable::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
// Clear color values.
|
||||
std::array<VkClearValue, 4> clear_values = {};
|
||||
clear_values[0].color = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
clear_values[1].color = {background_color[0], 0.0f, 0.0f, 0.0f};
|
||||
clear_values[2].color = {0.0f, background_color[1], 0.0f, 0.0f};
|
||||
clear_values[3].color = {0.0f, 0.0f, background_color[2], 0.0f};
|
||||
|
||||
// Begin the render pass.
|
||||
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 = clear_values.size();
|
||||
render_pass_begin_info.pClearValues = clear_values.data();
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
auto cmd = draw_cmd_buffers[i];
|
||||
|
||||
// Begin command buffer.
|
||||
vkBeginCommandBuffer(cmd, &command_buffer_begin_info);
|
||||
|
||||
// Set framebuffer for this command buffer.
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
// We will add draw commands in the same command buffer.
|
||||
vkCmdBeginRenderPass(cmd, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
// Bind a pipeline for dynamic attachments.
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.color);
|
||||
|
||||
// Set viewport dynamically.
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
|
||||
// Set scissor dynamically.
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
// Toggle color_write_enable for each attachment dynamically.
|
||||
std::array<VkBool32, 3> color_write_enables = {r_bit_enabled, g_bit_enabled, b_bit_enabled};
|
||||
vkCmdSetColorWriteEnableEXT(cmd, static_cast<uint32_t>(color_write_enables.size()), color_write_enables.data());
|
||||
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
|
||||
// Advance to subpass 1.
|
||||
vkCmdNextSubpass(cmd, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
// Bind a pipeline for the composition.
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition);
|
||||
|
||||
// Bind descriptors for the active pipeline.
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.composition, 0, 1, &descriptor_sets.composition, 0, NULL);
|
||||
|
||||
// Draw composition image.
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
|
||||
// Draw user interface.
|
||||
draw_ui(cmd);
|
||||
|
||||
// Complete render pass.
|
||||
vkCmdEndRenderPass(cmd);
|
||||
|
||||
// Complete the command buffer.
|
||||
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void ColorWriteEnable::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Background color"))
|
||||
{
|
||||
if (drawer.color_op<vkb::Drawer::ColorOp::Pick>("", background_color, 0,
|
||||
ImGuiColorEditFlags_NoSidePreview |
|
||||
ImGuiColorEditFlags_NoSmallPreview |
|
||||
ImGuiColorEditFlags_Float |
|
||||
ImGuiColorEditFlags_HDR))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
if (drawer.header("Enabled attachment"))
|
||||
{
|
||||
if (drawer.checkbox("Red bit", &r_bit_enabled))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Green bit", &g_bit_enabled))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Blue bit", &b_bit_enabled))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColorWriteEnable::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();
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_color_write_enable()
|
||||
{
|
||||
return std::make_unique<ColorWriteEnable>();
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/* Copyright (c) 2023, Mobica Limited
|
||||
*
|
||||
* 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"
|
||||
|
||||
/**
|
||||
* @brief Color generation toggle using VK_EXT_color_write_enable
|
||||
*/
|
||||
|
||||
class ColorWriteEnable : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
ColorWriteEnable();
|
||||
virtual ~ColorWriteEnable();
|
||||
|
||||
// Create pipeline
|
||||
void prepare_pipelines();
|
||||
|
||||
// Override basic framework functionality
|
||||
void build_command_buffers() override;
|
||||
void render(float delta_time) override;
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void setup_render_pass() override;
|
||||
void setup_framebuffer() override;
|
||||
void prepare_gui() override;
|
||||
|
||||
private:
|
||||
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
|
||||
{
|
||||
FrameBufferAttachment red, green, blue;
|
||||
int32_t width, height;
|
||||
} attachments;
|
||||
|
||||
struct
|
||||
{
|
||||
VkSampler red, green, blue;
|
||||
} samplers;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipeline color, composition;
|
||||
} pipelines;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout color, composition;
|
||||
} pipeline_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSetLayout color, composition;
|
||||
} descriptor_set_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet composition;
|
||||
} descriptor_sets;
|
||||
|
||||
void create_attachment(VkFormat format, FrameBufferAttachment *attachment);
|
||||
void create_attachments();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
|
||||
bool r_bit_enabled = true;
|
||||
bool g_bit_enabled = true;
|
||||
bool b_bit_enabled = true;
|
||||
|
||||
std::array<float, 3> background_color = {0.5f, 0.5f, 0.5f};
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::Application> create_color_write_enable();
|
||||
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2022-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(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "Conditional rendering"
|
||||
DESCRIPTION "Demonstrates usage of VK_EXT_conditional_rendering for conditionally toggling the visibility of sub-meshes of a complex glTF model."
|
||||
SHADER_FILES_GLSL
|
||||
"conditional_rendering/glsl/model.vert"
|
||||
"conditional_rendering/glsl/model.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"conditional_rendering/hlsl/model.vert.hlsl"
|
||||
"conditional_rendering/hlsl/model.frag.hlsl")
|
||||
@@ -1,171 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2022-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.
|
||||
-
|
||||
////
|
||||
= Conditional rendering
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/conditional_rendering[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
:pp: {plus}{plus}
|
||||
|
||||
image::./images/sample.png[Sample]
|
||||
|
||||
== Overview
|
||||
|
||||
The https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_EXT_conditional_rendering.html[VK_EXT_conditional_rendering] extension allows the execution of rendering commands to be conditional based on a value taken from a dedicated conditional buffer.
|
||||
This may help an application reduce the latency by conditionally discarding rendering commands without application intervention.
|
||||
|
||||
This sample demonstrates usage of this extension for conditionally toggling the visibility of sub-meshes of a complex glTF model.
|
||||
Instead of having to update command buffers, this is done by updating the aforementioned buffer.
|
||||
|
||||
== Conditional buffer
|
||||
|
||||
As mentioned in the introduction a buffer is used to conditionally execute rendering and dispatch commands (for compute, which is not done in this sample).
|
||||
The first step is setting up this buffer.
|
||||
|
||||
Important notes on setting up a conditional buffer:
|
||||
|
||||
* A *dedicated buffer type* named `VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT`
|
||||
* The buffer format is fixed to consecutive *32-bit values*
|
||||
* Offset is also aligned at 32-bits
|
||||
|
||||
The fixed alignment makes it easy to map this to C/C{pp} host structures:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
std::vector<int32_t> conditional_visibility_list;
|
||||
----
|
||||
|
||||
Setting up the buffer is no different from other buffers:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
conditional_visibility_buffer =
|
||||
std::make_unique<vkb::core::Buffer>(get_device(),
|
||||
sizeof(int32_t) * conditional_visibility_list.size(),
|
||||
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
----
|
||||
|
||||
With this we get a buffer that matches the size and layout of the host application.
|
||||
For simplicity we create a host visible buffer in this sample.
|
||||
Depending on the use-case a device local buffer would yield better performance but would also require a different update strategy
|
||||
|
||||
== Conditional execution
|
||||
|
||||
The extension introduces two new functions that allow you to mark regions of a command buffer for conditional execution:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Begins a new conditional rendering block
|
||||
void vkCmdBeginConditionalRenderingEXT(VkCommandBuffer commandBuffer, const VkConditionalRenderingBeginInfoEXT* pConditionalRenderingBegin)
|
||||
// Ends the current conditional rendering block
|
||||
void vkCmdEndConditionalRenderingEXT(VkCommandBuffer commandBuffer)
|
||||
----
|
||||
|
||||
Wrapping drawing and/or dispatch commands in such regions will result in them only being executed if our conditional buffer contains a non-zero value at the given offset.
|
||||
|
||||
A basic example of this could look like this:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkConditionalRenderingBeginInfoEXT conditional_rendering_info{};
|
||||
conditional_rendering_info.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
|
||||
conditional_rendering_info.buffer = conditional_buffer.buffer;
|
||||
conditional_rendering_info.offset = current_mesh_index * sizeof(int32_t);
|
||||
|
||||
vkCmdBeginConditionalRenderingEXT(command_buffer, &conditional_rendering_info);
|
||||
vkCmdDrawIndexed(...);
|
||||
vkCmdEndConditionalRenderingEXT(command_buffer);
|
||||
----
|
||||
|
||||
The conditional_rendering_info structure contains the parameters used by the `vkCmdBeginConditionalRenderingEXT` function to determine if the commands in that region are to be executed.
|
||||
|
||||
So for this basic example if the 32-bit conditional buffer value at the selected offset is zero, the `vkCmdDrawIndexed` will not be executed.
|
||||
|
||||
Changing the buffer value at the select offset 0 to 1 and synchronizing the buffer will have the draw command executed for the next draw.
|
||||
|
||||
Moving to the actual example we create a conditional buffer with one 32-bit value per node in the glTF scene:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Setup the host visilibty list
|
||||
conditional_visibility_list.resize(linear_scene_nodes.size());
|
||||
std::fill(conditional_visibility_list.begin(), conditional_visibility_list.end(), 1);
|
||||
|
||||
// Create a buffer to hold the visibility list
|
||||
conditional_visibility_buffer =
|
||||
std::make_unique<vkb::core::Buffer>(get_device(),
|
||||
sizeof(int32_t) * conditional_visibility_list.size(),
|
||||
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Copy the current visibility list to the dedicated buffer
|
||||
conditional_visibility_buffer->update(conditional_visibility_list.data(), sizeof(int32_t) * conditional_visibility_list.size());
|
||||
----
|
||||
|
||||
Using this setup, each visible glTF node maps to an entry in the conditional visibility buffer by it's unique node index, calculated as `node_index * sizeof(int32_t)`:
|
||||
|
||||
image::./images/conditional-buffer-mapping.png[Buffer mapping]
|
||||
|
||||
So we can now control draws using values stored in the conditional buffer.
|
||||
To do so, the command buffer iterates over all nodes of the gltF scene (put into a linear vector for convenience) and wraps the draw command for each node in a conditional rendering block, so a node is only drawn when the visibility buffer value at it's offset equals 1:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
uint32_t node_index = 0;
|
||||
for (auto &node : linear_scene_nodes)
|
||||
{
|
||||
glm::mat4 node_transform = node.node->get_transform().get_world_matrix();
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
|
||||
const auto &vertex_buffer_pos = node.sub_mesh->vertex_buffers.at("position");
|
||||
const auto &vertex_buffer_normal = node.sub_mesh->vertex_buffers.at("normal");
|
||||
auto & index_buffer = node.sub_mesh->index_buffer;
|
||||
|
||||
auto mat = dynamic_cast<const vkb::sg::PBRMaterial *>(node.sub_mesh->get_material());
|
||||
|
||||
// Start a conditional rendering block, commands in this block are only executed if the buffer at the current position is 1 at command buffer submission time
|
||||
VkConditionalRenderingBeginInfoEXT conditional_rendering_info{};
|
||||
conditional_rendering_info.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
|
||||
conditional_rendering_info.buffer = conditional_visibility_buffer->get_handle();
|
||||
// We offset into the visibility buffer based on the index of the node to be drawn
|
||||
conditional_rendering_info.offset = sizeof(int32_t) * node_index;
|
||||
vkCmdBeginConditionalRenderingEXT(draw_cmd_buffers[i], &conditional_rendering_info);
|
||||
|
||||
// Pass data for the current node via push commands
|
||||
push_const_block.model_matrix = node_transform;
|
||||
push_const_block.color = glm::vec4(mat->base_color_factor.rgb, 1.0f);
|
||||
vkCmdPushConstants(draw_cmd_buffers[i], pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_const_block), &push_const_block);
|
||||
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer_pos.get(), offsets);
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 1, 1, vertex_buffer_normal.get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, node.sub_mesh->index_type);
|
||||
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], node.sub_mesh->vertex_indices, 1, 0, 0, 0);
|
||||
|
||||
// End the conditional rendering block
|
||||
vkCmdEndConditionalRenderingEXT(draw_cmd_buffers[i]);
|
||||
|
||||
node_index++;
|
||||
}
|
||||
----
|
||||
|
||||
With the above command buffer setup, we can toggle visibility of each node in the glTF scene by just changing the conditional buffer value at the node's offsets.
|
||||
@@ -1,423 +0,0 @@
|
||||
/* Copyright (c) 2022-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Using VK_EXT_conditional_rendering, which executes or discards draw commands based on values sourced from a buffer
|
||||
*/
|
||||
|
||||
#include "conditional_rendering.h"
|
||||
|
||||
#include "gltf_loader.h"
|
||||
#include "scene_graph/components/mesh.h"
|
||||
#include "scene_graph/components/pbr_material.h"
|
||||
#include "scene_graph/components/sub_mesh.h"
|
||||
|
||||
ConditionalRendering::ConditionalRendering()
|
||||
{
|
||||
title = "Conditional rendering";
|
||||
add_device_extension(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
ConditionalRendering::~ConditionalRendering()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionalRendering::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// We need to enable conditional rendering using a new feature struct
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceConditionalRenderingFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT,
|
||||
conditionalRendering);
|
||||
}
|
||||
|
||||
void ConditionalRendering::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.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));
|
||||
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
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);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, nullptr);
|
||||
|
||||
uint32_t node_index = 0;
|
||||
for (auto &node : linear_scene_nodes)
|
||||
{
|
||||
const auto &vertex_buffer_pos = node.sub_mesh->vertex_buffers.at("position");
|
||||
const auto &vertex_buffer_normal = node.sub_mesh->vertex_buffers.at("normal");
|
||||
auto &index_buffer = node.sub_mesh->index_buffer;
|
||||
|
||||
// Start a conditional rendering block, commands in this block are only executed if the buffer at the current position is 1 at command buffer submission time
|
||||
VkConditionalRenderingBeginInfoEXT conditional_rendering_info{};
|
||||
conditional_rendering_info.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
|
||||
conditional_rendering_info.buffer = conditional_visibility_buffer->get_handle();
|
||||
// We offset into the visibility buffer based on the index of the node to be drawn
|
||||
conditional_rendering_info.offset = sizeof(int32_t) * node_index;
|
||||
vkCmdBeginConditionalRenderingEXT(draw_cmd_buffers[i], &conditional_rendering_info);
|
||||
|
||||
// Pass data for the current node via push commands
|
||||
auto node_material = dynamic_cast<const vkb::sg::PBRMaterial *>(node.sub_mesh->get_material());
|
||||
push_const_block.model_matrix = node.node->get_transform().get_world_matrix();
|
||||
push_const_block.color = glm::vec4(node_material->base_color_factor.rgb, 1.0f);
|
||||
vkCmdPushConstants(draw_cmd_buffers[i], pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_const_block), &push_const_block);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer_pos.get(), offsets);
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 1, 1, vertex_buffer_normal.get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, node.sub_mesh->index_type);
|
||||
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], node.sub_mesh->vertex_indices, 1, 0, 0, 0);
|
||||
|
||||
// End the conditional rendering block
|
||||
vkCmdEndConditionalRenderingEXT(draw_cmd_buffers[i]);
|
||||
|
||||
node_index++;
|
||||
}
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionalRendering::load_assets()
|
||||
{
|
||||
vkb::GLTFLoader loader{get_device()};
|
||||
scene = loader.read_scene_from_file("scenes/Buggy/glTF-Embedded/Buggy.gltf");
|
||||
assert(scene);
|
||||
// Store all scene nodes in a linear vector for easier access
|
||||
for (auto &mesh : scene->get_components<vkb::sg::Mesh>())
|
||||
{
|
||||
for (auto &node : mesh->get_nodes())
|
||||
{
|
||||
for (auto &sub_mesh : mesh->get_submeshes())
|
||||
{
|
||||
linear_scene_nodes.push_back({mesh->get_name(), node, sub_mesh});
|
||||
}
|
||||
}
|
||||
}
|
||||
// By default, all nodes should be visible, so we initialize the list with ones for each element
|
||||
conditional_visibility_list.resize(linear_scene_nodes.size());
|
||||
std::fill(conditional_visibility_list.begin(), conditional_visibility_list.end(), 1);
|
||||
}
|
||||
|
||||
void ConditionalRendering::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4)};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(static_cast<uint32_t>(pool_sizes.size()), pool_sizes.data(), 1);
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void ConditionalRendering::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)};
|
||||
|
||||
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));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
// Pass scene node information via push constants
|
||||
VkPushConstantRange push_constant_range = vkb::initializers::push_constant_range(VK_SHADER_STAGE_VERTEX_BIT, sizeof(push_const_block), 0);
|
||||
pipeline_layout_create_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_create_info.pPushConstantRanges = &push_constant_range;
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void ConditionalRendering::setup_descriptor_sets()
|
||||
{
|
||||
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 matrix_buffer_descriptor = create_descriptor(*uniform_buffer);
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_buffer_descriptor)};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void ConditionalRendering::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_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);
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(
|
||||
pipeline_layout,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
std::vector<VkPipelineColorBlendAttachmentState> blend_attachment_states = {
|
||||
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;
|
||||
pipeline_create_info.layout = pipeline_layout;
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
// Vertex bindings an attributes for model rendering
|
||||
// Binding description, we use separate buffers for the vertex attributes
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
vkb::initializers::vertex_input_binding_description(1, sizeof(glm::vec3), 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(1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0), // 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;
|
||||
|
||||
shader_stages[0] = load_shader("conditional_rendering", "model.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("conditional_rendering", "model.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void ConditionalRendering::prepare_uniform_buffers()
|
||||
{
|
||||
// Matrices vertex shader uniform buffer
|
||||
uniform_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(uniform_data),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void ConditionalRendering::update_uniform_buffers()
|
||||
{
|
||||
uniform_data.projection = camera.matrices.perspective;
|
||||
// Scale the view matrix as the model is pretty large, and also flip it upside down
|
||||
uniform_data.view = glm::scale(camera.matrices.view, glm::vec3(0.1f, -0.1f, 0.1f));
|
||||
uniform_buffer->convert_and_update(uniform_data);
|
||||
}
|
||||
|
||||
// Creates a dedicated buffer to store the visibility information sourced at draw time
|
||||
void ConditionalRendering::prepare_visibility_buffer()
|
||||
{
|
||||
// Conditional values are 32 bits wide and if it's zero the rendering commands are discarded
|
||||
// We therefore create a buffer that can hold int32 conditional values for all nodes in the glTF scene
|
||||
// The extension also introduces the new buffer usage flag VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT that we need to set
|
||||
conditional_visibility_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(int32_t) * conditional_visibility_list.size(),
|
||||
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_visibility_buffer();
|
||||
}
|
||||
|
||||
// Updates the visibility buffer with the currently selected node visibility
|
||||
void ConditionalRendering::update_visibility_buffer()
|
||||
{
|
||||
conditional_visibility_buffer->update(conditional_visibility_list.data(), sizeof(int32_t) * conditional_visibility_list.size());
|
||||
}
|
||||
|
||||
void ConditionalRendering::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 ConditionalRendering::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position(glm::vec3(1.9f, 2.05f, -18.0f));
|
||||
camera.set_rotation(glm::vec3(-11.25f, -38.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_visibility_buffer();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_sets();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConditionalRendering::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void ConditionalRendering::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Visibility"))
|
||||
{
|
||||
if (drawer.button("All"))
|
||||
{
|
||||
std::fill(conditional_visibility_list.begin(), conditional_visibility_list.end(), 1);
|
||||
update_visibility_buffer();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (drawer.button("None"))
|
||||
{
|
||||
std::fill(conditional_visibility_list.begin(), conditional_visibility_list.end(), 0);
|
||||
update_visibility_buffer();
|
||||
}
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::BeginChild("InnerRegion", ImVec2(200.0f, 400.0f), false);
|
||||
uint32_t idx = 0;
|
||||
for (auto &node : linear_scene_nodes)
|
||||
{
|
||||
if (drawer.checkbox(("[" + std::to_string(idx) + "] " + node.name).c_str(), &conditional_visibility_list[idx]))
|
||||
{
|
||||
update_visibility_buffer();
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConditionalRendering::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::Application> create_conditional_rendering()
|
||||
{
|
||||
return std::make_unique<ConditionalRendering>();
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/* Copyright (c) 2023-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Using VK_EXT_conditional_rendering, which executes or discards draw commands based on values sourced from a buffer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class ConditionalRendering : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer = nullptr;
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer = nullptr;
|
||||
|
||||
std::unique_ptr<vkb::sg::Scene> scene;
|
||||
struct SceneNode
|
||||
{
|
||||
std::string name;
|
||||
vkb::sg::Node *node;
|
||||
vkb::sg::SubMesh *sub_mesh;
|
||||
};
|
||||
std::vector<SceneNode> linear_scene_nodes;
|
||||
|
||||
struct UniformData
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
} uniform_data;
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer;
|
||||
|
||||
VkPipeline pipeline;
|
||||
VkPipelineLayout pipeline_layout;
|
||||
VkDescriptorSet descriptor_set;
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::mat4 model_matrix;
|
||||
glm::vec4 color;
|
||||
} push_const_block;
|
||||
|
||||
std::vector<int32_t> conditional_visibility_list;
|
||||
std::unique_ptr<vkb::core::BufferC> conditional_visibility_buffer;
|
||||
|
||||
ConditionalRendering();
|
||||
~ConditionalRendering();
|
||||
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_sets();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void prepare_visibility_buffer();
|
||||
void update_visibility_buffer();
|
||||
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_conditional_rendering();
|
||||
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 186 KiB |
@@ -1,39 +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 "Conservative rasterization"
|
||||
DESCRIPTION "Uses an offscreen buffer with lower resolution to demonstrate the effect of conservative rasterization (VK_EXT_conservative_rasterization)"
|
||||
SHADER_FILES_GLSL
|
||||
"conservative_rasterization/glsl/fullscreen.vert"
|
||||
"conservative_rasterization/glsl/fullscreen.frag"
|
||||
"conservative_rasterization/glsl/triangle.vert"
|
||||
"conservative_rasterization/glsl/triangle.frag"
|
||||
"conservative_rasterization/glsl/triangleoverlay.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"conservative_rasterization/hlsl/fullscreen.vert.hlsl"
|
||||
"conservative_rasterization/hlsl/fullscreen.frag.hlsl"
|
||||
"conservative_rasterization/hlsl/triangle.vert.hlsl"
|
||||
"conservative_rasterization/hlsl/triangle.frag.hlsl"
|
||||
"conservative_rasterization/hlsl/triangleoverlay.frag.hlsl")
|
||||
@@ -1,30 +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.
|
||||
-
|
||||
////
|
||||
|
||||
= Conservative Rasterization
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/conservative_rasterization[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
*Extension*: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_EXT_conservative_rasterization[`VK_EXT_conservative_rasterization`]
|
||||
|
||||
Uses conservative rasterization to change the way fragments are generated.
|
||||
Enables overestimation to generate fragments for every pixel touched instead of only pixels that are fully covered.
|
||||
@@ -1,638 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Conservative rasterization
|
||||
*
|
||||
* Note: Requires a device that supports the VK_EXT_conservative_rasterization extension
|
||||
*
|
||||
* Uses an offscreen buffer with lower resolution to demonstrate the effect of conservative rasterization
|
||||
*/
|
||||
|
||||
#include "conservative_rasterization.h"
|
||||
|
||||
#define FB_COLOR_FORMAT VK_FORMAT_R8G8B8A8_UNORM
|
||||
#define ZOOM_FACTOR 16
|
||||
|
||||
ConservativeRasterization::ConservativeRasterization()
|
||||
{
|
||||
title = "Conservative rasterization";
|
||||
|
||||
// Reading device properties of conservative rasterization requires VK_KHR_get_physical_device_properties2 to be enabled
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
|
||||
// Enable extension required for conservative rasterization
|
||||
add_device_extension(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
ConservativeRasterization::~ConservativeRasterization()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyImageView(get_device().get_handle(), offscreen_pass.color.view, nullptr);
|
||||
vkDestroyImage(get_device().get_handle(), offscreen_pass.color.image, nullptr);
|
||||
vkFreeMemory(get_device().get_handle(), offscreen_pass.color.mem, nullptr);
|
||||
vkDestroyImageView(get_device().get_handle(), offscreen_pass.depth.view, nullptr);
|
||||
vkDestroyImage(get_device().get_handle(), offscreen_pass.depth.image, nullptr);
|
||||
vkFreeMemory(get_device().get_handle(), offscreen_pass.depth.mem, nullptr);
|
||||
|
||||
vkDestroyRenderPass(get_device().get_handle(), offscreen_pass.render_pass, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), offscreen_pass.sampler, nullptr);
|
||||
vkDestroyFramebuffer(get_device().get_handle(), offscreen_pass.framebuffer, nullptr);
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.triangle, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.triangle_overlay, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.triangle_conservative_raster, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.fullscreen, nullptr);
|
||||
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.fullscreen, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.scene, nullptr);
|
||||
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.scene, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.fullscreen, nullptr);
|
||||
}
|
||||
|
||||
uniform_buffers.scene.reset();
|
||||
triangle.vertices.reset();
|
||||
triangle.indices.reset();
|
||||
}
|
||||
|
||||
void ConservativeRasterization::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
gpu.get_mutable_requested_features().fillModeNonSolid = gpu.get_features().fillModeNonSolid;
|
||||
gpu.get_mutable_requested_features().wideLines = gpu.get_features().wideLines;
|
||||
}
|
||||
|
||||
// Setup offscreen framebuffer, attachments and render passes for lower resolution rendering of the scene
|
||||
void ConservativeRasterization::prepare_offscreen()
|
||||
{
|
||||
offscreen_pass.width = width / ZOOM_FACTOR;
|
||||
offscreen_pass.height = height / ZOOM_FACTOR;
|
||||
|
||||
// Find a suitable depth format
|
||||
VkFormat framebuffer_depth_format = vkb::get_suitable_depth_format(get_device().get_gpu().get_handle());
|
||||
|
||||
// Color attachment
|
||||
VkImageCreateInfo image = vkb::initializers::image_create_info();
|
||||
image.imageType = VK_IMAGE_TYPE_2D;
|
||||
image.format = FB_COLOR_FORMAT;
|
||||
image.extent.width = offscreen_pass.width;
|
||||
image.extent.height = offscreen_pass.height;
|
||||
image.extent.depth = 1;
|
||||
image.mipLevels = 1;
|
||||
image.arrayLayers = 1;
|
||||
image.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
// We will sample directly from the color attachment
|
||||
image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
VkMemoryAllocateInfo memory_allocation_info = vkb::initializers::memory_allocate_info();
|
||||
VkMemoryRequirements memory_requirements;
|
||||
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image, nullptr, &offscreen_pass.color.image));
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), offscreen_pass.color.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, &offscreen_pass.color.mem));
|
||||
VK_CHECK(vkBindImageMemory(get_device().get_handle(), offscreen_pass.color.image, offscreen_pass.color.mem, 0));
|
||||
|
||||
VkImageViewCreateInfo color_image_view = vkb::initializers::image_view_create_info();
|
||||
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
color_image_view.format = FB_COLOR_FORMAT;
|
||||
color_image_view.subresourceRange = {};
|
||||
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
color_image_view.subresourceRange.baseMipLevel = 0;
|
||||
color_image_view.subresourceRange.levelCount = 1;
|
||||
color_image_view.subresourceRange.baseArrayLayer = 0;
|
||||
color_image_view.subresourceRange.layerCount = 1;
|
||||
color_image_view.image = offscreen_pass.color.image;
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &color_image_view, nullptr, &offscreen_pass.color.view));
|
||||
|
||||
// Create sampler to sample from the attachment in the fragment shader
|
||||
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_LINEAR;
|
||||
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
sampler_info.addressModeV = sampler_info.addressModeU;
|
||||
sampler_info.addressModeW = sampler_info.addressModeU;
|
||||
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, &offscreen_pass.sampler));
|
||||
|
||||
// Depth attachment
|
||||
image.format = framebuffer_depth_format;
|
||||
image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image, nullptr, &offscreen_pass.depth.image));
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), offscreen_pass.depth.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, &offscreen_pass.depth.mem));
|
||||
VK_CHECK(vkBindImageMemory(get_device().get_handle(), offscreen_pass.depth.image, offscreen_pass.depth.mem, 0));
|
||||
|
||||
// The depth format we get for the current device may not include a stencil part, which affects the aspect mask used by the image view
|
||||
const VkImageAspectFlags aspect_mask = vkb::is_depth_only_format(framebuffer_depth_format) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
|
||||
VkImageViewCreateInfo depth_stencil_view = vkb::initializers::image_view_create_info();
|
||||
depth_stencil_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
depth_stencil_view.format = framebuffer_depth_format;
|
||||
depth_stencil_view.flags = 0;
|
||||
depth_stencil_view.subresourceRange = {};
|
||||
depth_stencil_view.subresourceRange.aspectMask = aspect_mask;
|
||||
depth_stencil_view.subresourceRange.baseMipLevel = 0;
|
||||
depth_stencil_view.subresourceRange.levelCount = 1;
|
||||
depth_stencil_view.subresourceRange.baseArrayLayer = 0;
|
||||
depth_stencil_view.subresourceRange.layerCount = 1;
|
||||
depth_stencil_view.image = offscreen_pass.depth.image;
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &depth_stencil_view, nullptr, &offscreen_pass.depth.view));
|
||||
|
||||
// Create a separate render pass for the offscreen rendering as it may differ from the one used for scene rendering
|
||||
|
||||
std::array<VkAttachmentDescription, 2> attachment_descriptions = {};
|
||||
// Color attachment
|
||||
attachment_descriptions[0].format = FB_COLOR_FORMAT;
|
||||
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;
|
||||
// Depth attachment
|
||||
attachment_descriptions[1].format = framebuffer_depth_format;
|
||||
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_DONT_CARE;
|
||||
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;
|
||||
|
||||
VkAttachmentReference color_reference = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference depth_reference = {1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
|
||||
VkSubpassDescription subpass_description = {};
|
||||
subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass_description.colorAttachmentCount = 1;
|
||||
subpass_description.pColorAttachments = &color_reference;
|
||||
subpass_description.pDepthStencilAttachment = &depth_reference;
|
||||
|
||||
// Use subpass dependencies for layout transitions
|
||||
std::array<VkSubpassDependency, 2> dependencies;
|
||||
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
// Create the actual renderpass
|
||||
VkRenderPassCreateInfo render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_create_info.attachmentCount = static_cast<uint32_t>(attachment_descriptions.size());
|
||||
render_pass_create_info.pAttachments = attachment_descriptions.data();
|
||||
render_pass_create_info.subpassCount = 1;
|
||||
render_pass_create_info.pSubpasses = &subpass_description;
|
||||
render_pass_create_info.dependencyCount = static_cast<uint32_t>(dependencies.size());
|
||||
render_pass_create_info.pDependencies = dependencies.data();
|
||||
|
||||
VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &offscreen_pass.render_pass));
|
||||
|
||||
VkImageView attachments[2];
|
||||
attachments[0] = offscreen_pass.color.view;
|
||||
attachments[1] = offscreen_pass.depth.view;
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = vkb::initializers::framebuffer_create_info();
|
||||
framebuffer_create_info.renderPass = offscreen_pass.render_pass;
|
||||
framebuffer_create_info.attachmentCount = 2;
|
||||
framebuffer_create_info.pAttachments = attachments;
|
||||
framebuffer_create_info.width = offscreen_pass.width;
|
||||
framebuffer_create_info.height = offscreen_pass.height;
|
||||
framebuffer_create_info.layers = 1;
|
||||
|
||||
VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, &offscreen_pass.framebuffer));
|
||||
|
||||
// Fill a descriptor for later use in a descriptor set
|
||||
offscreen_pass.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
offscreen_pass.descriptor.imageView = offscreen_pass.color.view;
|
||||
offscreen_pass.descriptor.sampler = offscreen_pass.sampler;
|
||||
}
|
||||
|
||||
void ConservativeRasterization::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
// First render pass: Render a low res triangle to an offscreen framebuffer to use for visualization in second pass
|
||||
{
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.05f, 0.05f, 0.05f, 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 = offscreen_pass.render_pass;
|
||||
render_pass_begin_info.framebuffer = offscreen_pass.framebuffer;
|
||||
render_pass_begin_info.renderArea.extent.width = offscreen_pass.width;
|
||||
render_pass_begin_info.renderArea.extent.height = offscreen_pass.height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(offscreen_pass.width), static_cast<float>(offscreen_pass.height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(offscreen_pass.width, offscreen_pass.height, 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.scene, 0, 1, &descriptor_sets.scene, 0, nullptr);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, conservative_raster_enabled ? pipelines.triangle_conservative_raster : pipelines.triangle);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, triangle.vertices->get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], triangle.indices->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], triangle.index_count, 1, 0, 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
|
||||
|
||||
// Second render pass: Render scene with conservative rasterization
|
||||
{
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.05f, 0.05f, 0.05f, 0.25f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
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.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;
|
||||
|
||||
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);
|
||||
|
||||
// Low-res triangle from offscreen framebuffer
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.fullscreen);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.fullscreen, 0, 1, &descriptor_sets.fullscreen, 0, nullptr);
|
||||
vkCmdDraw(draw_cmd_buffers[i], 3, 1, 0, 0);
|
||||
|
||||
// Overlay actual triangle
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, triangle.vertices->get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], triangle.indices->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.triangle_overlay);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.scene, 0, 1, &descriptor_sets.scene, 0, nullptr);
|
||||
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 ConservativeRasterization::load_assets()
|
||||
{
|
||||
// Create a single triangle
|
||||
struct Vertex
|
||||
{
|
||||
float position[3];
|
||||
float color[3];
|
||||
};
|
||||
|
||||
std::vector<Vertex> vertex_buffer = {
|
||||
{{1.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
|
||||
{{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}},
|
||||
{{0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}};
|
||||
uint32_t vertex_buffer_size = static_cast<uint32_t>(vertex_buffer.size()) * sizeof(Vertex);
|
||||
std::vector<uint32_t> index_buffer = {0, 1, 2};
|
||||
triangle.index_count = static_cast<uint32_t>(index_buffer.size());
|
||||
uint32_t index_buffer_size = triangle.index_count * sizeof(uint32_t);
|
||||
|
||||
// Host visible source buffers (staging)
|
||||
vkb::core::BufferC vertex_staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), vertex_buffer);
|
||||
vkb::core::BufferC index_staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), index_buffer);
|
||||
|
||||
// Device local destination buffers
|
||||
triangle.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);
|
||||
|
||||
triangle.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 host to device
|
||||
get_device().copy_buffer(vertex_staging_buffer, *triangle.vertices, queue);
|
||||
get_device().copy_buffer(index_staging_buffer, *triangle.indices, queue);
|
||||
}
|
||||
|
||||
void ConservativeRasterization::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, 2)};
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_info =
|
||||
vkb::initializers::descriptor_pool_create_info(pool_sizes, 2);
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void ConservativeRasterization::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings;
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout;
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info;
|
||||
|
||||
// Scene rendering
|
||||
set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), // Binding 0: Vertex shader uniform buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), // Binding 1: Fragment shader image sampler
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2) // Binding 2: Fragment shader uniform buffer
|
||||
};
|
||||
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.scene));
|
||||
pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.scene, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.scene));
|
||||
|
||||
// Fullscreen pass
|
||||
set_layout_bindings = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), // Binding 0: Vertex shader uniform buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1) // Binding 1: Fragment shader image sampler
|
||||
};
|
||||
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.fullscreen));
|
||||
pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layouts.fullscreen, 1);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.fullscreen));
|
||||
}
|
||||
|
||||
void ConservativeRasterization::setup_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo descriptor_set_allocate_info;
|
||||
|
||||
// Scene rendering
|
||||
descriptor_set_allocate_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layouts.scene, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_allocate_info, &descriptor_sets.scene));
|
||||
VkDescriptorBufferInfo scene_buffer_descriptor = create_descriptor(*uniform_buffers.scene);
|
||||
std::vector<VkWriteDescriptorSet> offscreen_write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &scene_buffer_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(offscreen_write_descriptor_sets.size()), offscreen_write_descriptor_sets.data(), 0, nullptr);
|
||||
|
||||
// Fullscreen pass
|
||||
descriptor_set_allocate_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layouts.fullscreen, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_allocate_info, &descriptor_sets.fullscreen));
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.fullscreen, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &offscreen_pass.descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void ConservativeRasterization::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_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);
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(pipeline_layouts.fullscreen, render_pass, 0);
|
||||
|
||||
// Conservative rasterization setup
|
||||
|
||||
// Get device properties for conservative rasterization
|
||||
// Requires VK_KHR_get_physical_device_properties2 and manual function pointer creation
|
||||
PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR =
|
||||
reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2KHR>(vkGetInstanceProcAddr(get_instance().get_handle(), "vkGetPhysicalDeviceProperties2KHR"));
|
||||
assert(vkGetPhysicalDeviceProperties2KHR);
|
||||
VkPhysicalDeviceProperties2KHR device_properties{};
|
||||
conservative_raster_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
|
||||
device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
|
||||
device_properties.pNext = &conservative_raster_properties;
|
||||
vkGetPhysicalDeviceProperties2KHR(get_device().get_gpu().get_handle(), &device_properties);
|
||||
|
||||
// Vertex bindings and attributes
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
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: 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();
|
||||
|
||||
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();
|
||||
|
||||
// Full screen pass
|
||||
shader_stages[0] = load_shader("conservative_rasterization", "fullscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("conservative_rasterization", "fullscreen.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
// Empty vertex input state (full screen triangle generated in vertex shader)
|
||||
VkPipelineVertexInputStateCreateInfo empty_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
pipeline_create_info.pVertexInputState = &empty_input_state;
|
||||
pipeline_create_info.layout = pipeline_layouts.fullscreen;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.fullscreen));
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.layout = pipeline_layouts.scene;
|
||||
|
||||
// Original triangle outline
|
||||
// TODO(tomatkinson): Check support for lines
|
||||
rasterization_state.lineWidth = 2.0f;
|
||||
rasterization_state.polygonMode = VK_POLYGON_MODE_LINE;
|
||||
shader_stages[0] = load_shader("conservative_rasterization", "triangle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("conservative_rasterization", "triangleoverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.triangle_overlay));
|
||||
|
||||
pipeline_create_info.renderPass = offscreen_pass.render_pass;
|
||||
|
||||
// Triangle rendering
|
||||
rasterization_state.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
shader_stages[0] = load_shader("conservative_rasterization", "triangle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("conservative_rasterization", "triangle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Basic pipeline
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.triangle));
|
||||
|
||||
// Pipeline with conservative rasterization enabled
|
||||
VkPipelineRasterizationConservativeStateCreateInfoEXT conservative_rasterization_state{};
|
||||
conservative_rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT;
|
||||
conservative_rasterization_state.conservativeRasterizationMode = VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT;
|
||||
conservative_rasterization_state.extraPrimitiveOverestimationSize = conservative_raster_properties.maxExtraPrimitiveOverestimationSize;
|
||||
|
||||
// Conservative rasterization state has to be chained into the pipeline rasterization state create info structure
|
||||
rasterization_state.pNext = &conservative_rasterization_state;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.triangle_conservative_raster));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void ConservativeRasterization::prepare_uniform_buffers()
|
||||
{
|
||||
uniform_buffers.scene = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_scene),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers_scene();
|
||||
}
|
||||
|
||||
void ConservativeRasterization::update_uniform_buffers_scene()
|
||||
{
|
||||
ubo_scene.projection = camera.matrices.perspective;
|
||||
ubo_scene.model = camera.matrices.view;
|
||||
uniform_buffers.scene->convert_and_update(ubo_scene);
|
||||
}
|
||||
|
||||
void ConservativeRasterization::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 ConservativeRasterization::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), 512.0f, 0.1f);
|
||||
camera.set_rotation(glm::vec3(0.0f));
|
||||
camera.set_translation(glm::vec3(0.0f, 0.0f, -2.0f));
|
||||
|
||||
load_assets();
|
||||
prepare_offscreen();
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_set();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConservativeRasterization::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers_scene();
|
||||
}
|
||||
}
|
||||
|
||||
void ConservativeRasterization::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.checkbox("Conservative rasterization", &conservative_raster_enabled))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
if (drawer.header("Device properties"))
|
||||
{
|
||||
drawer.text("maxExtraPrimitiveOverestimationSize: %f", conservative_raster_properties.maxExtraPrimitiveOverestimationSize);
|
||||
drawer.text("extraPrimitiveOverestimationSizeGranularity: %f", conservative_raster_properties.extraPrimitiveOverestimationSizeGranularity);
|
||||
drawer.text("primitiveUnderestimation: %s", conservative_raster_properties.primitiveUnderestimation ? "yes" : "no");
|
||||
drawer.text("conservativePointAndLineRasterization: %s", conservative_raster_properties.conservativePointAndLineRasterization ? "yes" : "no");
|
||||
drawer.text("degenerateTrianglesRasterized: %s", conservative_raster_properties.degenerateTrianglesRasterized ? "yes" : "no");
|
||||
drawer.text("degenerateLinesRasterized: %s", conservative_raster_properties.degenerateLinesRasterized ? "yes" : "no");
|
||||
drawer.text("fullyCoveredFragmentShaderInputVariable: %s", conservative_raster_properties.fullyCoveredFragmentShaderInputVariable ? "yes" : "no");
|
||||
drawer.text("conservativeRasterizationPostDepthCoverage: %s", conservative_raster_properties.conservativeRasterizationPostDepthCoverage ? "yes" : "no");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_conservative_rasterization()
|
||||
{
|
||||
return std::make_unique<ConservativeRasterization>();
|
||||
}
|
||||
@@ -1,128 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Conservative rasterization
|
||||
*
|
||||
* Note: Requires a device that supports the VK_EXT_conservative_rasterization extension
|
||||
*
|
||||
* Uses an offscreen buffer with lower resolution to demonstrate the effect of conservative rasterization
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
#define FB_COLOR_FORMAT VK_FORMAT_R8G8B8A8_UNORM
|
||||
#define ZOOM_FACTOR 16
|
||||
|
||||
class ConservativeRasterization : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
// Fetch and store conservative rasterization state props for display purposes
|
||||
VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_properties{};
|
||||
|
||||
bool conservative_raster_enabled = true;
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
float position[3];
|
||||
float color[3];
|
||||
};
|
||||
|
||||
struct Triangle
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> vertices;
|
||||
std::unique_ptr<vkb::core::BufferC> indices;
|
||||
uint32_t index_count;
|
||||
} triangle;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer;
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> scene;
|
||||
} uniform_buffers;
|
||||
|
||||
struct UboScene
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
} ubo_scene;
|
||||
|
||||
struct PipelineLayouts
|
||||
{
|
||||
VkPipelineLayout scene;
|
||||
VkPipelineLayout fullscreen;
|
||||
} pipeline_layouts;
|
||||
|
||||
struct Pipelines
|
||||
{
|
||||
VkPipeline triangle;
|
||||
VkPipeline triangle_conservative_raster;
|
||||
VkPipeline triangle_overlay;
|
||||
VkPipeline fullscreen;
|
||||
} pipelines;
|
||||
|
||||
struct DescriptorSetLayouts
|
||||
{
|
||||
VkDescriptorSetLayout scene;
|
||||
VkDescriptorSetLayout fullscreen;
|
||||
} descriptor_set_layouts;
|
||||
|
||||
struct DescriptorSets
|
||||
{
|
||||
VkDescriptorSet scene;
|
||||
VkDescriptorSet fullscreen;
|
||||
} descriptor_sets;
|
||||
|
||||
// Framebuffer for offscreen rendering
|
||||
struct FrameBufferAttachment
|
||||
{
|
||||
VkImage image;
|
||||
VkDeviceMemory mem;
|
||||
VkImageView view;
|
||||
};
|
||||
struct OffscreenPass
|
||||
{
|
||||
int32_t width, height;
|
||||
VkFramebuffer framebuffer;
|
||||
FrameBufferAttachment color, depth;
|
||||
VkRenderPass render_pass;
|
||||
VkSampler sampler;
|
||||
VkDescriptorImageInfo descriptor;
|
||||
} offscreen_pass;
|
||||
|
||||
ConservativeRasterization();
|
||||
~ConservativeRasterization();
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void build_command_buffers() override;
|
||||
void prepare_offscreen();
|
||||
void load_assets();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers_scene();
|
||||
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;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_conservative_rasterization();
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2020-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(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Sascha Willems"
|
||||
NAME "VK_EXT_Debug_Utils"
|
||||
DESCRIPTION "Demonstrates the use of the debug utils extension for tagging and labeling Vulkan objects for debugging applications like RenderDoc"
|
||||
SHADER_FILES_GLSL
|
||||
"debug_utils/glsl/composition.vert"
|
||||
"debug_utils/glsl/composition.frag"
|
||||
"debug_utils/glsl/bloom.vert"
|
||||
"debug_utils/glsl/bloom.frag"
|
||||
"debug_utils/glsl/gbuffer.vert"
|
||||
"debug_utils/glsl/gbuffer.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"debug_utils/hlsl/composition.vert.hlsl"
|
||||
"debug_utils/hlsl/composition.frag.hlsl"
|
||||
"debug_utils/hlsl/bloom.vert.hlsl"
|
||||
"debug_utils/hlsl/bloom.frag.hlsl"
|
||||
"debug_utils/hlsl/gbuffer.vert.hlsl"
|
||||
"debug_utils/hlsl/gbuffer.frag.hlsl")
|
||||
@@ -1,294 +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.
|
||||
-
|
||||
////
|
||||
= Vulkan Debug Utilities Extension
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/debug_utils[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
This tutorial, along with the accompanying example code, demonstrates the use of the https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_EXT_debug_utils[VK_EXT_debug_utils] extension to setup a validation layer messenger callback and pass additional debugging information to debuggers like https://renderdoc.org/[RenderDoc].
|
||||
|
||||
`VK_EXT_debug_utils` has been introduced based on feedback for the initial Vulkan debugging extensions `VK_EXT_debug_report` and `VK_EXT_debug_marker`, combining these into a single instance extensions with some added functionality.
|
||||
|
||||
== Setup
|
||||
|
||||
NOTE: Enabling the extension is done inside the framework, see the `Instance` class in link:../../../framework/core/instance.cpp[instance.cpp] for details.
|
||||
|
||||
Enabling the functionality for the debug utilities is done by adding the extension to the list of extensions to enable at instance level.
|
||||
As with all extensions, this is optional and you should check if the extension is present before enabling it.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
uint32_t instance_extension_count;
|
||||
VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr));
|
||||
|
||||
std::vector<VkExtensionProperties> available_instance_extensions(instance_extension_count);
|
||||
VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, available_instance_extensions.data()));
|
||||
|
||||
bool debug_utils = false;
|
||||
for (auto &available_extension : available_instance_extensions)
|
||||
{
|
||||
if (strcmp(available_extension.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0)
|
||||
{
|
||||
debug_utils = true;
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Validation
|
||||
|
||||
NOTE: Validation setup is done inside the framework, see the `Instance` class in `instance.cpp` for details.
|
||||
|
||||
After creating your instance with the `VK_EXT_debug_utils` extension enabled, you'll be able to use the debug functions that it provides.
|
||||
|
||||
=== Setting Up
|
||||
|
||||
NOTE: Depending on the implementation and loader you're using you may need to manually get the function pointers for these via `vkGetInstanceProcAddr` before you can use these.
|
||||
|
||||
`vkCreateDebugUtilsMessengerEXT` is used for setting up the debug messenger callback that is triggered by the validation layers:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkDebugUtilsMessengerCreateInfoEXT debug_utils_create_info = {VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
|
||||
|
||||
debug_utils_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
|
||||
debug_utils_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
|
||||
debug_utils_create_info.pfnUserCallback = debug_utils_messenger_callback;
|
||||
----
|
||||
|
||||
We then pass this to the `pNext` member of our instance creation structure, enabling validation for instance creation and destruction:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkInstanceCreateInfo instance_create_info = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
...
|
||||
instance_create_info.pNext = &debug_utils_create_info;
|
||||
----
|
||||
|
||||
After instance creation we can create the actual debug utils messenger callback that is invoked by the enabled validation layers:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
result = vkCreateDebugUtilsMessengerEXT(handle, &debug_utils_create_info, nullptr, &debug_utils_messenger);
|
||||
----
|
||||
|
||||
The `messageSeverity` member of the `VkDebugUtilsMessengerCreateInfoEXT` struct determines the kind of validation layer messages that are passed to the user callback:
|
||||
|
||||
* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: Verbose messages, including diagnostic messages from loaders, layers and drivers.
|
||||
* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: Informational message like resource details.
|
||||
* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: Warnings that may hint at application side bugs and undefined behavior.
|
||||
* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: Errors caused by application side violation of valid usage as specified by the spec.
|
||||
|
||||
In your typically validation setup you'd use `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT` and `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT` to catch bugs and errors in your application.
|
||||
|
||||
A basic debug messenger callback may then look like this:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL debug_utils_messenger_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT message_type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
|
||||
void *user_data)
|
||||
{
|
||||
if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
|
||||
{
|
||||
LOGW("{} - {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage)
|
||||
}
|
||||
else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
|
||||
{
|
||||
LOGE("{} - {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage);
|
||||
}
|
||||
return VK_FALSE;
|
||||
}
|
||||
----
|
||||
|
||||
With the above setup, Vulkan spec violations of your application will now be reported to the standard output like this:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
Error: 0 - UNASSIGNED-CoreValidation-DrawState-InvalidRenderArea: Cannot execute a render pass with
|
||||
renderArea not within the bound of the framebuffer. RenderArea: x 0, y 0, width 1281, height 721.
|
||||
Framebuffer: width 1280, height 720.
|
||||
----
|
||||
|
||||
== Adding information for debugging tools
|
||||
|
||||
In this chapter we will take a look at how to pass additional information from our application to debugging tools.
|
||||
|
||||
The debug utilities adds several functions to add debugging information to your sample application's command buffers, queues, and all other Vulkan objects.
|
||||
|
||||
=== Inserting labels
|
||||
|
||||
The extension allows you to add colored labels to `command buffers` and `queues`, that work as markers inside the Vulkan Event chain.
|
||||
|
||||
This is esp.
|
||||
helpful in more complex applications with multiple command buffers in flight across different queues.
|
||||
|
||||
For comparison here is an unmarked event view from RenderDoc versus one viewed using debugging labels:
|
||||
|
||||
image::./images/renderdoc_no_labels.jpg[]
|
||||
image::./images/renderdoc_with_labels.jpg[]
|
||||
|
||||
Color choice aside, it's clear that the event tree with added debug labels is much easier to navigate.
|
||||
|
||||
There are two distinct concepts for adding labels to either a command buffer or a queue:
|
||||
|
||||
* Encapsulating labels: These are started with a `begin` command and closed with an `end` command.
|
||||
They encapsulate all submitted commands in between and can be *arbitrarily nested*.
|
||||
* Inserting labels: Those are inserted at the exact point where the commands are submitted.
|
||||
Think of these as *simple markers*.
|
||||
|
||||
The new functions to add such labels are:
|
||||
|
||||
* For command buffers
|
||||
** `vkCmdBeginDebugUtilsLabelEXT`
|
||||
** `vkCmdEndDebugUtilsLabelEXT`
|
||||
** `vkCmdInsertDebugUtilsLabelEXT`
|
||||
* For queues
|
||||
** `vkQueueBeginDebugUtilsLabelEXT`
|
||||
** `vkQueueEndDebugUtilsLabelEXT`
|
||||
** `vkQueueInsertDebugUtilsLabelEXT`
|
||||
|
||||
Once you start a new label via `vkCmd/QueueBeginDebugUtilsLabelEXT` all commands submitted to that command buffer or queue are encapsulated by that label until you end it via `vkCmd/QueueEndDebugUtilsLabelEXT` whereas a call to `vkCmd/QueueInsertDebugUtilsLabelEXT` simply inserts a marker at the current command buffer or queue command position;
|
||||
|
||||
For convenience, the sample wraps those functions into dedicated functions.
|
||||
|
||||
In this (simplified) code from our sample application we use encapsulating and nested labels to tag the whole separable bloom filter passes for the debugger, and also insert a marker before submitting the draw command for the bloom pass' full-screen quad:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
cmd_begin_label(draw_cmd_buffers[i], "Separable bloom filter", {0.5f, 0.76f, 0.34f, 1.0f});
|
||||
|
||||
cmd_begin_label(draw_cmd_buffers[i], "Vertical bloom pass", {0.4f, 0.61f, 0.27f, 1.0f});
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], ...);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], ...);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], ...);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], ...);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], ...);
|
||||
vkCmdDraw(draw_cmd_buffers[i], ...);
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
cmd_end_label(draw_cmd_buffers[i]);
|
||||
|
||||
cmd_begin_label(draw_cmd_buffers[i], "Horizontal bloom pass and composition", {0.4f, 0.61f, 0.27f, 1.0f});
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], ...);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], ...);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], ...);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], ...);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], ...);
|
||||
vkCmdDraw(draw_cmd_buffers[i], ...);
|
||||
cmd_insert_label(draw_cmd_buffers[i], "Bloom full screen quad", {1.0f, 1.0f, 1.0f, 1.0f});
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], ...);
|
||||
vkCmdDraw(draw_cmd_buffers[i], ...);
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
cmd_end_label(draw_cmd_buffers[i]);
|
||||
|
||||
cmd_end_label(draw_cmd_buffers[i]);
|
||||
----
|
||||
|
||||
Running this in RenderDoc will display the event browser with our colored debug labels:
|
||||
|
||||
image::./images/renderdoc_nested_cmd.jpg[]
|
||||
|
||||
=== Vulkan object naming and tagging
|
||||
|
||||
The other important functionality of this extension is the possibility to name (and tag) all Vulkan objects in your application.
|
||||
This makes object identification of the resources (inside the debugger) a lot easier and will help you understand your applications structure and aid you in finding bugs and problematic behavior.
|
||||
|
||||
Imagine you need to debug a problem with a shader module not properly working or seemingly the wrong shader used by a pipeline.
|
||||
Without adding names to your Vulkan objects, all your resources will have similar names auto-generated by the debugging application.
|
||||
In the case of RenderDoc it's the object's type with a continuous number:
|
||||
|
||||
image:./images/renderdoc_resource_inspector_no_names.jpg[]
|
||||
|
||||
Finding "Shader Module 257" or any of the "Graphics Pipeline 259/260" in your code will prove tricky to impossible.
|
||||
|
||||
But if you're using the new extension to add meaningful names to your Vulkan objects, connecting the resources and finding them in your application becomes straightforward:
|
||||
|
||||
image:./images/renderdoc_resource_inspector_names.jpg[]
|
||||
|
||||
Now you can clearly see what shader this actually is and what pipelines are using it.
|
||||
As an added bonus you also get named resources in the resource list, so searching for a specific resources is now also possible.
|
||||
|
||||
This is also evident in the pipeline state, where you can now e.g.
|
||||
see what pipeline, shader and buffer are bound at what stage:
|
||||
|
||||
image:./images/renderdoc_pipeline_state_names.jpg[]
|
||||
|
||||
This makes it very easy to see if the correct resources are used at that pipeline stage.
|
||||
(1) shows the pipeline and shader used at the vertex shader stage, and (2) lists the uniform buffer bound to set 0.
|
||||
|
||||
The new functions to set names and tags for Vulkan objects are:
|
||||
|
||||
* `vkSetDebugUtilsObjectNameEXT`
|
||||
* `vkSetDebugUtilsObjectTagEXT`
|
||||
|
||||
`vkSetDebugUtilsObjectNameEXT` lets you add a name to any Vulkan object via it's handle:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkDebugUtilsObjectNameInfoEXT name_info = {VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
|
||||
name_info.objectType = VK_OBJECT_TYPE_BUFFER;
|
||||
name_info.objectHandle = (uint64_t) uniform_buffers.matrices.handle;
|
||||
name_info.pObjectName = "Some uniform buffer";
|
||||
vkSetDebugUtilsObjectNameEXT(device, &name_info);
|
||||
----
|
||||
|
||||
`vkSetDebugUtilsObjectTagEXT` lets you add arbitrary data to any Vulkan object via it's handle.
|
||||
That data may be displayed by a debugging app or your own app inside the debug messenger callback:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkDebugUtilsObjectTagInfoEXT tag_info = {VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT};
|
||||
tag_info.objectType = VK_OBJECT_TYPE_SHADER_MODULE;
|
||||
tag_info.objectHandle = (uint64_t) shader_stage.module;
|
||||
tag_info.tagName = 0;
|
||||
tag_info.tagSize = shader_source_glsl.data_size;
|
||||
tag_info.pTag = &shader_source_glsl.data;
|
||||
vkSetDebugUtilsObjectTagEXT(device, &info);
|
||||
----
|
||||
|
||||
For convenience, the sample wraps those functions into dedicated functions.
|
||||
|
||||
== Running the sample with a Vulkan debugger
|
||||
|
||||
To see this in action, you need to run the sample application from inside a Vulkan debugger.
|
||||
If you're unfamiliar with this, this is a sample setup for running our sample application from RenderDoc.
|
||||
The paths depend on where you have downloaded the source from this repository and the platform for which you are compiling:
|
||||
|
||||
image:./images/renderdoc_launch_settings.jpg[]
|
||||
|
||||
(1) is the binary you want to start, which depends on the platform you have compiled the samples for.
|
||||
(2) refers to the path that's passed as the working directory to the binary, which must be the root path of the repository so the asset's can be properly loaded.
|
||||
(3) tells the binary which sample to run.
|
||||
After setting these up press (4) to start the application from within RenderDoc.
|
||||
|
||||
Once the sample application is running, press F12 do capture the current frame, close the application and then select the capture in RenderDoc.
|
||||
|
||||
Once loaded you should be able to see a trace of a whole frame from that sample application along with labels and named Vulkan objects:
|
||||
|
||||
image:./images/renderdoc_final.jpg[]
|
||||
@@ -1,158 +0,0 @@
|
||||
/* Copyright (c) 2020-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Debug Utils labeling
|
||||
* Note that you need to run this example inside a debugging tool like RenderDoc to see those labels
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class DebugUtils : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
bool bloom = true;
|
||||
bool display_skysphere = true;
|
||||
bool debug_utils_supported = false;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture skysphere;
|
||||
} textures;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::sg::SubMesh> skysphere;
|
||||
std::unique_ptr<vkb::sg::SubMesh> scene;
|
||||
} models;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> matrices;
|
||||
} uniform_buffers;
|
||||
|
||||
struct UBOVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skysphere_modelview;
|
||||
float modelscale = 0.05f;
|
||||
} ubo_vs;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipeline skysphere;
|
||||
VkPipeline sphere;
|
||||
VkPipeline composition;
|
||||
VkPipeline bloom[2];
|
||||
} pipelines;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout models;
|
||||
VkPipelineLayout composition;
|
||||
VkPipelineLayout bloom_filter;
|
||||
} pipeline_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet skysphere;
|
||||
VkDescriptorSet sphere;
|
||||
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;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::vec4 offset;
|
||||
glm::vec4 color;
|
||||
uint32_t object_type;
|
||||
} push_const_block;
|
||||
|
||||
DebugUtils();
|
||||
~DebugUtils();
|
||||
void debug_check_extension();
|
||||
void cmd_begin_label(VkCommandBuffer command_buffer, const char *label_name, std::vector<float> color);
|
||||
void cmd_insert_label(VkCommandBuffer command_buffer, const char *label_name, std::vector<float> color);
|
||||
void cmd_end_label(VkCommandBuffer command_buffer);
|
||||
void queue_begin_label(VkQueue queue, const char *label_name, std::vector<float> color);
|
||||
void queue_insert_label(VkQueue queue, const char *label_name, std::vector<float> color);
|
||||
void queue_end_label(VkQueue queue);
|
||||
void set_object_name(VkObjectType object_type, uint64_t object_handle, const char *object_name);
|
||||
VkPipelineShaderStageCreateInfo debug_load_shader(const std::string &file, VkShaderStageFlagBits stage);
|
||||
void debug_name_objects();
|
||||
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 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_debug_utils();
|
||||
|
Before Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 93 KiB |
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2023-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 "Descriptor buffers"
|
||||
DESCRIPTION "Using VK_KHR_descriptor_buffer"
|
||||
SHADER_FILES_GLSL
|
||||
"descriptor_buffer_basic/glsl/cube.vert"
|
||||
"descriptor_buffer_basic/glsl/cube.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"descriptor_buffer_basic/hlsl/cube.vert.hlsl"
|
||||
"descriptor_buffer_basic/hlsl/cube.frag.hlsl")
|
||||
@@ -1,257 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023-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.
|
||||
-
|
||||
////
|
||||
= Descriptor 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/extensions/descriptor_buffer_basic[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
Binding and managing descriptors in Vulkan can become pretty complex, both for the application and the driver.
|
||||
With the https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_ext_descriptor_buffer[`VK_EXT_descriptor_buffer`] extension, this interface is simplified and maps more directly to how hardware sees descriptors.
|
||||
It also simplifies the programming model, as you no longer have to create descriptor pool upfront.
|
||||
|
||||
This sample shows how to use that extension by rendering multiple objects with different uniform buffers and images using the new interface of creating and binding descriptors.
|
||||
|
||||
== Deprecated descriptor bindings
|
||||
|
||||
Creating and binding descriptors in Vulkan requires different steps and function calls.
|
||||
|
||||
After all, descriptors are just memory, and something like a `VkDescriptorPool` was an abstract concept that didn't actually map to hardware.
|
||||
On most implementations `vkCreateDescriptorPool` did nothing more than just a memory allocation.
|
||||
Same for `vkAllocateDescriptorSets`, which in the end is also just some sort of memory allocation, while `vkUpdateDescriptorSets` did some memory copies for the descriptors to that buffer.
|
||||
|
||||
With the streamlined descriptor setup from `VK_EXT_descriptor_buffer`, the api now maps more closely to this and removes the need for the following functions:
|
||||
|
||||
* vkCreateDescriptorPool
|
||||
* vkAllocateDescriptorSets
|
||||
* vkUpdateDescriptorSets
|
||||
* vkCmdBindDescriptorSets
|
||||
|
||||
Other concepts of Vulkan's descriptor logic like descriptor set layouts and pipeline layouts are still used and not deprecated.
|
||||
|
||||
== The new way
|
||||
|
||||
The `VK_EXT_descriptor_buffer` replaces all of this with *resource descriptor buffer*.
|
||||
These store descriptors in a way that the GPU can directly read them from such a buffer.
|
||||
The application simply puts them into those buffers.
|
||||
That buffer is then bound at command buffer recording time similar to other buffer types.
|
||||
|
||||
To make the following code easier to understand, let's take a look at the interfaces of our shaders:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
// Vertex shader
|
||||
layout (set = 0, binding = 0) uniform UBOScene {
|
||||
mat4 projection;
|
||||
mat4 view;
|
||||
} uboCamera;
|
||||
|
||||
layout (set = 1, binding = 0) uniform UBOModel {
|
||||
mat4 local;
|
||||
} uboModel;
|
||||
|
||||
// Fragment shader
|
||||
layout (set = 2, binding = 0) uniform sampler2D samplerColorMap;
|
||||
----
|
||||
|
||||
We use three descriptor sets, each with one binding.
|
||||
|
||||
=== Creating the descriptor buffers
|
||||
|
||||
Descriptors are now stored in and accessed from memory, so instead of having to use the old approach of creating dedicated Vulkan objects, we create buffers that will store descriptors instead.
|
||||
|
||||
The extension introduces two different types of descriptors: Resource descriptors for buffers (uniform buffers, shader storage buffers) and sampler/combined image sampler descriptors.
|
||||
In this sample we'll be using both types, so we create two different buffers.
|
||||
|
||||
As is usual in Vulkan, implementations have different size and alignment requirements. Alignment requirements for setting offsets in the descriptor buffer with `vkCmdSetDescriptorBufferOffsetsEXT` are defined by `VkPhysicalDeviceDescriptorBufferPropertiesEXT::descriptorBufferOffsetAlignment`. At the start of the example we fetch that information into `descriptor_buffer_properties`.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkPhysicalDeviceProperties2KHR device_properties{};
|
||||
descriptor_buffer_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT;
|
||||
device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
|
||||
device_properties.pNext = &descriptor_buffer_properties;
|
||||
vkGetPhysicalDeviceProperties2KHR(get_device().get_gpu().get_handle(), &device_properties);
|
||||
----
|
||||
|
||||
So to calculate the actual buffer sizes required to store the descriptors, we need to get the sizes of set layouts and also align them to `VkPhysicalDeviceDescriptorBufferPropertiesEXT::descriptorBufferOffsetAlignment`. We also need to fetch offsets of the descriptor bindings of a set layout as by the Vulkan specs size of the set layout is at least a sum of sizes of descriptor bindings of this layout but it can be higher than that and there are no guarantees about the layout of descriptor bindings in a descriptor set layout, meaning that first descriptor binding of a set can start exactly at the beginning of a set layout memory or it can start with non 0 offset if driver implementation puts some metadata there.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Get set layout descriptor sizes.
|
||||
vkGetDescriptorSetLayoutSizeEXT(get_device().get_handle(), uniform_binding_descriptor.layout, &uniform_binding_descriptor.size);
|
||||
vkGetDescriptorSetLayoutSizeEXT(get_device().get_handle(), image_binding_descriptor.layout, &image_binding_descriptor.size);
|
||||
|
||||
// Adjust set layout sizes to satisfy alignment requirements.
|
||||
uniform_binding_descriptor.size = aligned_size(uniform_binding_descriptor.size, descriptor_buffer_properties.descriptorBufferOffsetAlignment);
|
||||
image_binding_descriptor.size = aligned_size(image_binding_descriptor.size, descriptor_buffer_properties.descriptorBufferOffsetAlignment);
|
||||
|
||||
// Get descriptor bindings offsets as descriptors are placed inside set layout by those offsets.
|
||||
vkGetDescriptorSetLayoutBindingOffsetEXT(get_device().get_handle(), uniform_binding_descriptor.layout, 0u, &uniform_binding_descriptor.offset);
|
||||
vkGetDescriptorSetLayoutBindingOffsetEXT(get_device().get_handle(), image_binding_descriptor.layout, 0u, &image_binding_descriptor.offset);
|
||||
----
|
||||
|
||||
Creating the resource descriptors for the uniform buffers using the `VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT` usage flag:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
uniform_binding_descriptor.buffer = std::make_unique<vkb::core::Buffer>(get_device(),
|
||||
(static_cast<uint32_t>(cubes.size()) + 1) * uniform_binding_descriptor.size,
|
||||
VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
----
|
||||
|
||||
Creating the combined image sampler descriptors by additionally adding the `VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT` usage flag:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
image_binding_descriptor.buffer = std::make_unique<vkb::core::Buffer>(get_device(),
|
||||
static_cast<uint32_t>(cubes.size()) * image_binding_descriptor.size,
|
||||
VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
----
|
||||
|
||||
=== Putting the descriptors into the buffers
|
||||
|
||||
After creating the appropriate buffers we now put the actual descriptors into those buffers, making them accessible to the GPU.
|
||||
This is done with the `vkGetDescriptorEXT` function.
|
||||
|
||||
The sample uses one global uniform buffer that stores the scene matrices, one uniform buffer per object displayed and one combined image sampler per object.
|
||||
|
||||
For resource descriptor buffers, we can simply put buffer device addresses into it.
|
||||
No need for descriptors, as the GPU only needs to know the address of the buffers to access them:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
char *uniform_descriptor_buf_ptr = (char *) resource_descriptor_buffer->get_data();
|
||||
|
||||
// Global matrices uniform buffer
|
||||
VkDescriptorAddressInfoEXT addr_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT};
|
||||
addr_info.address = uniform_buffers.scene->get_device_address();
|
||||
addr_info.range = uniform_buffers.scene->get_size();
|
||||
addr_info.format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
VkDescriptorGetInfoEXT buffer_descriptor_info{VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT};
|
||||
buffer_descriptor_info.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
buffer_descriptor_info.data.pUniformBuffer = &addr_info;
|
||||
vkGetDescriptorEXT(get_device().get_handle(), &buffer_descriptor_info, descriptor_buffer_properties.uniformBufferDescriptorSize, uniform_descriptor_buf_ptr);
|
||||
|
||||
// Per-cube uniform buffers
|
||||
// We use pointers to offset and align the data we put into the descriptor buffers
|
||||
for (size_t i = 0; i < cubes.size(); i++)
|
||||
{
|
||||
VkDescriptorAddressInfoEXT cube_addr_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT};
|
||||
cube_addr_info.address = cubes[i].uniform_buffer->get_device_address();
|
||||
cube_addr_info.range = cubes[i].uniform_buffer->get_size();
|
||||
cube_addr_info.format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
buffer_descriptor_info.data.pUniformBuffer = &cube_addr_info;
|
||||
vkGetDescriptorEXT(get_device().get_handle(), &buffer_descriptor_info, descriptor_buffer_properties.uniformBufferDescriptorSize, uniform_descriptor_buf_ptr + (i + 1) * uniform_binding_descriptor.size + uniform_binding_descriptor.offset);
|
||||
}
|
||||
----
|
||||
|
||||
For combined image samplers (or samplers alone) we can't use buffer device addresses as the implementation needs more information, so we have to put actual descriptors into the buffer instead:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// For combined images we need to put descriptors into the descriptor buffers
|
||||
// We use pointers to offset and align the data we put into the descriptor buffers
|
||||
char *image_descriptor_buf_ptr = (char *) image_binding_descriptor.buffer->get_data();
|
||||
for (size_t i = 0; i < cubes.size(); i++)
|
||||
{
|
||||
VkDescriptorImageInfo image_descriptor = create_descriptor(cubes[i].texture);
|
||||
|
||||
VkDescriptorGetInfoEXT image_descriptor_info{VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT};
|
||||
image_descriptor_info.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
image_descriptor_info.data.pCombinedImageSampler = &image_descriptor;
|
||||
vkGetDescriptorEXT(get_device().get_handle(), &image_descriptor_info, descriptor_buffer_properties.combinedImageSamplerDescriptorSize, image_descriptor_buf_ptr + i * image_binding_descriptor.size + image_binding_descriptor.offset);
|
||||
}
|
||||
----
|
||||
|
||||
=== Binding the buffers
|
||||
|
||||
As noted earlier, we no longer bind descriptor sets using `vkCmdBindDescriptorSets` but instead use `vkCmdBindDescriptorBuffersEXT` to bind the (resource) descriptor buffers and then use `vkCmdSetDescriptorBufferOffsetsEXT` to index into that buffer for the next draw:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// Descriptor buffer bindings
|
||||
// Binding 0 = uniform buffer
|
||||
VkDescriptorBufferBindingInfoEXT descriptor_buffer_binding_info[2]{};
|
||||
descriptor_buffer_binding_info[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT;
|
||||
descriptor_buffer_binding_info[0].address = resource_descriptor_buffer->get_device_address();
|
||||
descriptor_buffer_binding_info[0].usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
// Binding 1 = Image
|
||||
descriptor_buffer_binding_info[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT;
|
||||
descriptor_buffer_binding_info[1].address = image_descriptor_buffer->get_device_address();
|
||||
descriptor_buffer_binding_info[1].usage = VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
vkCmdBindDescriptorBuffersEXT(draw_cmd_buffers[i], 2, descriptor_buffer_binding_info);
|
||||
|
||||
uint32_t buffer_index_ubo = 0;
|
||||
uint32_t buffer_index_image = 1;
|
||||
|
||||
// Global Matrices (set 0)
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &buffer_index_ubo, &buffer_offset);
|
||||
|
||||
// Set and offset into descriptor for each model
|
||||
for (size_t j = 0; j < cubes.size(); j++)
|
||||
{
|
||||
// Uniform buffer (set 1)
|
||||
// Model ubos start at offset * (j + 1) (+1 as slot 0 is global matrices)
|
||||
buffer_offset = (j + 1) * uniform_binding_descriptor.size;
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 1, 1, &buffer_index_ubo, &buffer_offset);
|
||||
// Image (set 2)
|
||||
buffer_offset = j * image_binding_descriptor.size;
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 2, 1, &buffer_index_image, &buffer_offset);
|
||||
draw_model(models.cube, draw_cmd_buffers[i]);
|
||||
}
|
||||
----
|
||||
|
||||
In detail and in reference to our shader interface:
|
||||
|
||||
Earlier on, we did put the device address for the global matrices uniform buffer at the beginning to the resource descriptor buffer.
|
||||
So we set it to point at `buffer_offset = 0` for set 0:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &buffer_index_ubo, &buffer_offset);
|
||||
----
|
||||
|
||||
We then loop through all cubes displayed in the example and let the descriptor buffer point at the next device address using the alignment of the implementation for set 1:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 1, 1, &buffer_index_ubo, &buffer_offset);
|
||||
----
|
||||
|
||||
With an alignment of 16 (see `VkPhysicalDeviceDescriptorBufferPropertiesEXT`) the device address for the uniform buffer for the first cube would start at byte 16 in the resource descriptor buffer, the device address for the second cube's uniform buffer would start at byte 32.
|
||||
|
||||
The descriptor buffer containing the descriptors for our combined image samples is bound to set 2:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 2, 1, &buffer_index_image, &buffer_offset);
|
||||
----
|
||||
|
||||
== What about the shaders?
|
||||
|
||||
With descriptor set and pipeline layouts, Vulkan decouples the shader interfaces from the application.
|
||||
And since we don't change these but only the way how we provide descriptors to the GPU, *no changes to the shaders are required*.
|
||||
@@ -1,486 +0,0 @@
|
||||
/* Copyright (c) 2024-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Using descriptor buffers replacing descriptor sets with VK_EXT_descriptor_buffer
|
||||
* This render multiple cubes passing uniform buffers and combined image samples to the GPU using descriptor buffers instead of descriptor sets
|
||||
* This allows for a more bindless design
|
||||
*/
|
||||
|
||||
#include "descriptor_buffer_basic.h"
|
||||
|
||||
#include "core/buffer.h"
|
||||
#include "scene_graph/components/sub_mesh.h"
|
||||
|
||||
DescriptorBufferBasic::DescriptorBufferBasic()
|
||||
{
|
||||
title = "Descriptor buffers";
|
||||
|
||||
set_api_version(VK_API_VERSION_1_1);
|
||||
|
||||
// Enable extension required for descriptor buffers
|
||||
add_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
|
||||
|
||||
add_device_extension(VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
DescriptorBufferBasic::~DescriptorBufferBasic()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), uniform_binding_descriptor.layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), image_binding_descriptor.layout, nullptr);
|
||||
for (auto &cube : cubes)
|
||||
{
|
||||
cube.uniform_buffer.reset();
|
||||
cube.texture.image.reset();
|
||||
vkDestroySampler(get_device().get_handle(), cube.texture.sampler, nullptr);
|
||||
}
|
||||
uniform_buffers.scene.reset();
|
||||
uniform_binding_descriptor.buffer.reset();
|
||||
image_binding_descriptor.buffer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
|
||||
// Enable features required for this example
|
||||
|
||||
// We need device addresses for buffers in certain places
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceBufferDeviceAddressFeatures,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES,
|
||||
bufferDeviceAddress);
|
||||
|
||||
// We need to enable the descriptor buffer feature of the VK_EXT_descriptor_buffer extension
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceDescriptorBufferFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT,
|
||||
descriptorBuffer);
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::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));
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
||||
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);
|
||||
|
||||
const auto &vertex_buffer = models.cube->vertex_buffers.at("vertex_buffer");
|
||||
auto &index_buffer = models.cube->index_buffer;
|
||||
|
||||
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, models.cube->index_type);
|
||||
|
||||
// Descriptor buffer bindings
|
||||
// Binding 0 = uniform buffer
|
||||
VkDescriptorBufferBindingInfoEXT descriptor_buffer_binding_info[2]{};
|
||||
descriptor_buffer_binding_info[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT;
|
||||
descriptor_buffer_binding_info[0].address = uniform_binding_descriptor.buffer->get_device_address();
|
||||
descriptor_buffer_binding_info[0].usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
// Binding 1 = Image
|
||||
descriptor_buffer_binding_info[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT;
|
||||
descriptor_buffer_binding_info[1].address = image_binding_descriptor.buffer->get_device_address();
|
||||
descriptor_buffer_binding_info[1].usage = VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
vkCmdBindDescriptorBuffersEXT(draw_cmd_buffers[i], 2, descriptor_buffer_binding_info);
|
||||
|
||||
uint32_t buffer_index_ubo = 0;
|
||||
uint32_t buffer_index_image = 1;
|
||||
VkDeviceSize buffer_offset = 0;
|
||||
|
||||
// Global Matrices (set 0)
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &buffer_index_ubo, &buffer_offset);
|
||||
|
||||
// Set an offset into descriptor for each model
|
||||
for (size_t j = 0; j < cubes.size(); j++)
|
||||
{
|
||||
// Uniform buffer (set 1)
|
||||
// Model ubos start at offset * (j + 1) (+1 as slot 0 is global matrices)
|
||||
buffer_offset = (j + 1) * uniform_binding_descriptor.size;
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 1, 1, &buffer_index_ubo, &buffer_offset);
|
||||
// Image (set 2)
|
||||
buffer_offset = j * image_binding_descriptor.size;
|
||||
vkCmdSetDescriptorBufferOffsetsEXT(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 2, 1, &buffer_index_image, &buffer_offset);
|
||||
draw_model(models.cube, draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::load_assets()
|
||||
{
|
||||
models.cube = load_model("scenes/textured_unit_cube.gltf");
|
||||
cubes[0].texture = load_texture("textures/crate01_color_height_rgba.ktx", vkb::sg::Image::Color);
|
||||
cubes[1].texture = load_texture("textures/crate02_color_height_rgba.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
inline VkDeviceSize aligned_size(VkDeviceSize value, VkDeviceSize alignment)
|
||||
{
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::setup_descriptor_set_layout()
|
||||
{
|
||||
// Using descriptor buffers still requires the creation of descriptor set layouts
|
||||
|
||||
VkDescriptorSetLayoutBinding set_layout_binding{};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info{};
|
||||
descriptor_layout_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
descriptor_layout_create_info.bindingCount = 1;
|
||||
descriptor_layout_create_info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
descriptor_layout_create_info.pBindings = &set_layout_binding;
|
||||
|
||||
// Create a layout for uniform buffers
|
||||
set_layout_binding = vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &uniform_binding_descriptor.layout));
|
||||
|
||||
// Create a layout for combined image samplers
|
||||
set_layout_binding = vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &image_binding_descriptor.layout));
|
||||
|
||||
// Create a pipeline layout using set 0 = Camera UBO, set 1 = Model UBO and set 2 = Model combined image
|
||||
const std::array<VkDescriptorSetLayout, 3> descriptor_set_layouts = {uniform_binding_descriptor.layout, uniform_binding_descriptor.layout, image_binding_descriptor.layout};
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(descriptor_set_layouts.data(), static_cast<uint32_t>(descriptor_set_layouts.size()));
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
|
||||
// Get set layout descriptor sizes.
|
||||
vkGetDescriptorSetLayoutSizeEXT(get_device().get_handle(), uniform_binding_descriptor.layout, &uniform_binding_descriptor.size);
|
||||
vkGetDescriptorSetLayoutSizeEXT(get_device().get_handle(), image_binding_descriptor.layout, &image_binding_descriptor.size);
|
||||
|
||||
// Adjust set layout sizes to alignment.
|
||||
uniform_binding_descriptor.size = aligned_size(uniform_binding_descriptor.size, descriptor_buffer_properties.descriptorBufferOffsetAlignment);
|
||||
image_binding_descriptor.size = aligned_size(image_binding_descriptor.size, descriptor_buffer_properties.descriptorBufferOffsetAlignment);
|
||||
|
||||
// Get descriptor bindings offsets as descriptors are placed inside set layout by those offsets.
|
||||
vkGetDescriptorSetLayoutBindingOffsetEXT(get_device().get_handle(), uniform_binding_descriptor.layout, 0u, &uniform_binding_descriptor.offset);
|
||||
vkGetDescriptorSetLayoutBindingOffsetEXT(get_device().get_handle(), image_binding_descriptor.layout, 0u, &image_binding_descriptor.offset);
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::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);
|
||||
|
||||
// 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, 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: 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();
|
||||
|
||||
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;
|
||||
|
||||
// We need to set this flag to let the implementation know that this pipeline uses descriptor buffers
|
||||
pipeline_create_info.flags = VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT;
|
||||
|
||||
const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {
|
||||
load_shader("descriptor_buffer_basic", "cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
|
||||
load_shader("descriptor_buffer_basic", "cube.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));
|
||||
}
|
||||
|
||||
// This function creates the descriptor buffers and puts the descriptors into those buffers, so they can be used during command buffer creation
|
||||
void DescriptorBufferBasic::prepare_descriptor_buffer()
|
||||
{
|
||||
// This buffer will contain resource descriptors for all the uniform buffers (one per cube and one with global matrices)
|
||||
uniform_binding_descriptor.buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
(static_cast<uint32_t>(cubes.size()) + 1) * uniform_binding_descriptor.size,
|
||||
VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Samplers and combined images need to be stored in a separate buffer due to different flags (VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT) (one image per cube)
|
||||
image_binding_descriptor.buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
static_cast<uint32_t>(cubes.size()) * image_binding_descriptor.size,
|
||||
VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Put the descriptors into the above buffers
|
||||
|
||||
// For combined images we need to put descriptors into the descriptor buffers
|
||||
// We use pointers to offset and align the data we put into the descriptor buffers
|
||||
char *image_descriptor_buf_ptr = (char *) image_binding_descriptor.buffer->get_data();
|
||||
for (size_t i = 0; i < cubes.size(); i++)
|
||||
{
|
||||
VkDescriptorImageInfo image_descriptor = create_descriptor(cubes[i].texture);
|
||||
|
||||
VkDescriptorGetInfoEXT image_descriptor_info{VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT};
|
||||
image_descriptor_info.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
image_descriptor_info.data.pCombinedImageSampler = &image_descriptor;
|
||||
|
||||
// Note: we can just write combined image sampler descriptors back-to-back in the buffer here
|
||||
// regardless of whether descriptor_buffer_properties.combinedImageSamplerDescriptorSingleArray is true or
|
||||
// false because these aren't an array of descriptors, just individual descriptors in the same buffer.
|
||||
// If these were actually an array (descriptor count > 1) then we would have to check the
|
||||
// combinedImageSamplerDescriptorSingleArray property and separate the image descriptor part from the
|
||||
// sampler descriptor part. We would place all the image descriptors first, followed by all the samplers.
|
||||
|
||||
vkGetDescriptorEXT(get_device().get_handle(), &image_descriptor_info, descriptor_buffer_properties.combinedImageSamplerDescriptorSize, image_descriptor_buf_ptr + i * image_binding_descriptor.size + image_binding_descriptor.offset);
|
||||
}
|
||||
|
||||
// For uniform buffers we only need to put their buffer device addresses into the descriptor buffers
|
||||
char *uniform_descriptor_buf_ptr = (char *) uniform_binding_descriptor.buffer->get_data();
|
||||
|
||||
// Global matrices uniform buffer
|
||||
VkDescriptorAddressInfoEXT addr_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT};
|
||||
addr_info.address = uniform_buffers.scene->get_device_address();
|
||||
addr_info.range = uniform_buffers.scene->get_size();
|
||||
addr_info.format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
VkDescriptorGetInfoEXT buffer_descriptor_info{VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT};
|
||||
buffer_descriptor_info.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
buffer_descriptor_info.data.pUniformBuffer = &addr_info;
|
||||
vkGetDescriptorEXT(get_device().get_handle(), &buffer_descriptor_info, descriptor_buffer_properties.uniformBufferDescriptorSize, uniform_descriptor_buf_ptr);
|
||||
|
||||
// Per-cube uniform buffers
|
||||
// We use pointers to offset and align the data we put into the descriptor buffers
|
||||
for (size_t i = 0; i < cubes.size(); i++)
|
||||
{
|
||||
VkDescriptorAddressInfoEXT cube_addr_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT};
|
||||
cube_addr_info.address = cubes[i].uniform_buffer->get_device_address();
|
||||
cube_addr_info.range = cubes[i].uniform_buffer->get_size();
|
||||
cube_addr_info.format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
buffer_descriptor_info.data.pUniformBuffer = &cube_addr_info;
|
||||
vkGetDescriptorEXT(get_device().get_handle(), &buffer_descriptor_info, descriptor_buffer_properties.uniformBufferDescriptorSize, uniform_descriptor_buf_ptr + (i + 1) * uniform_binding_descriptor.size + uniform_binding_descriptor.offset);
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::prepare_uniform_buffers()
|
||||
{
|
||||
// Vertex shader scene uniform buffer block
|
||||
uniform_buffers.scene = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(UboScene),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
// Vertex shader cube model uniform buffer blocks
|
||||
for (auto &cube : cubes)
|
||||
{
|
||||
cube.uniform_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(glm::mat4),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
|
||||
update_uniform_buffers();
|
||||
update_cube_uniform_buffers(0.0f);
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::update_uniform_buffers()
|
||||
{
|
||||
ubo_scene.projection = camera.matrices.perspective;
|
||||
ubo_scene.view = camera.matrices.view;
|
||||
uniform_buffers.scene->convert_and_update(ubo_scene);
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::update_cube_uniform_buffers(float delta_time)
|
||||
{
|
||||
cubes[0].model_mat = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f));
|
||||
cubes[1].model_mat = glm::translate(glm::mat4(1.0f), glm::vec3(1.5f, 0.5f, 0.0f));
|
||||
|
||||
for (auto &cube : cubes)
|
||||
{
|
||||
cube.model_mat = glm::rotate(cube.model_mat, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
cube.model_mat = glm::rotate(cube.model_mat, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
cube.model_mat = glm::rotate(cube.model_mat, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
cube.uniform_buffer->convert_and_update(cube.model_mat);
|
||||
}
|
||||
|
||||
if (animate)
|
||||
{
|
||||
cubes[0].rotation.x += 2.5f * delta_time;
|
||||
if (cubes[0].rotation.x > 360.0f)
|
||||
{
|
||||
cubes[0].rotation.x -= 360.0f;
|
||||
}
|
||||
cubes[1].rotation.y += 2.0f * delta_time;
|
||||
if (cubes[1].rotation.y > 360.0f)
|
||||
{
|
||||
cubes[1].rotation.y -= 360.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::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 DescriptorBufferBasic::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Extension specific functions
|
||||
*/
|
||||
|
||||
VkPhysicalDeviceProperties2KHR device_properties{};
|
||||
descriptor_buffer_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT;
|
||||
device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
|
||||
device_properties.pNext = &descriptor_buffer_properties;
|
||||
vkGetPhysicalDeviceProperties2KHR(get_device().get_gpu().get_handle(), &device_properties);
|
||||
|
||||
if (descriptor_buffer_properties.maxResourceDescriptorBufferBindings < 2)
|
||||
{
|
||||
LOGE("VkPhysicalDeviceDescriptorBufferPropertiesEXT.maxResourceDescriptorBufferBindings={}. Sample requires at least 2 to run.", descriptor_buffer_properties.maxResourceDescriptorBufferBindings);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
End of extension specific functions
|
||||
*/
|
||||
|
||||
// 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), 512.0f, 0.1f);
|
||||
camera.set_rotation(glm::vec3(0.0f, 0.0f, 0.0f));
|
||||
camera.set_translation(glm::vec3(0.0f, 0.0f, -5.0f));
|
||||
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_descriptor_buffer();
|
||||
prepare_pipelines();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (animate)
|
||||
{
|
||||
update_cube_uniform_buffers(delta_time);
|
||||
}
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorBufferBasic::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
drawer.checkbox("Animate", &animate);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_descriptor_buffer_basic()
|
||||
{
|
||||
return std::make_unique<DescriptorBufferBasic>();
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/* Copyright (c) 2023-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Using descriptor buffers replacing descriptor sets with VK_EXT_descriptor_buffer
|
||||
* This render multiple cubes passing uniform buffers and combined image samples to the GPU using descriptor buffers instead of descriptor sets
|
||||
* This allows for a more bindless design
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class DescriptorBufferBasic : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
bool animate = true;
|
||||
|
||||
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer_properties{};
|
||||
|
||||
struct DescriptorData
|
||||
{
|
||||
VkDescriptorSetLayout layout{VK_NULL_HANDLE};
|
||||
std::unique_ptr<vkb::core::BufferC> buffer;
|
||||
VkDeviceSize size;
|
||||
VkDeviceSize offset;
|
||||
};
|
||||
DescriptorData uniform_binding_descriptor;
|
||||
DescriptorData image_binding_descriptor;
|
||||
|
||||
struct Cube
|
||||
{
|
||||
Texture texture;
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer;
|
||||
glm::vec3 rotation;
|
||||
glm::mat4 model_mat;
|
||||
};
|
||||
std::array<Cube, 2> cubes;
|
||||
|
||||
struct Models
|
||||
{
|
||||
std::unique_ptr<vkb::sg::SubMesh> cube;
|
||||
} models;
|
||||
|
||||
struct UniformBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> scene;
|
||||
} uniform_buffers;
|
||||
|
||||
struct UboScene
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
} ubo_scene;
|
||||
|
||||
VkPipeline pipeline{VK_NULL_HANDLE};
|
||||
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
|
||||
|
||||
DescriptorBufferBasic();
|
||||
~DescriptorBufferBasic() override;
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void build_command_buffers() override;
|
||||
void load_assets();
|
||||
void setup_descriptor_set_layout();
|
||||
void prepare_pipelines();
|
||||
void prepare_descriptor_buffer();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void update_cube_uniform_buffers(float delta_time);
|
||||
void draw();
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void render(float delta_time) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_descriptor_buffer_basic();
|
||||
@@ -1,38 +0,0 @@
|
||||
# Copyright (c) 2021-2024, 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.
|
||||
#
|
||||
|
||||
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 "Hans-Kristian Arntzen"
|
||||
NAME "Descriptor indexing"
|
||||
DESCRIPTION "Demonstrates update-after-bind as well as non-uniform indexing of descriptors (VK_EXT_descriptor_indexing)"
|
||||
SHADER_FILES_GLSL
|
||||
"descriptor_indexing/glsl/nonuniform-quads.vert"
|
||||
"descriptor_indexing/glsl/nonuniform-quads.frag"
|
||||
"descriptor_indexing/glsl/update-after-bind-quads.vert"
|
||||
"descriptor_indexing/glsl/update-after-bind-quads.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"descriptor_indexing/hlsl/nonuniform-quads.vert.hlsl"
|
||||
"descriptor_indexing/hlsl/nonuniform-quads.frag.hlsl"
|
||||
"descriptor_indexing/hlsl/update-after-bind-quads.vert.hlsl"
|
||||
"descriptor_indexing/hlsl/update-after-bind-quads.frag.hlsl"
|
||||
DXC_ADDITIONAL_ARGUMENTS "-fspv-extension=SPV_EXT_descriptor_indexing")
|
||||
@@ -1,447 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2021-2024, 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.
|
||||
-
|
||||
////
|
||||
= Descriptor indexing
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/descriptor_indexing[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
Descriptor indexing is an extension which adds a *lot* of flexibility to how resources are accessed.
|
||||
The core functionality of this extension is that we can treat descriptor memory as one massive array, and we can freely access any resource we want at any time, by indexing.
|
||||
The main insight is that if an array is large enough, an index into that array is indistinguishable from a pointer.
|
||||
By allowing free access to any resource at any time, we can efficiently implement some advanced algorithms which rely on this functionality.
|
||||
|
||||
Descriptor indexing is also known by the term "bindless", which refers to the fact that binding individual descriptor sets and descriptors is no longer the primary way we keep shader pipelines fed.
|
||||
Instead, we can bind a huge descriptor set once and just index into a large number of descriptors.
|
||||
"Bindless algorithms" are generally built around this flexibility where we either index freely into a lot of descriptors at once, or update descriptors where we please.
|
||||
In this model, "binding" descriptors is not a concern anymore.
|
||||
At most, we need to write/copy descriptors to where we need them and we can now consider descriptors more like memory blobs rather than highly structured API objects.
|
||||
|
||||
== Use cases
|
||||
|
||||
=== Update-After-Bind, streaming descriptors concurrently
|
||||
|
||||
The first major feature is update-after-bind.
|
||||
In Vulkan, you generally have to create a `VkDescriptorSet` and update it with all descriptors before you call `vkCmdBindDescriptorSets`.
|
||||
After a set is bound, the descriptor set cannot be updated again until the GPU is done using it.
|
||||
This gives drivers a lot of flexibility in how they access the descriptors.
|
||||
They are free to copy the descriptors and pack them somewhere else, promote them to hardware registers, the list goes on.
|
||||
|
||||
Update-After-Bind gives flexibility to applications instead.
|
||||
Descriptors can be updated at any time as long as they are not actually accessed by the GPU.
|
||||
Descriptors can also be updated while the descriptor set is bound to a command buffer, which enables a "streaming" use case.
|
||||
|
||||
The link:../../performance/constant_data[Constant Data performance sample] also demonstrates update-after-bind descriptors.
|
||||
|
||||
==== Concurrent updates
|
||||
|
||||
Another "hidden" feature of update-after-bind is that it is possible to update the descriptor set from multiple threads.
|
||||
This is very useful for true "bindless" since unrelated tasks might want to update descriptors in different parts of the streamed/bindless descriptor set.
|
||||
|
||||
==== Descriptor flags
|
||||
|
||||
To enable UPDATE_AFTER_BIND_BIT features for a descriptor binding, there is a little song and dance that must be performed.
|
||||
|
||||
In `VkDescriptorSetLayoutCreateInfo` we must pass down binding flags in a separate struct with `pNext`.
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkDescriptorSetLayoutCreateInfo set_layout_create_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
set_layout_create_info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT;
|
||||
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;
|
||||
|
||||
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));
|
||||
----
|
||||
|
||||
The `VkDescriptorPool` must also be created with UPDATE_AFTER_BIND_BIT.
|
||||
Note that there is global limit to how many UPDATE_AFTER_BIND_BIT descriptors can be allocated at any point.
|
||||
The min-spec here is 500k, which should be good enough.
|
||||
|
||||
See code for more detailed comments.
|
||||
|
||||
=== Non-uniform indexing, enabling advanced algorithms
|
||||
|
||||
While update-after-bind adds flexibility to descriptor management, non-uniform indexing adds great flexibility for shaders.
|
||||
|
||||
==== The different levels of indexing resources
|
||||
|
||||
How we access resources has changed a lot over the years.
|
||||
Mostly this is due to hardware considerations, but modern hardware is generally quite flexible in how resources are accessed.
|
||||
|
||||
===== Constant indexing
|
||||
|
||||
In the beginning there was constant indexing.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(set = 0, binding = 0) uniform sampler2D Tex[4];
|
||||
|
||||
texture(Tex[0], ...);
|
||||
texture(Tex[2], ...);
|
||||
|
||||
// We can trivially flatten a constant-indexed array into individual resources,
|
||||
// so, constant indexing requires no fancy hardware indexing support.
|
||||
layout(set = 0, binding = 0) uniform sampler2D Tex0;
|
||||
layout(set = 0, binding = 1) uniform sampler2D Tex1;
|
||||
layout(set = 0, binding = 2) uniform sampler2D Tex2;
|
||||
layout(set = 0, binding = 3) uniform sampler2D Tex3;
|
||||
----
|
||||
|
||||
===== Dynamic indexing
|
||||
|
||||
After constant indexing we have dynamic indexing.
|
||||
This has been supported since Vulkan 1.0.
|
||||
The dynamic indexing features allow us to use a non-constant expression to index an array.
|
||||
The restriction is that the index must be *dynamically uniform*, which will be explained later ...
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(set = 0, binding = 0) uniform sampler2D Tex[4];
|
||||
|
||||
texture(Tex[dynamically_uniform_expression], ...);
|
||||
----
|
||||
|
||||
===== Non-uniform indexing
|
||||
|
||||
Non-uniform indexing completely removes all restrictions on how we index into arrays, but we must notify our intent to the compiler.
|
||||
Normally, drivers and hardware can assume that the dynamically uniform guarantee holds, and optimize for that case.
|
||||
If we use the `nonuniformEXT` decoration in `GL_EXT_nonuniform_qualifier` we can let the compiler know that the guarantee does not necessarily hold, and the compiler will deal with it in the most efficient way possible for the target hardware.
|
||||
|
||||
The rationale for having to annotate like this is that driver compiler backends would be forced to be more conservative than necessary if applications were not required to use `nonuniformEXT`.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
// Unsized arrays, nice!
|
||||
layout(set = 0, binding = 0) uniform sampler2D Tex[];
|
||||
texture(Tex[nonuniformEXT(arbitrary_expression)], ...);
|
||||
----
|
||||
|
||||
==== Non-uniform indexing vs. texture atlas vs. texture array
|
||||
|
||||
Accessing arbitrary textures in a draw call is not a new problem, and graphics programmers have found ways over the years to workaround restrictions in older APIs.
|
||||
Rather than having multiple textures, it is technically possible to pack multiple textures into one texture resource, and sample from the correct part of the texture.
|
||||
This kind of technique is typically referred to as "texture atlas".
|
||||
Texture arrays (e.g.
|
||||
sampler2DArray) is another feature which can be used for similar purposes.
|
||||
|
||||
Problems with atlas:
|
||||
|
||||
* Mip-mapping is hard to implement, and must likely be done manually with derivatives and math
|
||||
* Anisotropic filtering is basically impossible
|
||||
* Any other sampler addressing than `CLAMP_TO_EDGE` is very awkward to implement
|
||||
* Cannot use different texture formats
|
||||
|
||||
Problems with texture array:
|
||||
|
||||
* All resolutions must match
|
||||
* Number of array layers is limited (just 256 in min-spec)
|
||||
* Cannot use different texture formats
|
||||
|
||||
Non-uniform indexing solves these issues since we can freely use multiple sampled image descriptors instead.
|
||||
Atlases and texture arrays still have their place.
|
||||
There are many use cases where these restrictions do not cause problems.
|
||||
|
||||
==== Not just textures
|
||||
|
||||
Non-uniform indexing is not just limited to textures (although that is the most relevant use case).
|
||||
Any descriptor type can be used as long as the device supports it.
|
||||
|
||||
==== When to use non-uniform indexing qualifier
|
||||
|
||||
*Dynamically uniform* is a somewhat difficult concept to understand.
|
||||
There is some terminology we must introduce here.
|
||||
|
||||
===== The invocation group
|
||||
|
||||
The invocation group is a set of threads (invocations) which work together to perform a task.
|
||||
|
||||
In graphics pipelines, the invocation group is all threads which are spawned as part of a single draw command.
|
||||
This includes multiple instances, and for multi-draw-indirect it is limited to a single `gl_DrawID`.
|
||||
|
||||
In compute pipelines, the invocation group is a single workgroup, so it's very easy to know when it is safe to avoid nonuniformEXT.
|
||||
|
||||
An expression is considered dynamically uniform if all invocations in an invocation group have the same value.
|
||||
|
||||
===== How do subgroups interact here?
|
||||
|
||||
It is very easy to think that dynamically uniform just means "as long as the index is uniform in the subgroup, it's fine!".
|
||||
This is certainly true for most (desktop) architectures, but not all.
|
||||
|
||||
It is technically possible that a value can be subgroup uniform, but still not dynamically uniform.
|
||||
Consider a case where we a have a workgroup size of 128 threads, with a subgroup size of 32.
|
||||
Even if each subgroup does `subgroupBroadcastFirst()` on the index, each subgroup might have different values, and thus, we still technically need `nonuniformEXT` here.
|
||||
If you know that you have only one subgroup per workgroup however, `subgroupBroadcastFirst()` is good enough.
|
||||
|
||||
The safe thing to do is to just add `nonuniformEXT` if you cannot prove the dynamically uniform property.
|
||||
If the compiler knows that it only really cares about subgroup uniformity, it could trivially optimize away `nonuniformEXT(subgroupBroadcastFirst())` anyways.
|
||||
|
||||
The common reason to use subgroups in the first place, is that it was an old workaround for lack of true non-uniform indexing, especially for desktop GPUs.
|
||||
A common pattern would be something like:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
bool needs_work = true;
|
||||
uint index = something_non_uniform();
|
||||
do
|
||||
{
|
||||
if (subgroupBroadcastFirst(index) == index)
|
||||
{
|
||||
// index is subgroup uniform, which is good enough for most (all?) desktop GPU architectures.
|
||||
// It is not technically correct, just use nonuniformEXT().
|
||||
// This style of code can still be worthwhile if we're loading uniform data based on index,
|
||||
// since we can greatly improve memory access patterns,
|
||||
// but that's another topic and is very IHV dependent ...
|
||||
texture(Tex[index], ...);
|
||||
needs_work = false;
|
||||
}
|
||||
} while (needs_work);
|
||||
----
|
||||
|
||||
===== Vulkan GLSL examples
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
#version 450
|
||||
#extension GL_EXT_nonuniform_qualifier : require
|
||||
layout(local_size_x = 64) in;
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D Combined[];
|
||||
layout(set = 1, binding = 0) uniform texture2D Tex[];
|
||||
layout(set = 2, binding = 0) uniform sampler Samp[];
|
||||
layout(set = 3, binding = 0) uniform U { vec4 v; } UBO[];
|
||||
layout(set = 4, binding = 0) buffer S { vec4 v; } SSBO[];
|
||||
layout(set = 5, binding = 0, r32ui) uniform uimage2D Img[];
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_GlobalInvocationID.x;
|
||||
vec2 uv = vec2(gl_GlobalInvocationID.yz) / 1024.0;
|
||||
|
||||
vec4 a = textureLod(Combined[nonuniformEXT(index)], uv, 0.0);
|
||||
vec4 b = textureLod(nonuniformEXT(sampler2D(Tex[index], Samp[index])), uv, 0.0);
|
||||
vec4 c = UBO[nonuniformEXT(index)].v;
|
||||
vec4 d = SSBO[nonuniformEXT(index)].v;
|
||||
|
||||
imageAtomicAdd(Img[nonuniformEXT(index)], ivec2(0), floatBitsToUint(a.x + b.y + c.z + d.w));
|
||||
}
|
||||
----
|
||||
|
||||
===== HLSL examples
|
||||
|
||||
With DXC:
|
||||
|
||||
[,hlsl]
|
||||
----
|
||||
Texture2D<float4> Tex[] : register(t0, space0);
|
||||
SamplerState Samp[] : register(s0, space1);
|
||||
|
||||
struct Float4 { float4 v; };
|
||||
ConstantBuffer<Float4> CBV[] : register(b0, space2);
|
||||
RWStructuredBuffer<float4> SSBO[] : register(u0, space3);
|
||||
RWTexture2D<uint> Img[] : register(u0, space4);
|
||||
|
||||
[numthreads(64, 1, 1)]
|
||||
void main(uint3 thr : SV_DispatchThreadID)
|
||||
{
|
||||
uint index = thr.x;
|
||||
float2 uv = float2(thr.yz) / 1024.0;
|
||||
float4 a = Tex[NonUniformResourceIndex(index)].SampleLevel(Samp[NonUniformResourceIndex(index)], uv, 0.0);
|
||||
float4 b = CBV[NonUniformResourceIndex(index)].v;
|
||||
float4 c = SSBO[NonUniformResourceIndex(index)][0];
|
||||
|
||||
uint out_value;
|
||||
InterlockedAdd(Img[NonUniformResourceIndex(index)][int2(0, 0)], asuint(a.x + b.y + c.z), out_value);
|
||||
}
|
||||
----
|
||||
|
||||
===== What to look for in SPIR-V
|
||||
|
||||
In SPIR-V, it might be a bit unclear where to place the `NonUniform` decoration, but it is defined such that it is the final argument which is used in a load/store/sample/atomic command that must be decorated.
|
||||
It is meaningless to decorate the index expression itself (although it would be natural!).
|
||||
Some older buggy drivers did rely on the index itself being decorated though, so if you're emitting SPIR-V yourself, it does not hurt to place NonUniform index redundantly, although it is an ugly caveat ...
|
||||
|
||||
E.g.
|
||||
for the `nonuniform-quads.frag` shader, we get:
|
||||
|
||||
----
|
||||
OpDecorate %27 NonUniform ; By spec, this is the only NonUniform we need
|
||||
%26 = OpSampledImage %25 %20 %24
|
||||
%27 = OpCopyObject %25 %26 ; This is a glslangValidator quirk
|
||||
%32 = OpImageSampleImplicitLod %v4float %27 %31 ; It is %27 here which is significant
|
||||
----
|
||||
|
||||
Similarly for `OpLoad` and `OpStore` from and to UBO/SSBO it would be the pointer argument, and for `OpAtomic*` we would use NonUniform on the pointer argument.
|
||||
|
||||
== The sample
|
||||
|
||||
image::./images/sample.png[Sample]
|
||||
|
||||
The goal of this sample is to demonstrate how to use the two main use cases enabled by descriptor indexing.
|
||||
|
||||
On the left side, we bind 64 unique textures and render them all in one draw call.
|
||||
This makes use of non-uniform indexing of descriptors and assigns `gl_InstanceIndex` to an index into the descriptor array.
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
#extension GL_EXT_nonuniform_qualifier : require
|
||||
layout(set = 0, binding = 0) uniform texture2D Textures[];
|
||||
layout(set = 1, binding = 0) uniform sampler ImmutableSampler;
|
||||
out_frag_color = texture(nonuniformEXT(sampler2D(Textures[in_texture_index], ImmutableSampler)), in_uv);
|
||||
----
|
||||
|
||||
The critical aspect here is `nonuniformEXT`, which lets us index into an array of resources where the index is *not* dynamically uniform.
|
||||
For graphics, dynamically uniform means that the index is the same across all threads spawned by a draw commands.
|
||||
|
||||
On the right side, we render the same textures, but in this case we use the "update-after-bind" model, where we simply stream descriptors to a single descriptor set.
|
||||
This is a style where we eliminate most of the complication with descriptor set management, and treat descriptor memory as a ring buffer.
|
||||
We can place an offset into this ring in push constant memory, e.g.:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
layout(push_constant) uniform Registers
|
||||
{
|
||||
layout(offset = 4) uint table_offset;
|
||||
} registers;
|
||||
|
||||
void main()
|
||||
{
|
||||
out_frag_color = texture(sampler2D(Textures[registers.table_offset], ImmutableSampler), in_uv);
|
||||
}
|
||||
----
|
||||
|
||||
We could go up to as much as 500k textures in the minimum spec for Vulkan, but it would be impractical to use that many for purposes of visualization.
|
||||
|
||||
== Debugging descriptor indexing
|
||||
|
||||
Descriptor indexing is very powerful, but it also means debugging and validating such shaders becomes more difficult.
|
||||
There is more room for errors, e.g.
|
||||
it is possible for application to index into descriptors that were never initialized, or you can access stale descriptors which resource was destroyed earlier.
|
||||
There are means to debug and validate this in Vulkan.
|
||||
|
||||
=== RenderDoc
|
||||
|
||||
For example, here we look at the non-uniform draw call which renders the left half of the screen.
|
||||
image:./images/non-uniform-draw.png[non-uniform-draw]
|
||||
|
||||
RenderDoc supports debugging of descriptor indexing.
|
||||
When inspecting the state panel, we can see all the descriptors which were accessed in the draw call.
|
||||
It is important to note that RenderDoc must instrument your shaders with extra code which tags the resources which are _actually_ accessed.
|
||||
|
||||
image::./images/non-uniform-usage.png[non-uniform-usage]
|
||||
|
||||
Here we can see that all array entries were used, except index 0, which was never accessed since that particular quad was fully clipped away.
|
||||
This is a quirk to keep in mind.
|
||||
|
||||
In the update-after-bind case, we draw one texture at a time, and these cases are much more straight forward to debug.
|
||||
|
||||
image::./images/update-after-bind.png[update-after-bind]
|
||||
|
||||
Here we see that we accessed index 202, which corresponds to the push constant `table_offset` we passed to the shader, neat!
|
||||
|
||||
=== GPU assisted validation
|
||||
|
||||
With descriptor indexing, it is impossible for a validation layer to validate at draw time, since it cannot know which resources a shader intends to access, and with update-after-bind, the descriptor might be filled in right before `vkQueueSubmit`.
|
||||
Similar to RenderDoc, the validation layers must instrument your shaders which slows runtime down significantly.
|
||||
For this reason, GPU-assisted validation is opt-in.
|
||||
|
||||
In `instance.cpp` we make use of `VK_EXT_validation_features` to enable GPU-assisted validation if `VKB_VALIDATION_LAYERS_GPU_ASSISTED` is set in the CMake build.
|
||||
|
||||
The key thing to know is that this is an extension exposed by the validation layer itself, so we need to query instance extensions directly on the layer.
|
||||
E.g.:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
bool validation_features = false;
|
||||
uint32_t layer_instance_extension_count;
|
||||
VK_CHECK(vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &layer_instance_extension_count, nullptr));
|
||||
std::vector<VkExtensionProperties> available_layer_instance_extensions(layer_instance_extension_count);
|
||||
VK_CHECK(vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &layer_instance_extension_count, available_layer_instance_extensions.data()));
|
||||
|
||||
for (auto &available_extension : available_layer_instance_extensions)
|
||||
{
|
||||
if (strcmp(available_extension.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME) == 0)
|
||||
{
|
||||
validation_features = true;
|
||||
LOGI("{} is available, enabling it", VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);
|
||||
enabled_extensions.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If present, we can pass down information to `vkCreateInstance` about the features we need to enable:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkValidationFeaturesEXT validation_features_info = {VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT};
|
||||
if (validation_features)
|
||||
{
|
||||
static const VkValidationFeatureEnableEXT enable_features[2] = {
|
||||
VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT,
|
||||
VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,
|
||||
};
|
||||
validation_features_info.enabledValidationFeatureCount = 2;
|
||||
validation_features_info.pEnabledValidationFeatures = enable_features;
|
||||
validation_features_info.pNext = instance_info.pNext;
|
||||
instance_info.pNext = &validation_features_info;
|
||||
}
|
||||
----
|
||||
|
||||
The features to enable is `GPU_ASSISTED_EXT` and `RESERVE_BINDING_SLOT_EXT`.
|
||||
The extra descriptor set slot is reserved by validation layers so it can bind metadata buffers.
|
||||
Instrumented shaders will write here as they execute.
|
||||
|
||||
If we enable this, and say pretend that we forgot to update descriptor #3:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
for (unsigned i = 0; i < NumDescriptorsNonUniform; i++)
|
||||
{
|
||||
...
|
||||
if (i != 3)
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), 1, &write, 0, nullptr);
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
We end up with:
|
||||
|
||||
----
|
||||
[error] [framework/core/instance.cpp:41] -1993010233 - UNASSIGNED-Descriptor uninitialized: Validation Error: [ UNASSIGNED-Descriptor uninitialized ] Object 0: handle = 0x55625acf5600, type = VK_OBJECT_TYPE_QUEUE; | MessageID = 0x893513c7 | Descriptor index 3 is uninitialized. Command buffer (0x55625b187090). Draw Index 0x4. Pipeline (0x520000000052). Shader Module (0x510000000051). Shader Instruction Index = 59. Stage = Fragment. Fragment coord (x,y) = (930.5, 0.5). Unable to find SPIR-V OpLine for source information. Build shader with debug info to get source information.
|
||||
[error] [framework/core/instance.cpp:41] -1993010233 - UNASSIGNED-Descriptor uninitialized: Validation Error: [ UNASSIGNED-Descriptor uninitialized ] Object 0: handle = 0x55625acf5600, type = VK_OBJECT_TYPE_QUEUE; | MessageID = 0x893513c7 | Descriptor index 67 is uninitialized. Command buffer (0x55625b184d60). Draw Index 0x4. Pipeline (0x520000000052). Shader Module (0x510000000051). Shader Instruction Index = 59. Stage = Fragment. Fragment coord (x,y) = (944.5, 0.5). Unable to find SPIR-V OpLine for source information. Build shader with debug info to get source information.
|
||||
[error] [framework/core/instance.cpp:41] -1993010233 - UNASSIGNED-Descriptor uninitialized: Validation Error: [ UNASSIGNED-Descriptor uninitialized ] Object 0: handle = 0x55625acf5600, type = VK_OBJECT_TYPE_QUEUE; | MessageID = 0x893513c7 | Descriptor index 131 is uninitialized. Command buffer (0x55625b1893c0). Draw Index 0x4. Pipeline (0x520000000052). Shader Module (0x510000000051). Shader Instruction Index = 59. Stage = Fragment. Fragment coord (x,y) = (944.5, 0.5). Unable to find SPIR-V OpLine for source information. Build shader with debug info to get source information.
|
||||
----
|
||||
|
||||
Adding debug symbols to the SPIR-V helps here, but that's another topic.
|
||||
|
||||
== Conclusion
|
||||
|
||||
Descriptor indexing is a highly potent extension, but with great power comes great responsibility to use all debug tools available to you.
|
||||
@@ -1,583 +0,0 @@
|
||||
/* 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>();
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/* Copyright (c) 2021-2024, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
class DescriptorIndexing : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
DescriptorIndexing();
|
||||
~DescriptorIndexing();
|
||||
|
||||
private:
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
virtual void render(float delta_time) override;
|
||||
virtual void build_command_buffers() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
void create_bindless_descriptors();
|
||||
void create_immutable_sampler_descriptor_set();
|
||||
void create_pipelines();
|
||||
|
||||
struct DescriptorHeap
|
||||
{
|
||||
VkDescriptorSetLayout set_layout{};
|
||||
VkDescriptorPool descriptor_pool{};
|
||||
VkDescriptorSet descriptor_set_update_after_bind{};
|
||||
VkDescriptorSet descriptor_set_nonuniform{};
|
||||
} descriptors;
|
||||
|
||||
struct ImmutableSampler
|
||||
{
|
||||
VkSampler sampler{};
|
||||
VkDescriptorSetLayout set_layout{};
|
||||
VkDescriptorPool descriptor_pool{};
|
||||
VkDescriptorSet descriptor_set{};
|
||||
} sampler;
|
||||
|
||||
struct Pipelines
|
||||
{
|
||||
VkPipelineLayout pipeline_layout{};
|
||||
VkPipeline update_after_bind{};
|
||||
VkPipeline non_uniform_indexing{};
|
||||
} pipelines;
|
||||
|
||||
struct TestImage
|
||||
{
|
||||
VkImage image{};
|
||||
VkImageView image_view{};
|
||||
VkDeviceMemory memory{};
|
||||
};
|
||||
std::vector<TestImage> test_images;
|
||||
void create_images();
|
||||
TestImage create_image(const float rgb[3], unsigned image_seed);
|
||||
|
||||
VkPhysicalDeviceDescriptorIndexingPropertiesEXT descriptor_indexing_properties{};
|
||||
std::default_random_engine rnd{42};
|
||||
std::uniform_real_distribution<float> distribution{0.0f, 0.1f};
|
||||
uint32_t descriptor_offset{};
|
||||
float accumulated_time{};
|
||||
const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_descriptor_indexing();
|
||||
|
Before Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 196 KiB |
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2023-2024, Mobica Limited
|
||||
#
|
||||
# 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 "Khronos"
|
||||
NAME "Dynamic blending"
|
||||
DESCRIPTION "Dynamic blending options available in the VK_EXT_extended_dynamic_state3 extension"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_blending/glsl/blending.vert"
|
||||
"dynamic_blending/glsl/blending.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"dynamic_blending/hlsl/blending.vert.hlsl"
|
||||
"dynamic_blending/hlsl/blending.frag.hlsl")
|
||||
@@ -1,67 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, Mobica Limited
|
||||
-
|
||||
- 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 blending
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates the functionality of VK_EXT_extended_dynamic_state3 related to blending. It includes the
|
||||
following features:
|
||||
|
||||
* `vkCmdSetColorBlendEnableEXT`: toggles blending on and off.
|
||||
* `vkCmdSetColorBlendEquationEXT`: modifies blending operators and factors.
|
||||
* `vkCmdSetColorBlendAdvancedEXT`: utilizes more complex blending operators.
|
||||
* `vkCmdSetColorWriteMaskEXT`: toggles individual channels on and off.
|
||||
|
||||
== How to use in Vulkan
|
||||
|
||||
To utilize this feature, the device extension `VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME` need to be enabled.
|
||||
The extension `VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME` is required for the advanced blend equations.
|
||||
All presented functions take an array of objects defining their action for subsequent color attachments:
|
||||
|
||||
* The `vkCmdSetColorBlendEnableEXT`
|
||||
function expects an array of booleans to toggle blending.
|
||||
* The `vkCmdSetColorBlendEquationEXT` function expects an array of
|
||||
`VkColorBlendEquationEXT` objects which determine operators and factors for
|
||||
color and alpha blending.
|
||||
* The `VkCmdSetColorBlendAdvancedEXT` function expects an array of `VkColorBlendAdvancedEXT` objects, which determine
|
||||
blending operators and premultiplication for color blending.
|
||||
* The `vkCmdSetColorWriteMaskEXT` function expects an array of
|
||||
`VkColorComponentFlags` objects. These objects can be created by combining
|
||||
the desired color bit flags using bitwise oring.
|
||||
|
||||
== The sample
|
||||
|
||||
The sample demonstrates how to set up an application to work with this
|
||||
extension:
|
||||
|
||||
* Enabling the extension.
|
||||
* Setting parameters for the presented methods.
|
||||
|
||||
The sample demonstrates how the use of each operator affects color blending.
|
||||
|
||||
== Documentation links
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetColorBlendEnableEXT.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetColorBlendEquationEXT.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetColorBlendAdvancedEXT.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetColorWriteMaskEXT.html
|
||||
@@ -1,697 +0,0 @@
|
||||
/* Copyright (c) 2023-2025, Mobica
|
||||
*
|
||||
* 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 "dynamic_blending.h"
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "gui.h"
|
||||
|
||||
DynamicBlending::DynamicBlending()
|
||||
{
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME);
|
||||
|
||||
title = "Dynamic blending";
|
||||
}
|
||||
|
||||
DynamicBlending::~DynamicBlending()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicBlending::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.0f, 0.0f, -5.0f});
|
||||
camera.set_rotation({-15.0f, 15.0f, 0.0f});
|
||||
camera.set_perspective(45.0f, static_cast<float>(width) / static_cast<float>(height), 256.0f, 0.1f);
|
||||
|
||||
initialize_operator_names();
|
||||
prepare_uniform_buffers();
|
||||
prepare_scene();
|
||||
setup_descriptor_pool();
|
||||
create_descriptor_set_layout();
|
||||
create_descriptor_set();
|
||||
create_pipelines();
|
||||
build_command_buffers();
|
||||
|
||||
rnd_engine = std::default_random_engine(time(nullptr));
|
||||
rnd_dist = std::uniform_real_distribution<float>(0.0f, 1.0f);
|
||||
|
||||
prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicBlending::initialize_operator_names()
|
||||
{
|
||||
for (uint32_t i = VK_BLEND_OP_ADD; i <= VK_BLEND_OP_MAX; ++i)
|
||||
{
|
||||
VkBlendOp op = static_cast<VkBlendOp>(i);
|
||||
blend_operator.values.push_back(op);
|
||||
blend_operator.names.push_back(vkb::to_string(op));
|
||||
}
|
||||
current_blend_color_operator_index = VK_BLEND_OP_ADD;
|
||||
current_blend_alpha_operator_index = VK_BLEND_OP_ADD;
|
||||
|
||||
for (uint32_t i = VK_BLEND_OP_ZERO_EXT; i <= VK_BLEND_OP_BLUE_EXT; ++i)
|
||||
{
|
||||
VkBlendOp op = static_cast<VkBlendOp>(i);
|
||||
advanced_blend_operator.values.push_back(op);
|
||||
advanced_blend_operator.names.push_back(vkb::to_string(op));
|
||||
}
|
||||
current_advanced_blend_operator_index = VK_BLEND_OP_SRC_OVER_EXT - VK_BLEND_OP_ZERO_EXT;
|
||||
|
||||
for (uint32_t i = VK_BLEND_FACTOR_ZERO; i <= VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; ++i)
|
||||
{
|
||||
// The substr(16) call is used to drop the "VK_BLEND_FACTOR_" prefix
|
||||
blend_factor_names.push_back(vkb::to_string(static_cast<VkBlendFactor>(i)).substr(16));
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicBlending::prepare_scene()
|
||||
{
|
||||
vertices = {
|
||||
{{-1.0f, -1.0f, 1.0f}, {0.0f, 0.0f}},
|
||||
{{1.0f, -1.0f, 1.0f}, {1.0f, 0.0f}},
|
||||
{{1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},
|
||||
|
||||
{{-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f}},
|
||||
{{1.0f, -1.0f, -1.0f}, {1.0f, 0.0f}},
|
||||
{{1.0f, 1.0f, -1.0f}, {1.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, -1.0f}, {0.0f, 1.0f}},
|
||||
};
|
||||
|
||||
std::vector<uint32_t> indices = {
|
||||
6, 5, 4, 4, 7, 6,
|
||||
0, 1, 2, 2, 3, 0};
|
||||
|
||||
index_count = static_cast<uint32_t>(indices.size());
|
||||
|
||||
vertex_buffer_size = static_cast<uint32_t>(vertices.size() * sizeof(Vertex));
|
||||
auto index_buffer_size = indices.size() * sizeof(uint32_t);
|
||||
|
||||
vertex_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
vertex_buffer_size,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_GPU_TO_CPU);
|
||||
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_GPU_TO_CPU);
|
||||
index_buffer->update(indices.data(), index_buffer_size);
|
||||
|
||||
face_preferences[0].index_offset = 0;
|
||||
face_preferences[0].index_count = index_count / 2;
|
||||
face_preferences[0].color_bit_enabled = {true, true, true, true};
|
||||
face_preferences[0].color = {{{1.0f, 0.0f, 0.0f, 1.0f},
|
||||
{0.0f, 1.0f, 0.0f, 1.0f},
|
||||
{0.0f, 0.0f, 1.0f, 1.0f},
|
||||
{0.0f, 0.0f, 0.0f, 1.0f}}};
|
||||
|
||||
face_preferences[1].index_offset = index_count / 2;
|
||||
face_preferences[1].index_count = index_count / 2;
|
||||
face_preferences[1].color_bit_enabled = {true, true, true, true};
|
||||
face_preferences[1].color = {{{0.0f, 1.0f, 1.0f, 0.5f},
|
||||
{1.0f, 0.0f, 1.0f, 0.5f},
|
||||
{1.0f, 1.0f, 0.0f, 0.5f},
|
||||
{1.0f, 1.0f, 1.0f, 0.5f}}};
|
||||
}
|
||||
|
||||
void DynamicBlending::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// We must have this or the sample isn't useful
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3ColorBlendEnable);
|
||||
|
||||
// Only request the features that we support
|
||||
REQUEST_OPTIONAL_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3ColorWriteMask);
|
||||
REQUEST_OPTIONAL_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3ColorBlendEnable);
|
||||
REQUEST_OPTIONAL_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3ColorBlendAdvanced);
|
||||
REQUEST_OPTIONAL_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3ColorBlendEquation);
|
||||
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT,
|
||||
advancedBlendCoherentOperations);
|
||||
}
|
||||
|
||||
void DynamicBlending::prepare_uniform_buffers()
|
||||
{
|
||||
camera_ubo = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(CameraUbo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
color_ubo = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(ColorUbo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
|
||||
void DynamicBlending::update_uniform_buffers()
|
||||
{
|
||||
CameraUbo cam;
|
||||
cam.model = glm::mat4(1.0f);
|
||||
cam.model = glm::translate(cam.model, glm::vec3(0.0f));
|
||||
cam.view = camera.matrices.view;
|
||||
cam.projection = camera.matrices.perspective;
|
||||
|
||||
camera_ubo->convert_and_update(cam);
|
||||
|
||||
update_color();
|
||||
|
||||
glm::mat4 invView = glm::inverse(camera.matrices.view);
|
||||
glm::vec4 plane0(vertices[0].pos[0], vertices[0].pos[1], vertices[0].pos[2], 1.0f);
|
||||
glm::vec4 plane1(vertices[4].pos[0], vertices[4].pos[1], vertices[4].pos[2], 1.0f);
|
||||
|
||||
plane0 = invView * plane0;
|
||||
plane1 = invView * plane1;
|
||||
|
||||
reverse = plane0.z < plane1.z;
|
||||
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
|
||||
void DynamicBlending::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2u),
|
||||
};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
static_cast<uint32_t>(pool_sizes.size()));
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void DynamicBlending::create_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, 0u),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 1u)};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings);
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_set_layout_create_info, nullptr, &descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layout);
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void DynamicBlending::create_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layout, 1u);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*camera_ubo);
|
||||
VkDescriptorBufferInfo color_descriptor = create_descriptor(*color_ubo);
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0u, &buffer_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1u, &color_descriptor),
|
||||
};
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0u, nullptr);
|
||||
}
|
||||
|
||||
void DynamicBlending::create_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(
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
VK_TRUE);
|
||||
|
||||
VkPipelineColorBlendAdvancedStateCreateInfoEXT blendAdvancedEXT{};
|
||||
blendAdvancedEXT.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_ADVANCED_STATE_CREATE_INFO_EXT;
|
||||
blendAdvancedEXT.blendOverlap = VK_BLEND_OVERLAP_UNCORRELATED_EXT;
|
||||
|
||||
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_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,
|
||||
};
|
||||
|
||||
if (eds_feature_support.extendedDynamicState3ColorWriteMask)
|
||||
{
|
||||
dynamic_state_enables.push_back(VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT);
|
||||
}
|
||||
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendEnable)
|
||||
{
|
||||
dynamic_state_enables.push_back(VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT);
|
||||
}
|
||||
|
||||
switch (current_blend_option)
|
||||
{
|
||||
case 0:
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendEquation)
|
||||
{
|
||||
dynamic_state_enables.push_back(VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendAdvanced)
|
||||
{
|
||||
dynamic_state_enables.push_back(VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
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)),
|
||||
};
|
||||
|
||||
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, 2> shader_stages{};
|
||||
shader_stages[0] = load_shader("dynamic_blending", "blending.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("dynamic_blending", "blending.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkGraphicsPipelineCreateInfo graphics_create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
graphics_create.pNext = VK_NULL_HANDLE;
|
||||
graphics_create.renderPass = render_pass;
|
||||
graphics_create.pInputAssemblyState = &input_assembly_state;
|
||||
graphics_create.pRasterizationState = &rasterization_state;
|
||||
graphics_create.pColorBlendState = &color_blend_state;
|
||||
graphics_create.pMultisampleState = &multisample_state;
|
||||
graphics_create.pViewportState = &viewport_state;
|
||||
graphics_create.pDepthStencilState = &depth_stencil_state;
|
||||
graphics_create.pDynamicState = &dynamic_state;
|
||||
graphics_create.pVertexInputState = &vertex_input_state;
|
||||
graphics_create.pTessellationState = VK_NULL_HANDLE;
|
||||
graphics_create.stageCount = 2;
|
||||
graphics_create.pStages = shader_stages.data();
|
||||
graphics_create.layout = pipeline_layout;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
1,
|
||||
&graphics_create,
|
||||
VK_NULL_HANDLE,
|
||||
&pipeline));
|
||||
}
|
||||
|
||||
void DynamicBlending::update_pipeline()
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
create_pipelines();
|
||||
}
|
||||
|
||||
void DynamicBlending::update_color()
|
||||
{
|
||||
for (uint32_t face = 0; face < 2; ++face)
|
||||
{
|
||||
for (uint32_t vertex = 0; vertex < 4; ++vertex)
|
||||
{
|
||||
auto &input_color = face_preferences[face].color[vertex];
|
||||
color.data[face * 4 + vertex] = glm::vec4(input_color[0], input_color[1], input_color[2], input_color[3]);
|
||||
}
|
||||
}
|
||||
color_ubo->convert_and_update(color);
|
||||
}
|
||||
|
||||
void DynamicBlending::randomize_color(std::array<float, 4> &color, bool alpha)
|
||||
{
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
color[i] = rnd_dist(rnd_engine);
|
||||
}
|
||||
if (alpha)
|
||||
{
|
||||
color[3] = rnd_dist(rnd_engine);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicBlending::update_color_uniform()
|
||||
{
|
||||
update_color();
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
|
||||
void DynamicBlending::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
std::array<VkClearValue, 2> clear_values;
|
||||
clear_values[0].color = {clear_color[0], clear_color[1], clear_color[2], clear_color[3]};
|
||||
clear_values[1].depthStencil = {0.0f, 0u};
|
||||
|
||||
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 = static_cast<uint32_t>(clear_values.size());
|
||||
render_pass_begin_info.pClearValues = clear_values.data();
|
||||
|
||||
for (uint32_t i = 0u; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
auto &cmd_buff = draw_cmd_buffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(cmd_buff, &command_buffer_begin_info));
|
||||
|
||||
vkCmdBeginRenderPass(cmd_buff, &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(cmd_buff, 0u, 1u, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(cmd_buff, 0u, 1u, &scissor);
|
||||
|
||||
{
|
||||
vkCmdBindDescriptorSets(cmd_buff, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0u, 1u, &descriptor_set, 0u, nullptr);
|
||||
vkCmdBindPipeline(cmd_buff, 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);
|
||||
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendEnable)
|
||||
{
|
||||
const VkBool32 blend_enable = this->blend_enable;
|
||||
vkCmdSetColorBlendEnableEXT(cmd_buff, 0, 1, &blend_enable);
|
||||
}
|
||||
|
||||
if (current_blend_option == 0)
|
||||
{
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendEquation)
|
||||
{
|
||||
VkColorBlendEquationEXT color_blend_equation;
|
||||
color_blend_equation.colorBlendOp = blend_operator.values[current_blend_color_operator_index];
|
||||
color_blend_equation.srcColorBlendFactor = static_cast<VkBlendFactor>(current_src_color_blend_factor);
|
||||
color_blend_equation.dstColorBlendFactor = static_cast<VkBlendFactor>(current_dst_color_blend_factor);
|
||||
color_blend_equation.alphaBlendOp = blend_operator.values[current_blend_alpha_operator_index];
|
||||
color_blend_equation.srcAlphaBlendFactor = static_cast<VkBlendFactor>(current_src_alpha_blend_factor);
|
||||
color_blend_equation.dstAlphaBlendFactor = static_cast<VkBlendFactor>(current_dst_alpha_blend_factor);
|
||||
vkCmdSetColorBlendEquationEXT(cmd_buff, 0, 1, &color_blend_equation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendAdvanced)
|
||||
{
|
||||
VkColorBlendAdvancedEXT color_blend_advanced;
|
||||
color_blend_advanced.advancedBlendOp = advanced_blend_operator.values[current_advanced_blend_operator_index];
|
||||
color_blend_advanced.srcPremultiplied = src_premultiplied;
|
||||
color_blend_advanced.dstPremultiplied = dst_premultiplied;
|
||||
color_blend_advanced.blendOverlap = VK_BLEND_OVERLAP_CONJOINT_EXT;
|
||||
color_blend_advanced.clampResults = VK_TRUE;
|
||||
vkCmdSetColorBlendAdvancedEXT(cmd_buff, 0, 1, &color_blend_advanced);
|
||||
}
|
||||
}
|
||||
|
||||
if (reverse)
|
||||
{
|
||||
build_command_buffer_for_plane(cmd_buff, face_preferences[1]);
|
||||
build_command_buffer_for_plane(cmd_buff, face_preferences[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
build_command_buffer_for_plane(cmd_buff, face_preferences[0]);
|
||||
build_command_buffer_for_plane(cmd_buff, face_preferences[1]);
|
||||
}
|
||||
}
|
||||
|
||||
draw_ui(cmd_buff);
|
||||
|
||||
vkCmdEndRenderPass(cmd_buff);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(cmd_buff));
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicBlending::build_command_buffer_for_plane(VkCommandBuffer &command_buffer, FacePreferences preferences)
|
||||
{
|
||||
if (eds_feature_support.extendedDynamicState3ColorWriteMask)
|
||||
{
|
||||
std::array<VkColorComponentFlags, 1> color_bit = {
|
||||
(preferences.color_bit_enabled[0] ? VK_COLOR_COMPONENT_R_BIT : 0u) |
|
||||
(preferences.color_bit_enabled[1] ? VK_COLOR_COMPONENT_G_BIT : 0u) |
|
||||
(preferences.color_bit_enabled[2] ? VK_COLOR_COMPONENT_B_BIT : 0u) |
|
||||
(preferences.color_bit_enabled[3] ? VK_COLOR_COMPONENT_A_BIT : 0u)};
|
||||
vkCmdSetColorWriteMaskEXT(command_buffer, 0, 1, color_bit.data());
|
||||
}
|
||||
vkCmdDrawIndexed(command_buffer, preferences.index_count, 1, preferences.index_offset, 0, 0);
|
||||
}
|
||||
|
||||
void DynamicBlending::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
|
||||
auto add_color_edit = [&](const char *caption, std::array<float, 4> &color) {
|
||||
ImGuiColorEditFlags flags = ImGuiColorEditFlags_None | ImGuiColorEditFlags_Float;
|
||||
float color_edit_width = 200;
|
||||
ImGui::PushID(++item_id);
|
||||
if (drawer.color_op<vkb::Drawer::ColorOp::Edit>(caption, color, color_edit_width, flags))
|
||||
// if (drawer.color_edit(caption, color, color_edit_width, flags))
|
||||
{
|
||||
update_color_uniform();
|
||||
}
|
||||
ImGui::PopID();
|
||||
};
|
||||
|
||||
auto add_color_mask_checkbox = [&](const char *caption, bool &enabled, bool same_line = true) {
|
||||
ImGui::PushID(++item_id);
|
||||
if (drawer.checkbox(caption, &enabled))
|
||||
{
|
||||
update_color_uniform();
|
||||
}
|
||||
ImGui::PopID();
|
||||
if (same_line)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
}
|
||||
};
|
||||
|
||||
auto add_next_button = [&](int32_t &index, int32_t first, int32_t last) {
|
||||
|
||||
};
|
||||
|
||||
auto add_combo_with_button = [&](const char *caption, int32_t &index, int32_t first, int32_t last, std::vector<std::string> names) {
|
||||
ImGui::PushID(++item_id);
|
||||
if (drawer.button("Next"))
|
||||
{
|
||||
index = (index + 1) % (last - first + 1);
|
||||
update_uniform_buffers();
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::SameLine();
|
||||
if (drawer.combo_box(caption, &index, names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
};
|
||||
|
||||
add_color_edit("Background", clear_color);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
FacePreferences ¤t_face = face_preferences[i];
|
||||
if (drawer.header(current_face_index == 0 ? "First face" : "Second face"))
|
||||
{
|
||||
add_color_edit("Top left", current_face.color[0]);
|
||||
add_color_edit("Top right", current_face.color[1]);
|
||||
add_color_edit("Bottom left", current_face.color[2]);
|
||||
add_color_edit("Bottom right", current_face.color[3]);
|
||||
|
||||
ImGui::PushID(++item_id);
|
||||
if (drawer.button("Random"))
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
randomize_color(current_face.color[i]);
|
||||
}
|
||||
update_color();
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
if (eds_feature_support.extendedDynamicState3ColorWriteMask)
|
||||
{
|
||||
drawer.text("Color write mask");
|
||||
add_color_mask_checkbox("Red", current_face.color_bit_enabled[0]);
|
||||
add_color_mask_checkbox("Green", current_face.color_bit_enabled[1]);
|
||||
add_color_mask_checkbox("Blue", current_face.color_bit_enabled[2]);
|
||||
add_color_mask_checkbox("Alpha", current_face.color_bit_enabled[3], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (drawer.header("Blending"))
|
||||
{
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendEnable)
|
||||
{
|
||||
if (drawer.checkbox("Enabled", &blend_enable))
|
||||
{
|
||||
update_color_uniform();
|
||||
}
|
||||
}
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendAdvanced)
|
||||
{
|
||||
if (drawer.radio_button("BlendEquationEXT", ¤t_blend_option, 0))
|
||||
{
|
||||
update_pipeline();
|
||||
update_color_uniform();
|
||||
}
|
||||
if (drawer.radio_button("BlendAdvancedEXT", ¤t_blend_option, 1))
|
||||
{
|
||||
update_pipeline();
|
||||
update_color_uniform();
|
||||
}
|
||||
}
|
||||
switch (current_blend_option)
|
||||
{
|
||||
case 0:
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendEquation)
|
||||
{
|
||||
if (drawer.header("BlendEquationEXT"))
|
||||
{
|
||||
add_combo_with_button("Color operator", current_blend_color_operator_index, VK_BLEND_OP_ADD, VK_BLEND_OP_MAX, blend_operator.names);
|
||||
add_combo_with_button("SrcColorBlendFactor", current_src_color_blend_factor, VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, blend_factor_names);
|
||||
add_combo_with_button("DstColorBlendFactor", current_dst_color_blend_factor, VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, blend_factor_names);
|
||||
|
||||
add_combo_with_button("Alpha operator", current_blend_alpha_operator_index, VK_BLEND_OP_ADD, VK_BLEND_OP_MAX, blend_operator.names);
|
||||
add_combo_with_button("SrcAlphaBlendFactor", current_src_alpha_blend_factor, VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, blend_factor_names);
|
||||
add_combo_with_button("DstAlphaBlendFactor", current_dst_alpha_blend_factor, VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, blend_factor_names);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (eds_feature_support.extendedDynamicState3ColorBlendAdvanced)
|
||||
{
|
||||
if (drawer.header("BlendAdvancedEXT"))
|
||||
{
|
||||
add_combo_with_button("Operator", current_advanced_blend_operator_index, VK_BLEND_OP_ZERO_EXT, VK_BLEND_OP_BLUE_EXT, advanced_blend_operator.names);
|
||||
if (drawer.checkbox("Src premultiplied", &src_premultiplied))
|
||||
{
|
||||
update_color_uniform();
|
||||
}
|
||||
if (drawer.checkbox("Dst premultiplied", &dst_premultiplied))
|
||||
{
|
||||
update_color_uniform();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicBlending::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicBlending::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
VK_CHECK(vkQueueSubmit(queue, 1u, &submit_info, VK_NULL_HANDLE));
|
||||
ApiVulkanSample::submit_frame();
|
||||
|
||||
VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||
}
|
||||
|
||||
bool DynamicBlending::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_blending()
|
||||
{
|
||||
return std::make_unique<DynamicBlending>();
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, Mobica
|
||||
*
|
||||
* 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"
|
||||
|
||||
class DynamicBlending : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
DynamicBlending();
|
||||
~DynamicBlending();
|
||||
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
void prepare_scene();
|
||||
void setup_descriptor_pool();
|
||||
void create_descriptor_set_layout();
|
||||
void create_descriptor_set();
|
||||
void create_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void update_color_uniform();
|
||||
void draw();
|
||||
void initialize_operator_names();
|
||||
void update_pipeline();
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT blend_properties;
|
||||
|
||||
bool reverse = false;
|
||||
|
||||
VkCommandBuffer copy_cmd;
|
||||
|
||||
struct Texture
|
||||
{
|
||||
VkImage image;
|
||||
VkDeviceMemory memory;
|
||||
VkImageView view;
|
||||
VkSampler sampler;
|
||||
} texture;
|
||||
|
||||
struct FacePreferences
|
||||
{
|
||||
uint16_t index_offset;
|
||||
uint16_t index_count;
|
||||
std::array<bool, 4> color_bit_enabled;
|
||||
std::array<std::array<float, 4>, 4> color;
|
||||
} face_preferences[2];
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
std::array<float, 3> pos;
|
||||
std::array<float, 2> uv;
|
||||
};
|
||||
|
||||
struct CameraUbo
|
||||
{
|
||||
alignas(16) glm::mat4 projection;
|
||||
alignas(16) glm::mat4 view;
|
||||
alignas(16) glm::mat4 model;
|
||||
};
|
||||
|
||||
struct ColorUbo
|
||||
{
|
||||
alignas(32) glm::vec4 data[8];
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> common;
|
||||
} uniform_buffers;
|
||||
|
||||
struct
|
||||
{
|
||||
std::vector<VkBlendOp> values;
|
||||
std::vector<std::string> names;
|
||||
} blend_operator, advanced_blend_operator;
|
||||
|
||||
std::vector<std::string> blend_factor_names;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer;
|
||||
uint32_t index_count = 0;
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
uint32_t vertex_buffer_size;
|
||||
uint8_t current_face_index = 1;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> camera_ubo;
|
||||
std::array<bool, 8> color_bit;
|
||||
|
||||
ColorUbo color;
|
||||
std::unique_ptr<vkb::core::BufferC> color_ubo;
|
||||
|
||||
VkDescriptorPool descriptor_pool;
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
VkPipelineLayout pipeline_layout;
|
||||
VkDescriptorSet descriptor_set;
|
||||
VkPipeline pipeline;
|
||||
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT eds_feature_support;
|
||||
|
||||
std::array<float, 4> clear_color = {0.5f, 0.5f, 0.5f, 1.0f};
|
||||
int32_t current_blend_color_operator_index;
|
||||
int32_t current_blend_alpha_operator_index;
|
||||
int32_t current_advanced_blend_operator_index;
|
||||
int32_t current_blend_option = 0;
|
||||
int32_t current_src_color_blend_factor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
int32_t current_dst_color_blend_factor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
int32_t current_src_alpha_blend_factor = VK_BLEND_FACTOR_ZERO;
|
||||
int32_t current_dst_alpha_blend_factor = VK_BLEND_FACTOR_ONE;
|
||||
|
||||
bool blend_enable = true;
|
||||
bool src_premultiplied = true;
|
||||
bool dst_premultiplied = true;
|
||||
bool clamp_results = true;
|
||||
|
||||
std::default_random_engine rnd_engine;
|
||||
std::uniform_real_distribution<float> rnd_dist;
|
||||
|
||||
void build_command_buffer_for_plane(VkCommandBuffer &command_buffer, FacePreferences preferences);
|
||||
void update_color();
|
||||
void randomize_color(std::array<float, 4> &color, bool alpha = false);
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_blending();
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2023-2024, Mobica Limited
|
||||
#
|
||||
# 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 "Khronos"
|
||||
NAME "Dynamic line rasterization"
|
||||
DESCRIPTION "Dynamic line rasterization options available in the VK_EXT_line_rasterization and VK_EXT_extended_dynamic_state3 extensions"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_line_rasterization/glsl/base.vert"
|
||||
"dynamic_line_rasterization/glsl/base.frag"
|
||||
"dynamic_line_rasterization/glsl/grid.vert"
|
||||
"dynamic_line_rasterization/glsl/grid.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"dynamic_line_rasterization/hlsl/base.vert.hlsl"
|
||||
"dynamic_line_rasterization/hlsl/base.frag.hlsl"
|
||||
"dynamic_line_rasterization/hlsl/grid.vert.hlsl"
|
||||
"dynamic_line_rasterization/hlsl/grid.frag.hlsl")
|
||||
@@ -1,68 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023-2024, Mobica Limited
|
||||
-
|
||||
- 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 line rasterization
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/dynamic_line_rasterization[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
image::./screenshot.png[]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates functions from various extensions related to dynamic line rasterization. These functions can be useful for developing CAD applications.
|
||||
|
||||
* From the `VK_EXT_line_rasterization` extension:
|
||||
** `vkCmdSetLineStippleEXT` - sets the stipple pattern.
|
||||
* From the `VK_EXT_extended_dynamic_state3` extension:
|
||||
** `vkCmdSetPolygonModeEXT` - sets how defined primitives should be rasterized.
|
||||
** `vkCmdSetLineRasterizationModeEXT` - sets the algorithm for line rasterization.
|
||||
** `vkCmdSetLineStippleEnableEXT` - toggles stippling for lines.
|
||||
* And also from the core Vulkan:
|
||||
** `vkCmdSetLineWidth` - sets the line width.
|
||||
** `vkCmdSetPrimitiveTopologyEXT` - defines which type of primitives is being drawn.
|
||||
|
||||
== The sample
|
||||
|
||||
Dynamic line rasterization contains a wireframed cube whose appearance can be modified by the user. The cube edges and filling are rendered in a single pipeline, using a different set of indices. The `vkCmdSetPrimitiveTopologyEXT` and `vkCmdSetPolygonModeEXT` functions are used to change the way they are rendered.
|
||||
|
||||
Users can modify the line width (`vkCmdSetLineWidth`) and choose how the line is drawn (`vkCmdSetLineRasterizationModeEXT`). The sample also demonstrates the ability to stipple the line. Stippling is defined by two variables:
|
||||
|
||||
** `lineStipplePattern` - a `uint16_t` where each bit represents whether a point on the line is colored (1) or transparent (0).
|
||||
** `lineStippleFactor` - a factor used to determine how many consecutive points are affected by a single pattern bit.
|
||||
|
||||
The sample also contains a grid rendered beneath the cube using a different pipeline. This grid represents another approach to line rasterization based on the fragment shader. Consequently, the appearance of the gridlines cannot be modified by the user.
|
||||
|
||||
== Credits
|
||||
|
||||
The infinite grid shader is based on the code from the https://asliceofrendering.com/scene%20helper/2020/01/05/InfiniteGrid/[asliceofrendering.com] blog.
|
||||
|
||||
== Documentation links
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetLineStippleEXT.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetPolygonModeEXT.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetLineRasterizationModeEXT.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetLineStippleEnableEXT.html
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetLineWidth.html
|
||||
@@ -1,546 +0,0 @@
|
||||
/* Copyright (c) 2023-2025, Mobica Limited
|
||||
*
|
||||
* 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 "dynamic_line_rasterization.h"
|
||||
|
||||
DynamicLineRasterization::DynamicLineRasterization()
|
||||
{
|
||||
add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
DynamicLineRasterization::~DynamicLineRasterization()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.object, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.grid, nullptr);
|
||||
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicLineRasterization::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.0f, 1.0f, -5.0f});
|
||||
camera.set_rotation({-15.0f, 15.0f, 0.0f});
|
||||
camera.set_perspective(45.0f, static_cast<float>(width) / static_cast<float>(height), 128.0f, 0.1f);
|
||||
|
||||
prepare_uniform_buffers();
|
||||
prepare_scene();
|
||||
setup_descriptor_pool();
|
||||
create_descriptor_set_layout();
|
||||
create_descriptor_set();
|
||||
create_pipelines();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::prepare_scene()
|
||||
{
|
||||
std::vector<glm::vec3> vertices = {
|
||||
{-1.0f, -1.0f, 1.0f},
|
||||
{1.0f, -1.0f, 1.0f},
|
||||
{1.0f, 1.0f, 1.0f},
|
||||
{-1.0f, 1.0f, 1.0f},
|
||||
|
||||
{-1.0f, -1.0f, -1.0f},
|
||||
{1.0f, -1.0f, -1.0f},
|
||||
{1.0f, 1.0f, -1.0f},
|
||||
{-1.0f, 1.0f, -1.0f}};
|
||||
|
||||
std::vector<uint32_t> cube_indices = {
|
||||
0, 1, 2,
|
||||
2, 3, 0,
|
||||
|
||||
4, 5, 6,
|
||||
6, 7, 4,
|
||||
|
||||
0, 3, 7,
|
||||
7, 4, 0,
|
||||
|
||||
1, 5, 6,
|
||||
6, 2, 1,
|
||||
|
||||
3, 2, 6,
|
||||
6, 7, 3,
|
||||
|
||||
0, 4, 5,
|
||||
5, 1, 0};
|
||||
|
||||
// Indices of the edges of the cube
|
||||
std::vector<uint32_t> edges_indices = {
|
||||
0, 1,
|
||||
1, 2,
|
||||
2, 3,
|
||||
3, 0,
|
||||
|
||||
4, 5,
|
||||
5, 6,
|
||||
6, 7,
|
||||
7, 4,
|
||||
|
||||
0, 4,
|
||||
1, 5,
|
||||
2, 6,
|
||||
3, 7};
|
||||
|
||||
cube_index_count = static_cast<uint32_t>(cube_indices.size());
|
||||
edges_index_count = static_cast<uint32_t>(edges_indices.size());
|
||||
uint32_t vertex_buffer_size = static_cast<uint32_t>(vertices.size() * sizeof(glm::vec3));
|
||||
uint32_t cube_index_buffer_size = static_cast<uint32_t>(cube_indices.size() * sizeof(uint32_t));
|
||||
uint32_t edges_index_buffer_size = static_cast<uint32_t>(edges_indices.size() * sizeof(uint32_t));
|
||||
|
||||
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);
|
||||
|
||||
cube_index_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
cube_index_buffer_size,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
cube_index_buffer->update(cube_indices.data(), cube_index_buffer_size);
|
||||
|
||||
edges_index_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
edges_index_buffer_size,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
edges_index_buffer->update(edges_indices.data(), edges_index_buffer_size);
|
||||
|
||||
fill_color = glm::vec4(0.957f, 0.384f, 0.024f, 0.1f);
|
||||
edge_color = glm::vec4(0.957f, 0.384f, 0.024f, 1.0f);
|
||||
|
||||
// Fill the first half of the stipple array with 'true' values for the initial stipple pattern.
|
||||
std::fill(gui_settings.stipple_pattern_arr.begin(), gui_settings.stipple_pattern_arr.begin() + 8, true);
|
||||
gui_settings.stipple_pattern = array_to_uint16(gui_settings.stipple_pattern_arr);
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2u),
|
||||
};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
|
||||
vkb::initializers::descriptor_pool_create_info(
|
||||
static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
static_cast<uint32_t>(pool_sizes.size()));
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::create_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(
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
VK_TRUE);
|
||||
|
||||
blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
|
||||
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_NEVER);
|
||||
|
||||
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_PRIMITIVE_TOPOLOGY,
|
||||
VK_DYNAMIC_STATE_POLYGON_MODE_EXT,
|
||||
VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT,
|
||||
VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_LINE_STIPPLE_EXT,
|
||||
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);
|
||||
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),
|
||||
};
|
||||
|
||||
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, 2> shader_stages{};
|
||||
shader_stages[0] = load_shader("dynamic_line_rasterization", "base.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("dynamic_line_rasterization", "base.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
VkGraphicsPipelineCreateInfo graphics_create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
graphics_create.pNext = VK_NULL_HANDLE;
|
||||
graphics_create.renderPass = render_pass;
|
||||
graphics_create.pInputAssemblyState = &input_assembly_state;
|
||||
graphics_create.pRasterizationState = &rasterization_state;
|
||||
graphics_create.pColorBlendState = &color_blend_state;
|
||||
graphics_create.pMultisampleState = &multisample_state;
|
||||
graphics_create.pViewportState = &viewport_state;
|
||||
graphics_create.pDepthStencilState = &depth_stencil_state;
|
||||
graphics_create.pDynamicState = &dynamic_state;
|
||||
graphics_create.pVertexInputState = &vertex_input_state;
|
||||
graphics_create.pTessellationState = VK_NULL_HANDLE;
|
||||
graphics_create.stageCount = 2;
|
||||
graphics_create.pStages = shader_stages.data();
|
||||
graphics_create.layout = pipeline_layout;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(),
|
||||
pipeline_cache,
|
||||
1,
|
||||
&graphics_create,
|
||||
VK_NULL_HANDLE,
|
||||
&pipelines.object));
|
||||
|
||||
shader_stages[0] = load_shader("dynamic_line_rasterization", "grid.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("dynamic_line_rasterization", "grid.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
graphics_create.pStages = shader_stages.data();
|
||||
vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
graphics_create.pVertexInputState = &vertex_input_state;
|
||||
|
||||
vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipelines.grid);
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::prepare_uniform_buffers()
|
||||
{
|
||||
camera_ubo = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(CameraUbo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::update_uniform_buffers()
|
||||
{
|
||||
CameraUbo cam;
|
||||
cam.model = glm::mat4(1.0f);
|
||||
cam.model = glm::translate(cam.model, glm::vec3(0.0f));
|
||||
cam.view = camera.matrices.view;
|
||||
cam.projection = camera.matrices.perspective;
|
||||
cam.viewProjectionInverse = glm::inverse(cam.projection * cam.view);
|
||||
|
||||
camera_ubo->convert_and_update(cam);
|
||||
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::create_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layout, 1u);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*camera_ubo);
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0u, &buffer_descriptor),
|
||||
};
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0u, nullptr);
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::create_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, 0u),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 1u)};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(set_layout_bindings);
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_set_layout_create_info, nullptr, &descriptor_set_layout));
|
||||
|
||||
VkPushConstantRange push_constant_range =
|
||||
vkb::initializers::push_constant_range(VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(glm::vec4), 0);
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layout);
|
||||
|
||||
pipeline_layout_create_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_create_info.pPushConstantRanges = &push_constant_range;
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::draw()
|
||||
{
|
||||
ApiVulkanSample::prepare_frame();
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
VK_CHECK(vkQueueSubmit(queue, 1u, &submit_info, VK_NULL_HANDLE));
|
||||
ApiVulkanSample::submit_frame();
|
||||
|
||||
VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
draw();
|
||||
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
std::array<VkClearValue, 2> clear_values;
|
||||
clear_values[0].color = {{0.05f, 0.05f, 0.05f, 1.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0u};
|
||||
|
||||
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 = static_cast<uint32_t>(clear_values.size());
|
||||
render_pass_begin_info.pClearValues = clear_values.data();
|
||||
|
||||
for (uint32_t i = 0u; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
auto &cmd_buff = draw_cmd_buffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(cmd_buff, &command_buffer_begin_info));
|
||||
|
||||
vkCmdBeginRenderPass(cmd_buff, &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(cmd_buff, 0u, 1u, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(cmd_buff, 0u, 1u, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(cmd_buff, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0u, 1u, &descriptor_set, 0u, nullptr);
|
||||
|
||||
// While dynamic parameterization is not utilized for the grid, it should be called before the first draw command to prevent validation layer warnings.
|
||||
vkCmdSetLineRasterizationModeEXT(cmd_buff, static_cast<VkLineRasterizationModeEXT>(gui_settings.selected_rasterization_mode));
|
||||
vkCmdSetLineWidth(cmd_buff, gui_settings.line_width);
|
||||
vkCmdSetLineStippleEnableEXT(cmd_buff, static_cast<VkBool32>(gui_settings.stipple_enabled));
|
||||
vkCmdSetLineStippleEXT(cmd_buff, gui_settings.stipple_factor, gui_settings.stipple_pattern);
|
||||
vkCmdSetPrimitiveTopologyEXT(cmd_buff, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
vkCmdSetPolygonModeEXT(cmd_buff, VK_POLYGON_MODE_FILL);
|
||||
|
||||
// Draw the grid
|
||||
if (gui_settings.grid_enabled)
|
||||
{
|
||||
vkCmdBindPipeline(cmd_buff, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.grid);
|
||||
vkCmdDraw(cmd_buff, 6, 1, 0, 0);
|
||||
}
|
||||
|
||||
vkCmdBindPipeline(cmd_buff, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.object);
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(cmd_buff, 0, 1, vertex_buffer->get(), offsets);
|
||||
|
||||
// Fill the cube
|
||||
if (gui_settings.fill_enabled)
|
||||
{
|
||||
vkCmdBindIndexBuffer(cmd_buff, cube_index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdPushConstants(cmd_buff, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(glm::vec4), &fill_color);
|
||||
vkCmdSetPrimitiveTopologyEXT(cmd_buff, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
vkCmdSetPolygonModeEXT(draw_cmd_buffers[i], VK_POLYGON_MODE_FILL);
|
||||
|
||||
vkCmdDrawIndexed(cmd_buff, cube_index_count, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Draw the cube edges
|
||||
vkCmdBindIndexBuffer(cmd_buff, edges_index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdPushConstants(cmd_buff, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(glm::vec4), &edge_color);
|
||||
vkCmdSetPrimitiveTopologyEXT(cmd_buff, VK_PRIMITIVE_TOPOLOGY_LINE_LIST);
|
||||
vkCmdSetPolygonModeEXT(cmd_buff, VK_POLYGON_MODE_LINE);
|
||||
|
||||
vkCmdDrawIndexed(cmd_buff, edges_index_count, 1, 0, 0, 0);
|
||||
|
||||
draw_ui(cmd_buff);
|
||||
|
||||
vkCmdEndRenderPass(cmd_buff);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(cmd_buff));
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceLineRasterizationFeaturesEXT, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT, smoothLines);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceLineRasterizationFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT,
|
||||
stippledSmoothLines);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceLineRasterizationFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT,
|
||||
bresenhamLines);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceLineRasterizationFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT,
|
||||
stippledBresenhamLines);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceLineRasterizationFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT,
|
||||
rectangularLines);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceLineRasterizationFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT,
|
||||
stippledRectangularLines);
|
||||
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT,
|
||||
extendedDynamicState);
|
||||
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3PolygonMode);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3LineRasterizationMode);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3LineStippleEnable);
|
||||
|
||||
{
|
||||
auto &features = gpu.get_mutable_requested_features();
|
||||
features.fillModeNonSolid = VK_TRUE;
|
||||
features.wideLines = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicLineRasterization::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
auto build_command_buffers_when = [this](bool drawer_action) {
|
||||
if (drawer_action)
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
};
|
||||
|
||||
auto uint16_to_hex_string = [](const char *caption, uint16_t value) {
|
||||
std::stringstream stream;
|
||||
stream << caption << std::hex << value;
|
||||
return stream.str();
|
||||
};
|
||||
|
||||
if (drawer.header("Primitive options"))
|
||||
{
|
||||
build_command_buffers_when(drawer.checkbox("Fill", &gui_settings.fill_enabled));
|
||||
build_command_buffers_when(drawer.checkbox("Grid", &gui_settings.grid_enabled));
|
||||
build_command_buffers_when(drawer.combo_box("Rasterization mode", &gui_settings.selected_rasterization_mode, gui_settings.rasterization_mode_names));
|
||||
build_command_buffers_when(drawer.slider_float("Line width", &gui_settings.line_width, 1.0f, 64.0f));
|
||||
build_command_buffers_when(drawer.checkbox("Stipple enabled", &gui_settings.stipple_enabled));
|
||||
// The stipple factor has a maximum value of 256. Here, a limit of 64 has been chosen to achieve a scroll step equal to 1.
|
||||
build_command_buffers_when(drawer.slider_int("Stipple factor", &gui_settings.stipple_factor, 1, 64));
|
||||
drawer.text(uint16_to_hex_string("Stipple pattern: ", gui_settings.stipple_pattern).c_str());
|
||||
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
ImGui::PushID(i);
|
||||
if (drawer.checkbox("", &(gui_settings.stipple_pattern_arr[i])))
|
||||
{
|
||||
gui_settings.stipple_pattern = array_to_uint16(gui_settings.stipple_pattern_arr);
|
||||
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
ImGui::PopID();
|
||||
if (i % 8 != 7)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t DynamicLineRasterization::array_to_uint16(const std::array<bool, 16> &array)
|
||||
{
|
||||
uint16_t result = 0;
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
if (array[i])
|
||||
{
|
||||
result |= (1 << i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DynamicLineRasterization::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ApiVulkanSample::resize(width, height);
|
||||
update_uniform_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_line_rasterization()
|
||||
{
|
||||
return std::make_unique<DynamicLineRasterization>();
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, Mobica Limited
|
||||
*
|
||||
* 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"
|
||||
|
||||
class DynamicLineRasterization : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
DynamicLineRasterization();
|
||||
virtual ~DynamicLineRasterization();
|
||||
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
bool resize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
private:
|
||||
struct CameraUbo
|
||||
{
|
||||
alignas(16) glm::mat4 projection;
|
||||
alignas(16) glm::mat4 view;
|
||||
alignas(16) glm::mat4 model;
|
||||
alignas(16) glm::mat4 viewProjectionInverse;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipeline grid;
|
||||
VkPipeline object;
|
||||
} pipelines;
|
||||
|
||||
VkDescriptorSet descriptor_set;
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
VkPipelineLayout pipeline_layout;
|
||||
VkDescriptorPool descriptor_pool;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> camera_ubo;
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
|
||||
std::unique_ptr<vkb::core::BufferC> cube_index_buffer, edges_index_buffer;
|
||||
|
||||
glm::vec4 fill_color, edge_color;
|
||||
|
||||
uint32_t cube_index_count, edges_index_count;
|
||||
|
||||
struct
|
||||
{
|
||||
bool fill_enabled = true;
|
||||
bool grid_enabled = true;
|
||||
|
||||
int selected_rasterization_mode = 0;
|
||||
std::vector<std::string> rasterization_mode_names = {"DEFAULT", "RECT", "BRESENHAM", "SMOOTH"};
|
||||
|
||||
bool stipple_enabled = true;
|
||||
float line_width = 1.0f;
|
||||
|
||||
int32_t stipple_factor = 1;
|
||||
uint16_t stipple_pattern;
|
||||
|
||||
std::array<bool, 16> stipple_pattern_arr = {};
|
||||
} gui_settings;
|
||||
|
||||
void draw();
|
||||
void prepare_scene();
|
||||
void create_pipelines();
|
||||
void create_descriptor_set();
|
||||
void create_descriptor_set_layout();
|
||||
void setup_descriptor_pool();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
|
||||
static uint16_t array_to_uint16(const std::array<bool, 16> &array);
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_line_rasterization();
|
||||
|
Before Width: | Height: | Size: 105 KiB |
@@ -1,30 +0,0 @@
|
||||
# Copyright (c) 2024, Mobica Limited
|
||||
#
|
||||
# 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 "Mobica"
|
||||
NAME "Dynamic Multisample Rasterization"
|
||||
DESCRIPTION "Demonstrate how to use dynamic multisample rasterization (MSAA) from VK_EXT_extended_dynamic_state3 extension"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_multisample_rasterization/model.vert"
|
||||
"dynamic_multisample_rasterization/model.frag")
|
||||
@@ -1,44 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2024, Mobica Limited
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
// Extended dynamic_state3: Rasterization samples
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates one of the functionalities of VK_EXT_extended_dynamic_state3 related to rasterization samples.
|
||||
The extension can be used to dynamically change sampling without need to swap pipelines.
|
||||
image:./image/image.png[]
|
||||
|
||||
== Enabling the extension
|
||||
|
||||
To be able to use this extension in Vulkan API:
|
||||
`VK_EXT_extended_dynamic_state3` depends on `VK_KHR_get_physical_device_properties2`, which is promoted to Vulkan 1.1. That is, to use this extension, `VK_EXT_extended_dynamic_state3` and either `VK_KHR_get_physical_device_properties2` or Vulkan 1.1 are required.
|
||||
Additionally this sample uses `VK_KHR_dynamic_rendering` which required Vulkan 1.2.
|
||||
|
||||
== Using the extension
|
||||
|
||||
To use the extension:
|
||||
1) `VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT` must be added to `VkPipelineDynamicStateCreateInfo`.
|
||||
2) Method `void vkCmdSetRasterizationSamplesEXT(VkCommandBuffer commandBuffer, VkSampleCountFlagBits rasterizationSamples)` should be called with the active command buffer.
|
||||
|
||||
== Resources
|
||||
|
||||
https://registry.khronos.org/vulkan/specs/latest/man/html/VK_EXT_extended_dynamic_state3.html
|
||||
https://registry.khronos.org/vulkan/specs/latest/man/html/vkCmdSetRasterizationSamplesEXT.html
|
||||
@@ -1,929 +0,0 @@
|
||||
/* Copyright (c) 2024-2025, Mobica Limited
|
||||
*
|
||||
* 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 "dynamic_multisample_rasterization.h"
|
||||
|
||||
#include "gltf_loader.h"
|
||||
#include "scene_graph/components/material.h"
|
||||
#include "scene_graph/components/mesh.h"
|
||||
#include "scene_graph/components/pbr_material.h"
|
||||
#include "scene_graph/components/sub_mesh.h"
|
||||
|
||||
DynamicMultisampleRasterization::DynamicMultisampleRasterization()
|
||||
{
|
||||
title = "DynamicState3 Multisample Rasterization";
|
||||
|
||||
set_api_version(VK_API_VERSION_1_2);
|
||||
add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
DynamicMultisampleRasterization::~DynamicMultisampleRasterization()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline_opaque, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline_opaque_flipped, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline_transparent, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline_transparent_flipped, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline_gui, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout_gui, nullptr);
|
||||
vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool_gui, VK_NULL_HANDLE);
|
||||
destroy_image_data(depth_stencil);
|
||||
destroy_image_data(color_attachment);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3RasterizationSamples);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceDynamicRenderingFeaturesKHR,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR,
|
||||
dynamicRendering);
|
||||
}
|
||||
|
||||
const std::string to_string(VkSampleCountFlagBits count)
|
||||
{
|
||||
switch (count)
|
||||
{
|
||||
case VK_SAMPLE_COUNT_1_BIT:
|
||||
return "No MSAA";
|
||||
case VK_SAMPLE_COUNT_2_BIT:
|
||||
return "2X MSAA";
|
||||
case VK_SAMPLE_COUNT_4_BIT:
|
||||
return "4X MSAA";
|
||||
case VK_SAMPLE_COUNT_8_BIT:
|
||||
return "8X MSAA";
|
||||
case VK_SAMPLE_COUNT_16_BIT:
|
||||
return "16X MSAA";
|
||||
case VK_SAMPLE_COUNT_32_BIT:
|
||||
return "32X MSAA";
|
||||
case VK_SAMPLE_COUNT_64_BIT:
|
||||
return "64X MSAA";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::prepare_supported_sample_count_list()
|
||||
{
|
||||
if (sample_count_prepared)
|
||||
return;
|
||||
|
||||
VkPhysicalDeviceProperties gpu_properties;
|
||||
vkGetPhysicalDeviceProperties(get_device().get_gpu().get_handle(), &gpu_properties);
|
||||
|
||||
VkSampleCountFlags supported_by_depth_and_color = gpu_properties.limits.framebufferColorSampleCounts & gpu_properties.limits.framebufferDepthSampleCounts;
|
||||
|
||||
// All possible sample counts are listed here from most to least preferred as default
|
||||
// On Mali GPUs 4X MSAA is recommended as best performance/quality trade-off
|
||||
std::vector<VkSampleCountFlagBits> counts = {VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_8_BIT,
|
||||
VK_SAMPLE_COUNT_16_BIT, VK_SAMPLE_COUNT_32_BIT, VK_SAMPLE_COUNT_64_BIT,
|
||||
VK_SAMPLE_COUNT_1_BIT};
|
||||
|
||||
std::copy_if(counts.begin(),
|
||||
counts.end(),
|
||||
std::back_inserter(supported_sample_count_list),
|
||||
[&supported_by_depth_and_color](auto count) { return supported_by_depth_and_color & count; });
|
||||
std::transform(supported_sample_count_list.begin(),
|
||||
supported_sample_count_list.end(),
|
||||
std::back_inserter(gui_settings.sample_counts),
|
||||
[](auto count) { return to_string(count); });
|
||||
if (!supported_sample_count_list.empty())
|
||||
{
|
||||
sample_count = supported_sample_count_list.front();
|
||||
}
|
||||
|
||||
sample_count_prepared = true;
|
||||
}
|
||||
|
||||
bool DynamicMultisampleRasterization::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position(glm::vec3(1.9f, 10.f, -18.f));
|
||||
camera.set_rotation(glm::vec3(0.f, -40.f, 0.f));
|
||||
camera.rotation_speed = 0.1f;
|
||||
|
||||
// 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();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_sets();
|
||||
|
||||
update_resources();
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::draw_node(VkCommandBuffer &draw_cmd_buffer, SceneNode &node)
|
||||
{
|
||||
assert(node.sub_mesh->vertex_buffers.count("position") == 1);
|
||||
assert(node.sub_mesh->vertex_buffers.count("normal") == 1);
|
||||
assert(node.sub_mesh->vertex_buffers.count("texcoord_0") == 1);
|
||||
|
||||
const auto &vertex_buffer_pos = node.sub_mesh->vertex_buffers.at("position");
|
||||
const auto &vertex_buffer_normal = node.sub_mesh->vertex_buffers.at("normal");
|
||||
const auto &vertex_buffer_uv = node.sub_mesh->vertex_buffers.at("texcoord_0");
|
||||
auto &index_buffer = node.sub_mesh->index_buffer;
|
||||
|
||||
// Pass data for the current node via push commands
|
||||
auto node_material = dynamic_cast<const vkb::sg::PBRMaterial *>(node.sub_mesh->get_material());
|
||||
assert(node_material);
|
||||
|
||||
push_const_block.model_matrix = node.node->get_transform().get_world_matrix();
|
||||
|
||||
push_const_block.base_color_factor = node_material->base_color_factor;
|
||||
push_const_block.metallic_factor = node_material->metallic_factor;
|
||||
push_const_block.roughness_factor = node_material->roughness_factor;
|
||||
push_const_block.base_texture_index = -1;
|
||||
push_const_block.normal_texture_index = -1;
|
||||
push_const_block.pbr_texture_index = -1;
|
||||
|
||||
auto base_color_texture = node_material->textures.find("base_color_texture");
|
||||
|
||||
if (base_color_texture != node_material->textures.end())
|
||||
{
|
||||
push_const_block.base_texture_index = name_to_texture_id.at(base_color_texture->second->get_name());
|
||||
}
|
||||
|
||||
auto normal_texture = node_material->textures.find("normal_texture");
|
||||
|
||||
if (normal_texture != node_material->textures.end())
|
||||
{
|
||||
push_const_block.normal_texture_index = name_to_texture_id.at(normal_texture->second->get_name());
|
||||
}
|
||||
|
||||
auto metallic_roughness_texture = node_material->textures.find("metallic_roughness_texture");
|
||||
|
||||
if (metallic_roughness_texture != node_material->textures.end())
|
||||
{
|
||||
push_const_block.pbr_texture_index = name_to_texture_id.at(metallic_roughness_texture->second->get_name());
|
||||
}
|
||||
|
||||
vkCmdPushConstants(draw_cmd_buffer, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_const_block), &push_const_block);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffer, 0, 1, vertex_buffer_pos.get(), offsets);
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffer, 1, 1, vertex_buffer_normal.get(), offsets);
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffer, 2, 1, vertex_buffer_uv.get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffer, index_buffer->get_handle(), 0, node.sub_mesh->index_type);
|
||||
|
||||
vkCmdDraw(draw_cmd_buffer, node.sub_mesh->vertex_indices, 1, 0, 0);
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
std::vector<VkClearValue> clear_values(2);
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
std::vector<VkRenderingAttachmentInfoKHR> attachments(2);
|
||||
attachments_setup(attachments, clear_values);
|
||||
|
||||
VkImageSubresourceRange range{};
|
||||
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
range.baseMipLevel = 0;
|
||||
range.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
range.baseArrayLayer = 0;
|
||||
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
|
||||
|
||||
VkImageSubresourceRange depth_range{range};
|
||||
depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
|
||||
{
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
if (sample_count != VK_SAMPLE_COUNT_1_BIT)
|
||||
{
|
||||
attachments[0].resolveImageView = swapchain_buffers[i].view;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachments[0].imageView = swapchain_buffers[i].view;
|
||||
}
|
||||
auto render_area = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}};
|
||||
auto render_info = vkb::initializers::rendering_info(render_area, 1, &attachments[0]);
|
||||
render_info.layerCount = 1;
|
||||
render_info.pDepthAttachment = &attachments[1];
|
||||
|
||||
vkb::image_layout_transition(draw_cmd_buffers[i],
|
||||
swapchain_buffers[i].image,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
0,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
range);
|
||||
|
||||
vkb::image_layout_transition(draw_cmd_buffers[i],
|
||||
depth_stencil.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
|
||||
depth_range);
|
||||
|
||||
vkCmdBeginRenderingKHR(draw_cmd_buffers[i], &render_info);
|
||||
vkCmdSetRasterizationSamplesEXT(draw_cmd_buffers[i], sample_count); // VK_EXT_extended_dynamic_state3
|
||||
|
||||
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, nullptr);
|
||||
|
||||
if (!scene_nodes_opaque.empty())
|
||||
{
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_opaque);
|
||||
for (auto &node : scene_nodes_opaque)
|
||||
{
|
||||
draw_node(draw_cmd_buffers[i], node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!scene_nodes_opaque_flipped.empty())
|
||||
{
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_opaque_flipped);
|
||||
for (auto &node : scene_nodes_opaque_flipped)
|
||||
{
|
||||
draw_node(draw_cmd_buffers[i], node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!scene_nodes_transparent.empty())
|
||||
{
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_transparent);
|
||||
for (auto &node : scene_nodes_transparent)
|
||||
{
|
||||
draw_node(draw_cmd_buffers[i], node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!scene_nodes_transparent_flipped.empty())
|
||||
{
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_transparent_flipped);
|
||||
for (auto &node : scene_nodes_transparent_flipped)
|
||||
{
|
||||
draw_node(draw_cmd_buffers[i], node);
|
||||
}
|
||||
}
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderingKHR(draw_cmd_buffers[i]);
|
||||
|
||||
vkb::image_layout_transition(draw_cmd_buffers[i],
|
||||
swapchain_buffers[i].image,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
range);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::draw_ui(VkCommandBuffer &cmd_buffer)
|
||||
{
|
||||
if (has_gui())
|
||||
{
|
||||
get_gui().draw(cmd_buffer, pipeline_gui, pipeline_layout_gui, descriptor_set_gui);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::load_assets()
|
||||
{
|
||||
vkb::GLTFLoader loader{get_device()};
|
||||
scene = loader.read_scene_from_file("scenes/space_module/SpaceModule.gltf");
|
||||
assert(scene);
|
||||
// Store all scene nodes in separate vectors for easier rendering
|
||||
for (auto &mesh : scene->get_components<vkb::sg::Mesh>())
|
||||
{
|
||||
for (auto &node : mesh->get_nodes())
|
||||
{
|
||||
for (auto &sub_mesh : mesh->get_submeshes())
|
||||
{
|
||||
auto &scale = node->get_transform().get_scale();
|
||||
|
||||
bool flipped = scale.x * scale.y * scale.z < 0;
|
||||
bool transparent = sub_mesh->get_material()->alpha_mode == vkb::sg::AlphaMode::Blend;
|
||||
|
||||
if (transparent) // transparent material
|
||||
{
|
||||
if (flipped)
|
||||
scene_nodes_transparent_flipped.push_back({node, sub_mesh});
|
||||
else
|
||||
scene_nodes_transparent.push_back({node, sub_mesh});
|
||||
}
|
||||
else // opaque material
|
||||
{
|
||||
if (flipped)
|
||||
scene_nodes_opaque_flipped.push_back({node, sub_mesh});
|
||||
else
|
||||
scene_nodes_opaque.push_back({node, sub_mesh});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto textures = scene->get_components<vkb::sg::Texture>();
|
||||
|
||||
for (auto texture : textures)
|
||||
{
|
||||
const auto &name = texture->get_name();
|
||||
auto image = texture->get_image();
|
||||
VkDescriptorImageInfo imageInfo;
|
||||
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
imageInfo.imageView = image->get_vk_image_view().get_handle();
|
||||
imageInfo.sampler = texture->get_sampler()->vk_sampler.get_handle();
|
||||
|
||||
image_infos.push_back(imageInfo);
|
||||
name_to_texture_id.emplace(name, static_cast<int32_t>(image_infos.size()) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::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, static_cast<uint32_t>(image_infos.size()))};
|
||||
|
||||
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 DynamicMultisampleRasterization::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_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1, static_cast<uint32_t>(image_infos.size())),
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
// Pass scene node information via push constants
|
||||
VkPushConstantRange push_constant_range = vkb::initializers::push_constant_range(VK_SHADER_STAGE_VERTEX_BIT, sizeof(push_const_block), 0);
|
||||
pipeline_layout_create_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_create_info.pPushConstantRanges = &push_constant_range;
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::setup_descriptor_sets()
|
||||
{
|
||||
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 matrix_buffer_descriptor = create_descriptor(*uniform_buffer);
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_buffer_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, image_infos.data(), image_infos.size())};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::attachments_setup(std::vector<VkRenderingAttachmentInfoKHR> &attachments, std::vector<VkClearValue> &clear_values)
|
||||
{
|
||||
prepare_supported_sample_count_list();
|
||||
destroy_image_data(color_attachment);
|
||||
destroy_image_data(depth_stencil);
|
||||
setup_color_attachment();
|
||||
setup_depth_stencil();
|
||||
|
||||
attachments[0].sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
attachments[0].imageView = color_attachment.view;
|
||||
attachments[0].imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].clearValue = clear_values[0];
|
||||
attachments[0].pNext = VK_NULL_HANDLE;
|
||||
attachments[0].resolveImageView = VK_NULL_HANDLE;
|
||||
|
||||
if (sample_count != VK_SAMPLE_COUNT_1_BIT)
|
||||
{
|
||||
attachments[0].resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachments[0].resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachments[0].resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].resolveMode = VK_RESOLVE_MODE_NONE;
|
||||
}
|
||||
|
||||
// Depth attachment
|
||||
attachments[1].sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
attachments[1].imageView = depth_stencil.view;
|
||||
attachments[1].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].clearValue = clear_values[1];
|
||||
attachments[1].pNext = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Create attachment that will be used in a dynamic rendering.
|
||||
void DynamicMultisampleRasterization::setup_color_attachment()
|
||||
{
|
||||
VkImageCreateInfo image_create_info{};
|
||||
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = get_render_context().get_format();
|
||||
image_create_info.extent = {get_render_context().get_surface_extent().width, get_render_context().get_surface_extent().height, 1};
|
||||
image_create_info.mipLevels = 1;
|
||||
image_create_info.arrayLayers = 1;
|
||||
image_create_info.samples = sample_count;
|
||||
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_create_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image_create_info, nullptr, &color_attachment.image));
|
||||
VkMemoryRequirements memReqs{};
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), color_attachment.image, &memReqs);
|
||||
|
||||
VkMemoryAllocateInfo memory_allocation{};
|
||||
memory_allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
memory_allocation.allocationSize = memReqs.size;
|
||||
memory_allocation.memoryTypeIndex = get_device().get_gpu().get_memory_type(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocation, nullptr, &color_attachment.mem));
|
||||
VK_CHECK(vkBindImageMemory(get_device().get_handle(), color_attachment.image, color_attachment.mem, 0));
|
||||
|
||||
VkImageViewCreateInfo image_view_create_info{};
|
||||
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
image_view_create_info.image = color_attachment.image;
|
||||
image_view_create_info.format = get_render_context().get_format();
|
||||
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.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
// Stencil aspect should only be set on depth + stencil formats (VK_FORMAT_D16_UNORM_S8_UINT..VK_FORMAT_D32_SFLOAT_S8_UINT
|
||||
if (depth_format >= VK_FORMAT_D16_UNORM_S8_UINT)
|
||||
{
|
||||
image_view_create_info.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view_create_info, nullptr, &color_attachment.view));
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::setup_depth_stencil()
|
||||
{
|
||||
VkImageCreateInfo image_create_info{};
|
||||
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = depth_format;
|
||||
image_create_info.extent = {get_render_context().get_surface_extent().width, get_render_context().get_surface_extent().height, 1};
|
||||
image_create_info.mipLevels = 1;
|
||||
image_create_info.arrayLayers = 1;
|
||||
image_create_info.samples = sample_count;
|
||||
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_create_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image_create_info, nullptr, &depth_stencil.image));
|
||||
VkMemoryRequirements memReqs{};
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), depth_stencil.image, &memReqs);
|
||||
|
||||
VkMemoryAllocateInfo memory_allocation{};
|
||||
memory_allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
memory_allocation.allocationSize = memReqs.size;
|
||||
memory_allocation.memoryTypeIndex = get_device().get_gpu().get_memory_type(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocation, nullptr, &depth_stencil.mem));
|
||||
VK_CHECK(vkBindImageMemory(get_device().get_handle(), depth_stencil.image, depth_stencil.mem, 0));
|
||||
|
||||
VkImageViewCreateInfo image_view_create_info{};
|
||||
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
image_view_create_info.image = depth_stencil.image;
|
||||
image_view_create_info.format = depth_format;
|
||||
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.subresourceRange.aspectMask = 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 (depth_format >= VK_FORMAT_D16_UNORM_S8_UINT)
|
||||
{
|
||||
image_view_create_info.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view_create_info, nullptr, &depth_stencil.view));
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::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(
|
||||
VK_COLOR_COMPONENT_R_BIT |
|
||||
VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT |
|
||||
VK_COLOR_COMPONENT_A_BIT,
|
||||
VK_FALSE);
|
||||
|
||||
blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
|
||||
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, // disable multisampling during configuration
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT /* VK_EXT_extended_dynamic_state3 */
|
||||
};
|
||||
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_layout,
|
||||
VK_NULL_HANDLE,
|
||||
0);
|
||||
|
||||
// Create graphics pipeline for dynamic rendering
|
||||
VkFormat color_rendering_format = get_render_context().get_format();
|
||||
|
||||
// Provide information for dynamic rendering
|
||||
VkPipelineRenderingCreateInfoKHR pipeline_rendering_create_info{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
|
||||
pipeline_rendering_create_info.pNext = VK_NULL_HANDLE;
|
||||
pipeline_rendering_create_info.colorAttachmentCount = 1;
|
||||
pipeline_rendering_create_info.pColorAttachmentFormats = &color_rendering_format;
|
||||
pipeline_rendering_create_info.depthAttachmentFormat = depth_format;
|
||||
|
||||
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.layout = pipeline_layout;
|
||||
pipeline_create_info.pNext = &pipeline_rendering_create_info;
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
// Vertex bindings an attributes for model rendering
|
||||
// Binding description, we use separate buffers for the vertex attributes
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
vkb::initializers::vertex_input_binding_description(1, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
vkb::initializers::vertex_input_binding_description(2, sizeof(glm::vec2), 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(1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0), // Normal
|
||||
vkb::initializers::vertex_input_attribute_description(2, 2, VK_FORMAT_R32G32_SFLOAT, 0), // TexCoord
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
shader_stages[0] = load_shader("dynamic_multisample_rasterization/model.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("dynamic_multisample_rasterization/model.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Add a pipeline for the opaque counterclockwise faces
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline_opaque));
|
||||
|
||||
// Add a pipeline for the opaque clockwise faces
|
||||
rasterization_state.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline_opaque_flipped));
|
||||
|
||||
// Add a pipeline for the transparent clockwise faces
|
||||
blend_attachment_state.blendEnable = VK_TRUE;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline_transparent_flipped));
|
||||
|
||||
// Add a pipeline for the transparent counterclockwise faces
|
||||
rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline_transparent));
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::prepare_gui_pipeline()
|
||||
{
|
||||
// Descriptor pool
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)};
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo = vkb::initializers::descriptor_pool_create_info(pool_sizes, 2);
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptorPoolInfo, nullptr, &descriptor_pool_gui));
|
||||
|
||||
// Descriptor set layout
|
||||
std::vector<VkDescriptorSetLayoutBinding> layout_bindings_gui = {
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||
};
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = vkb::initializers::descriptor_set_layout_create_info(layout_bindings_gui);
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_set_layout_create_info, nullptr, &descriptor_set_layout_gui));
|
||||
|
||||
// Descriptor set
|
||||
VkDescriptorSetAllocateInfo descriptor_allocation = vkb::initializers::descriptor_set_allocate_info(descriptor_pool_gui, &descriptor_set_layout_gui, 1);
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_allocation, &descriptor_set_gui));
|
||||
VkDescriptorImageInfo font_descriptor = vkb::initializers::descriptor_image_info(
|
||||
get_gui().get_sampler(),
|
||||
get_gui().get_font_image_view(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set_gui, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &font_descriptor)};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
|
||||
// Setup graphics pipeline for UI rendering
|
||||
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);
|
||||
|
||||
// Enable blending
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state{};
|
||||
blend_attachment_state.blendEnable = VK_TRUE;
|
||||
blend_attachment_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
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_MINUS_SRC_ALPHA;
|
||||
blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
|
||||
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);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT /* VK_EXT_extended_dynamic_state3 */
|
||||
};
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(dynamic_state_enables);
|
||||
|
||||
std::vector<vkb::ShaderModule *> shader_modules;
|
||||
|
||||
vkb::ShaderSource vert_shader("uioverlay/uioverlay.vert.spv");
|
||||
vkb::ShaderSource frag_shader("uioverlay/uioverlay.frag.spv");
|
||||
|
||||
shader_modules.push_back(&get_device().get_resource_cache().request_shader_module(VK_SHADER_STAGE_VERTEX_BIT, vert_shader, {}));
|
||||
shader_modules.push_back(&get_device().get_resource_cache().request_shader_module(VK_SHADER_STAGE_FRAGMENT_BIT, frag_shader, {}));
|
||||
|
||||
pipeline_layout_gui = get_device().get_resource_cache().request_pipeline_layout(shader_modules).get_handle();
|
||||
|
||||
// Create graphics pipeline for dynamic rendering
|
||||
VkFormat color_rendering_format = get_render_context().get_format();
|
||||
|
||||
// Provide information for dynamic rendering
|
||||
VkPipelineRenderingCreateInfoKHR pipeline_rendering_create_info{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
|
||||
pipeline_rendering_create_info.pNext = VK_NULL_HANDLE;
|
||||
pipeline_rendering_create_info.colorAttachmentCount = 1;
|
||||
pipeline_rendering_create_info.pColorAttachmentFormats = &color_rendering_format;
|
||||
pipeline_rendering_create_info.depthAttachmentFormat = depth_format;
|
||||
if (!vkb::is_depth_only_format(depth_format))
|
||||
{
|
||||
pipeline_rendering_create_info.stencilAttachmentFormat = depth_format;
|
||||
}
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info = vkb::initializers::pipeline_create_info(pipeline_layout_gui, VK_NULL_HANDLE);
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
|
||||
|
||||
shader_stages[0] = load_shader(vert_shader.get_filename(), VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader(frag_shader.get_filename(), VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
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();
|
||||
pipeline_create_info.pNext = &pipeline_rendering_create_info;
|
||||
|
||||
// Vertex bindings an attributes based on ImGui vertex definition
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, pos)), // Location 0: Position
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, uv)), // Location 1: UV
|
||||
vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R8G8B8A8_UNORM, offsetof(ImDrawVert, col)), // Location 0: Color
|
||||
};
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state_create_info = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state_create_info.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input_state_create_info.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state_create_info.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input_state_create_info.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state_create_info;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline_gui));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms
|
||||
void DynamicMultisampleRasterization::prepare_uniform_buffers()
|
||||
{
|
||||
// Matrices vertex shader uniform buffer
|
||||
uniform_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(uniform_data),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::prepare_gui()
|
||||
{
|
||||
create_gui(*window, /*stats=*/nullptr, 15.0f, true);
|
||||
|
||||
prepare_gui_pipeline();
|
||||
|
||||
// No need to call gui->prepare because the pipeline has been created above
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::update_uniform_buffers()
|
||||
{
|
||||
uniform_data.projection = camera.matrices.perspective;
|
||||
// Scale the view matrix as the model is pretty large, and also flip it upside down
|
||||
uniform_data.view = glm::scale(camera.matrices.view, glm::vec3(0.1f, -0.1f, 0.1f));
|
||||
uniform_buffer->convert_and_update(uniform_data);
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::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();
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::destroy_image_data(ImageData &image_data)
|
||||
{
|
||||
if (image_data.image != nullptr)
|
||||
{
|
||||
vkDestroyImageView(get_device().get_handle(), image_data.view, nullptr);
|
||||
vkDestroyImage(get_device().get_handle(), image_data.image, nullptr);
|
||||
vkFreeMemory(get_device().get_handle(), image_data.mem, nullptr);
|
||||
image_data.view = VK_NULL_HANDLE;
|
||||
image_data.image = VK_NULL_HANDLE;
|
||||
image_data.mem = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::update_resources()
|
||||
{
|
||||
prepared = false;
|
||||
|
||||
if (has_device())
|
||||
{
|
||||
get_device().wait_idle();
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
void DynamicMultisampleRasterization::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.combo_box("antialiasing", &gui_settings.sample_count_index, gui_settings.sample_counts))
|
||||
{
|
||||
sample_count = supported_sample_count_list[gui_settings.sample_count_index];
|
||||
|
||||
update_resources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicMultisampleRasterization::resize(const uint32_t _width, const uint32_t _height)
|
||||
{
|
||||
sample_count = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
if (!ApiVulkanSample::resize(_width, _height))
|
||||
return false;
|
||||
|
||||
sample_count = supported_sample_count_list[gui_settings.sample_count_index];
|
||||
|
||||
prepared = false;
|
||||
|
||||
update_resources();
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSample<vkb::BindingType::C>> create_dynamic_multisample_rasterization()
|
||||
{
|
||||
return std::make_unique<DynamicMultisampleRasterization>();
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/* Copyright (c) 2024, Mobica Limited
|
||||
*
|
||||
* 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"
|
||||
|
||||
class DynamicMultisampleRasterization : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
DynamicMultisampleRasterization();
|
||||
virtual ~DynamicMultisampleRasterization();
|
||||
|
||||
private:
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer = nullptr;
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer = nullptr;
|
||||
|
||||
std::unique_ptr<vkb::sg::Scene> scene;
|
||||
std::vector<VkDescriptorImageInfo> image_infos;
|
||||
std::map<std::string, int32_t> name_to_texture_id;
|
||||
|
||||
struct SceneNode
|
||||
{
|
||||
vkb::sg::Node *node;
|
||||
vkb::sg::SubMesh *sub_mesh;
|
||||
};
|
||||
std::vector<SceneNode> scene_nodes_opaque;
|
||||
std::vector<SceneNode> scene_nodes_opaque_flipped;
|
||||
std::vector<SceneNode> scene_nodes_transparent;
|
||||
std::vector<SceneNode> scene_nodes_transparent_flipped;
|
||||
|
||||
struct UniformData
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
} uniform_data;
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer;
|
||||
|
||||
VkPipeline pipeline_opaque{VK_NULL_HANDLE};
|
||||
VkPipeline pipeline_opaque_flipped{VK_NULL_HANDLE};
|
||||
VkPipeline pipeline_transparent{VK_NULL_HANDLE};
|
||||
VkPipeline pipeline_transparent_flipped{VK_NULL_HANDLE};
|
||||
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
|
||||
VkDescriptorSet descriptor_set{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE};
|
||||
|
||||
// GUI
|
||||
VkPipeline pipeline_gui{VK_NULL_HANDLE};
|
||||
VkPipelineLayout pipeline_layout_gui{VK_NULL_HANDLE};
|
||||
VkDescriptorSet descriptor_set_gui{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout descriptor_set_layout_gui{VK_NULL_HANDLE};
|
||||
VkDescriptorPool descriptor_pool_gui{VK_NULL_HANDLE};
|
||||
|
||||
ImageData color_attachment;
|
||||
|
||||
/**
|
||||
* @brief List of MSAA levels supported by the platform
|
||||
*/
|
||||
std::vector<VkSampleCountFlagBits> supported_sample_count_list{};
|
||||
|
||||
/**
|
||||
* @brief Enables MSAA if set to more than 1 sample per pixel
|
||||
* (e.g. sample count 4 enables 4X MSAA)
|
||||
*/
|
||||
VkSampleCountFlagBits sample_count{VK_SAMPLE_COUNT_1_BIT};
|
||||
|
||||
bool sample_count_prepared = false;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::mat4 model_matrix;
|
||||
|
||||
glm::vec4 base_color_factor;
|
||||
float metallic_factor;
|
||||
float roughness_factor;
|
||||
|
||||
int32_t base_texture_index;
|
||||
int32_t normal_texture_index;
|
||||
int32_t pbr_texture_index;
|
||||
|
||||
} push_const_block;
|
||||
|
||||
struct GUI_settings
|
||||
{
|
||||
int sample_count_index = 0;
|
||||
std::vector<std::string> sample_counts;
|
||||
} gui_settings;
|
||||
|
||||
public:
|
||||
virtual void build_command_buffers() override;
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
virtual 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;
|
||||
virtual void setup_depth_stencil() override;
|
||||
virtual void prepare_gui() override;
|
||||
void load_assets();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_sets();
|
||||
void prepare_pipelines();
|
||||
void prepare_gui_pipeline();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void draw();
|
||||
void prepare_supported_sample_count_list();
|
||||
void setup_color_attachment();
|
||||
void draw_ui(VkCommandBuffer &);
|
||||
void update_resources();
|
||||
void draw_node(VkCommandBuffer &, SceneNode &);
|
||||
void destroy_image_data(ImageData &image_data);
|
||||
void attachments_setup(std::vector<VkRenderingAttachmentInfoKHR> &attachments, std::vector<VkClearValue> &clear_values);
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSample<vkb::BindingType::C>> create_dynamic_multisample_rasterization();
|
||||
|
Before Width: | Height: | Size: 515 KiB |
@@ -1,33 +0,0 @@
|
||||
# Copyright (c) 2024, Mobica Limited
|
||||
#
|
||||
# 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 "Mobica"
|
||||
NAME "Dynamic primitive clipping"
|
||||
DESCRIPTION "Rendering using primitive clipping through VK_EXT_extended_dynamic_state3 extension"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_primitive_clipping/glsl/primitive_clipping.vert"
|
||||
"dynamic_primitive_clipping/glsl/primitive_clipping.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"dynamic_primitive_clipping/hlsl/primitive_clipping.vert.hlsl"
|
||||
"dynamic_primitive_clipping/hlsl/primitive_clipping.frag.hlsl")
|
||||
@@ -1,122 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2024, Mobica Limited
|
||||
-
|
||||
- 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 depth clipping and primitive clipping
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/dynamic_primitive_clipping[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
image::./screenshot.png[]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates how to apply *depth clipping* using `vkCmdSetDepthClipEnableEXT()` command which is a part of `VK_EXT_extended_dynamic_state3` extension.
|
||||
|
||||
Additionally it also shows how to apply *primitive clipping* using `gl_ClipDistance[]` builtin shader variable.
|
||||
|
||||
It is worth noting that *primitive clipping* and *depth clipping* are two separate features of the fixed-function vertex post-processing stage.
|
||||
They're both described in the same chapter of Vulkan specification ( chapter 27.4, "Primitive clipping" ).
|
||||
|
||||
== What is primitive clipping
|
||||
|
||||
Primitives produced by vertex/geometry/tesellation shaders are sent to fixed-function vertex post-processing.
|
||||
Primitive clipping is a part of post-processing pipeline in which primitives such as points/lines/triangles are culled against cull volume and then clipped to clip volume.
|
||||
And then they might be further clipped by results stored in `gl_ClipDistance[]` array - values in this array must be calculated in a vertex/geometry/tesellation shader.
|
||||
|
||||
In the past, fixed-function version of OpenGL API provided a method to specify parameters for up to 6 clipping planes ( half-spaces ) that could perform additional primitive clipping. Fixed-function hardware calculated proper distances to these planes and made a decision - should the primitive be clipped against these planes or not ( for historical study - search for the `glClipPlane()` description ).
|
||||
|
||||
Vulkan inherited the idea of primitive clipping, but with one important difference: user has to calculate the distance to the clip planes on its own in the vertex shader.
|
||||
And - because user does it in a shader - he does not have to use clip planes at all. It can be any kind of calculation, as long as the results are put in `gl_ClipDistance[]` array.
|
||||
|
||||
Values that are less than 0.0 cause the vertex to be be clipped. In case of triangle primitive the whole triangle is clipped if all of its vertices have values stored in `gl_ClipDistance[]` below 0.0. When some of these values are above 0.0 - triangle is split into new triangles as described in Vulkan specification.
|
||||
|
||||
== What is depth clipping
|
||||
|
||||
When depth clipping is disabled then effectively, there is no near or far plane clipping.
|
||||
|
||||
- depth values of primitives that are behind far plane are clamped to far plane depth value ( usually 1.0 )
|
||||
|
||||
- depth values of primitives that are in front of near plane are clamped to near plane depth value ( by default it's 0.0, but may be set to -1.0 if we use settings defined in `VkPipelineViewportDepthClipControlCreateInfoEXT` structure. This requires a presence of `VK_EXT_depth_clip_control` extension which is not part of this tutorial )
|
||||
|
||||
In this sample the result of depth clipping ( or lack of it ) is not clearly visible at first. Try to move viewer position closer to the object and see how "use depth clipping" checkbox changes object appearance.
|
||||
|
||||
== How to apply primitive clipping in Vulkan
|
||||
|
||||
Primitive clipping is relatively easy to configure in Vulkan API:
|
||||
|
||||
- `VkPhysicalDeviceFeatures::shaderClipDistance` must be set to VK_TRUE - in order to use `gl_ClipDistance[]` builtin variable in shaders
|
||||
|
||||
- `gl_ClipDistance[]` must be added to a definition of `gl_PerVertex` structure in a vertex shader. Simplest form with one value per vertex will look like this:
|
||||
|
||||
[,glsl]
|
||||
----
|
||||
out gl_PerVertex
|
||||
{
|
||||
vec4 gl_Position;
|
||||
float gl_ClipDistance[1];
|
||||
};
|
||||
----
|
||||
|
||||
The size of `gl_ClipDistance[]` array may not be larger than `VkPhysicalDeviceLimits::maxClipDistances`.
|
||||
|
||||
In Vulkan API there is no external command that turns the primitive clipping off. If you want to have such feature - you have to implement it yourself by providing some additional variable to the vertex shader ( see shaders/dynamic_primitive_clipping/primitive_clipping.vert on how it can be implemented ).
|
||||
|
||||
== How to apply depth clipping in Vulkan
|
||||
|
||||
There are few ways of applying depth clipping in Vulkan API:
|
||||
|
||||
- statically: when `VkPipelineRasterizationDepthClipStateCreateInfoEXT` IS NOT present and `VkPipelineRasterizationStateCreateInfo::depthClampEnable` is equal to VK_FALSE
|
||||
|
||||
- statically: when `VkPipelineRasterizationDepthClipStateCreateInfoEXT` is present in `VkGraphicsPipelineCreateInfo::pNext` chain and `VkPipelineRasterizationDepthClipStateCreateInfoEXT::depthClipEnable` is set to `VK_TRUE` ( requires extension `VK_EXT_depth_clip_enable` )
|
||||
|
||||
- using shader objects with `vkCmdSetDepthClipEnableEXT()` called before `vkCmdDraw*(cmd, ... )` command ( requires extensions: `VK_EXT_shader_object`, `VK_EXT_depth_clip_enable` )
|
||||
|
||||
- dynamically: when `VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT` is present in `VkPipelineDynamicStateCreateInfo::pDynamicStates` and command `vkCmdSetDepthClipEnableEXT()` is called before `vkCmdDraw*(cmd, ... )` command ( requires extensions: `VK_EXT_extended_dynamic_state3`, `VK_EXT_depth_clip_enable` )
|
||||
|
||||
This sample focuses on the last, dynamic case.
|
||||
|
||||
In order to use the dynamic depth clipping we need to:
|
||||
|
||||
- create `VkInstance` with extension `VK_KHR_get_physical_device_properties2`
|
||||
|
||||
- create `VkDevice` with extensions `VK_EXT_extended_dynamic_state3` and `VK_EXT_depth_clip_enable`
|
||||
|
||||
- `VkPhysicalDeviceDepthClipEnableFeaturesEXT::depthClipEnable` must be set to VK_TRUE
|
||||
|
||||
- `VkPhysicalDeviceExtendedDynamicState3FeaturesEXT::extendedDynamicState3DepthClipEnable` must be set to VK_TRUE - in order to use `vkCmdSetDepthClipEnableEXT()` command
|
||||
|
||||
- during graphics pipeline creation `VkPipelineDynamicStateCreateInfo::pDynamicStates` must contain `VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT`
|
||||
|
||||
- command `vkCmdSetDepthClipEnableEXT()` is called before `vkCmdDraw*(cmd, ... )` command
|
||||
|
||||
== Potential applications
|
||||
|
||||
In the past primitive clipping was used in various CAD applications to make cross-sections of different 3D objects.
|
||||
We still can use it in similar fashion, but other applications also come to mind:
|
||||
|
||||
- we can hide parts of the 3D model
|
||||
|
||||
- we can make holes in a terrain ( e.g. for tunnels )
|
||||
|
||||
- we can use it in some special effects
|
||||
|
||||
Advantage of using primitive clipping over using `discard` keyword in a fragment shader is obvious: we are doing it earlier in a pipeline which may result in better performance ( or may not, you have to measure it ). But beware of vertex density: because this technique is vertex based it may have some nasty results when vertices are too sparse. See "Torusknot" object type with "Clip space Y" visualization in a sample to see where the problem may arise.
|
||||
|
||||
Depth clipping ( or rather lack of it ) is widely used in different shadow mapping algorithms, where only rendering to depth buffer is performed. In other use cases, which include rendering to color buffer you must be aware that when a lot of primitives are clamped to near/far plane we will see the ones that were rendered first rather that the ones that are closer to the viewer. Example of such behavior is visible on the left part of Utah teapot on a screenshot above.
|
||||
@@ -1,393 +0,0 @@
|
||||
/* Copyright (c) 2024-2025, Mobica Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Rendering using primitive clipping configured by dynamic pipeline state
|
||||
*/
|
||||
|
||||
#include "dynamic_primitive_clipping.h"
|
||||
|
||||
DynamicPrimitiveClipping::DynamicPrimitiveClipping()
|
||||
{
|
||||
title = "Dynamic primitive clipping";
|
||||
set_api_version(VK_API_VERSION_1_1);
|
||||
|
||||
// Extensions required by vkCmdSetDepthClipEnableEXT().
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
DynamicPrimitiveClipping::~DynamicPrimitiveClipping()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), sample_pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.models, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.models, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicPrimitiveClipping::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup camera position.
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position(glm::vec3(0.0f, 0.0f, -50.0f));
|
||||
camera.set_rotation(glm::vec3(0.0f, 180.0f, 0.0f));
|
||||
|
||||
// Near plane is set far away from observer position in order to show depth clipping better.
|
||||
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 30.f, 256.0f);
|
||||
|
||||
// Load assets from file.
|
||||
load_assets();
|
||||
|
||||
// Setup parameters used on CPU.
|
||||
visualization_names = {"World space X", "World space Y", "Half-space in world space coordinates", "Half-space in clip space coordinates", "Clip space X", "Clip space Y", "Euclidean distance to center", "Manhattan distance to center", "Chebyshev distance to center"};
|
||||
|
||||
// Setup Vulkan objects required by GPU.
|
||||
prepare_uniform_buffers();
|
||||
setup_layouts();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_sets();
|
||||
build_command_buffers();
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// shaderClipDistance feature is required in order to gl_ClipDistance builtin shader variable to work.
|
||||
if (gpu.get_features().shaderClipDistance)
|
||||
{
|
||||
gpu.get_mutable_requested_features().shaderClipDistance = VK_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw vkb::VulkanException(VK_ERROR_FEATURE_NOT_PRESENT, "Selected GPU does not support gl_ClipDistance builtin shader variable");
|
||||
}
|
||||
|
||||
// Features required by vkCmdSetDepthClipEnableEXT().
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceDepthClipEnableFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT,
|
||||
depthClipEnable);
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
|
||||
extendedDynamicState3DepthClipEnable);
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
// Clear color and depth values.
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = {{0.1f, 0.1f, 0.1f, 0.0f}};
|
||||
clear_values[1].depthStencil = {1.0f, 0};
|
||||
|
||||
// Begin the render pass.
|
||||
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)
|
||||
{
|
||||
auto cmd = draw_cmd_buffers[i];
|
||||
|
||||
// Begin command buffer.
|
||||
vkBeginCommandBuffer(cmd, &command_buffer_begin_info);
|
||||
|
||||
// Set framebuffer for this command buffer.
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
// We will add draw commands in the same command buffer.
|
||||
vkCmdBeginRenderPass(cmd, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
// Bind the graphics pipeline.
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, sample_pipeline);
|
||||
|
||||
// Set viewport dynamically.
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||
|
||||
// Set scissor dynamically.
|
||||
VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0);
|
||||
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||
|
||||
// Enable depth clipping dynamically as defined in GUI.
|
||||
vkCmdSetDepthClipEnableEXT(cmd, params.useDepthClipping);
|
||||
|
||||
// Draw object once using descriptor_positive.
|
||||
if (params.drawObject[0])
|
||||
{
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.models, 0, 1, &descriptor_sets.descriptor_positive, 0, NULL);
|
||||
draw_model(models.objects[models.object_index], cmd);
|
||||
}
|
||||
|
||||
// Draw the same object for the second time, but this time using descriptor_negative.
|
||||
// Skip second rendering if primitive clipping is turned off by user in a GUI.
|
||||
if (params.drawObject[1] && params.usePrimitiveClipping)
|
||||
{
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layouts.models, 0, 1, &descriptor_sets.descriptor_negative, 0, NULL);
|
||||
draw_model(models.objects[models.object_index], cmd);
|
||||
}
|
||||
|
||||
// Draw user interface.
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
// Complete render pass.
|
||||
vkCmdEndRenderPass(cmd);
|
||||
|
||||
// Complete the command buffer.
|
||||
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::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();
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.combo_box("Object type", &models.object_index, model_names))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Use primitive clipping", ¶ms.usePrimitiveClipping))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.combo_box("Visualization", ¶ms.visualization, visualization_names))
|
||||
{
|
||||
}
|
||||
if (drawer.checkbox("Draw object 1", ¶ms.drawObject[0]))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Draw object 2", ¶ms.drawObject[1]))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Use depth clipping", ¶ms.useDepthClipping))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::load_assets()
|
||||
{
|
||||
// Load three different models. User may pick them from GUI.
|
||||
std::vector<std::string> filenames = {"teapot.gltf", "torusknot.gltf", "geosphere.gltf"};
|
||||
model_names = {"Teapot", "Torusknot", "Sphere"};
|
||||
for (auto file : filenames)
|
||||
{
|
||||
auto object = load_model("scenes/" + file);
|
||||
models.objects.emplace_back(std::move(object));
|
||||
}
|
||||
|
||||
// Setup model transformation matrices.
|
||||
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); // teapot matrix
|
||||
models.transforms.push_back(glm::mat4(1.0f)); // torusknot matrix
|
||||
models.transforms.push_back(glm::mat4(1.0f)); // sphere matrix
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::setup_layouts()
|
||||
{
|
||||
// Descriptor set layout contains information about single UBO.
|
||||
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),
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
// Pipeline layout contains above defined descriptor set layout.
|
||||
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));
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::prepare_pipelines()
|
||||
{
|
||||
// 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) * 3u), // Normal
|
||||
vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 5u) // UV
|
||||
};
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
|
||||
vertex_input.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
|
||||
vertex_input.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
// Specify we will use triangle lists to draw geometry.
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly = vkb::initializers::pipeline_input_assembly_state_create_info(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
|
||||
|
||||
// Specify rasterization state.
|
||||
VkPipelineRasterizationStateCreateInfo raster = vkb::initializers::pipeline_rasterization_state_create_info(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
|
||||
// Our attachment will write to all color channels, but no blending is enabled.
|
||||
VkPipelineColorBlendAttachmentState blend_attachment = vkb::initializers::pipeline_color_blend_attachment_state(VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo blend = vkb::initializers::pipeline_color_blend_state_create_info(1, &blend_attachment);
|
||||
|
||||
// We will have one viewport and scissor box.
|
||||
VkPipelineViewportStateCreateInfo viewport = vkb::initializers::pipeline_viewport_state_create_info(1, 1);
|
||||
|
||||
// Enable depth testing.
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil = vkb::initializers::pipeline_depth_stencil_state_create_info(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS);
|
||||
|
||||
// No multisampling.
|
||||
VkPipelineMultisampleStateCreateInfo multisample = vkb::initializers::pipeline_multisample_state_create_info(VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
// Specify that these states will be dynamic, i.e. not part of pipeline state object.
|
||||
// VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT must be specified here in order to use vkCmdSetDepthClipEnableEXT()
|
||||
std::array<VkDynamicState, 3> dynamics{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT};
|
||||
VkPipelineDynamicStateCreateInfo dynamic = vkb::initializers::pipeline_dynamic_state_create_info(dynamics.data(), vkb::to_u32(dynamics.size()));
|
||||
|
||||
// Load shaders.
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
|
||||
shader_stages[0] = load_shader("dynamic_primitive_clipping", "primitive_clipping.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("dynamic_primitive_clipping", "primitive_clipping.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// We need to specify the pipeline layout and the render pass description up front as well.
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info = vkb::initializers::pipeline_create_info(pipeline_layouts.models, render_pass);
|
||||
pipeline_create_info.stageCount = vkb::to_u32(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
pipeline_create_info.pVertexInputState = &vertex_input;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly;
|
||||
pipeline_create_info.pRasterizationState = &raster;
|
||||
pipeline_create_info.pColorBlendState = &blend;
|
||||
pipeline_create_info.pMultisampleState = &multisample;
|
||||
pipeline_create_info.pViewportState = &viewport;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil;
|
||||
pipeline_create_info.pDynamicState = &dynamic;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &sample_pipeline));
|
||||
}
|
||||
|
||||
// Prepare and initialize uniform buffer containing shader uniforms.
|
||||
void DynamicPrimitiveClipping::prepare_uniform_buffers()
|
||||
{
|
||||
// We will render the same object twice using two different sets of parameters called "positive" and "negative".
|
||||
uniform_buffers.buffer_positive = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(UBOVS),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
uniform_buffers.buffer_negative = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(UBOVS),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::update_uniform_buffers()
|
||||
{
|
||||
ubo_positive.projection = camera.matrices.perspective;
|
||||
ubo_positive.view = camera.matrices.view;
|
||||
ubo_positive.model = models.transforms[models.object_index];
|
||||
ubo_positive.colorTransformation = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
ubo_positive.sceneTransformation = glm::ivec2(params.visualization, 1);
|
||||
ubo_positive.usePrimitiveClipping = params.usePrimitiveClipping ? 1.0f : -1.0f;
|
||||
uniform_buffers.buffer_positive->convert_and_update(ubo_positive);
|
||||
|
||||
ubo_negative.projection = camera.matrices.perspective;
|
||||
ubo_negative.view = camera.matrices.view;
|
||||
ubo_negative.model = models.transforms[models.object_index];
|
||||
ubo_negative.colorTransformation = glm::vec4(-1.0f, 1.0f, 0.0f, 0.0f);
|
||||
ubo_negative.sceneTransformation = glm::ivec2(params.visualization, -1);
|
||||
ubo_negative.usePrimitiveClipping = params.usePrimitiveClipping ? 1.0f : -1.0f;
|
||||
uniform_buffers.buffer_negative->convert_and_update(ubo_negative);
|
||||
}
|
||||
|
||||
void DynamicPrimitiveClipping::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2 * 4)};
|
||||
uint32_t num_descriptor_sets = 2 * 2 * 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 DynamicPrimitiveClipping::setup_descriptor_sets()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&descriptor_set_layouts.models,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.descriptor_positive));
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.descriptor_negative));
|
||||
|
||||
std::vector<VkDescriptorBufferInfo> descriptor_buffer_infos = {
|
||||
create_descriptor(*uniform_buffers.buffer_positive),
|
||||
create_descriptor(*uniform_buffers.buffer_negative)};
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.descriptor_positive, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &descriptor_buffer_infos[0]),
|
||||
vkb::initializers::write_descriptor_set(descriptor_sets.descriptor_negative, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &descriptor_buffer_infos[1])};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_primitive_clipping()
|
||||
{
|
||||
return std::make_unique<DynamicPrimitiveClipping>();
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/* Copyright (c) 2024, Mobica Limited
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Rendering using primitive clipping configured by dynamic pipeline state
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class DynamicPrimitiveClipping : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
DynamicPrimitiveClipping();
|
||||
virtual ~DynamicPrimitiveClipping();
|
||||
|
||||
// Override basic framework functionality
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void build_command_buffers() override;
|
||||
void render(float delta_time) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
|
||||
// Auxiliary functions
|
||||
void load_assets();
|
||||
void setup_layouts();
|
||||
void prepare_pipelines();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_sets();
|
||||
|
||||
private:
|
||||
// vector of models rendered by sample
|
||||
struct Models
|
||||
{
|
||||
std::vector<std::unique_ptr<vkb::sg::SubMesh>> objects;
|
||||
std::vector<glm::mat4> transforms;
|
||||
int32_t object_index = 0;
|
||||
} models;
|
||||
|
||||
std::vector<std::string> model_names;
|
||||
std::vector<std::string> visualization_names;
|
||||
|
||||
// sample parameters used on CPU side
|
||||
struct Params
|
||||
{
|
||||
bool usePrimitiveClipping = true;
|
||||
bool drawObject[2] = {true, true};
|
||||
int32_t visualization = 0;
|
||||
bool useDepthClipping = false;
|
||||
} params;
|
||||
|
||||
// parameters sent to shaders through uniform buffers
|
||||
struct UBOVS
|
||||
{
|
||||
alignas(16) glm::mat4 projection;
|
||||
alignas(16) glm::mat4 view;
|
||||
alignas(16) glm::mat4 model;
|
||||
alignas(16) glm::vec4 colorTransformation;
|
||||
alignas(8) glm::ivec2 sceneTransformation;
|
||||
alignas(4) float usePrimitiveClipping;
|
||||
} ubo_positive, ubo_negative;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> buffer_positive;
|
||||
std::unique_ptr<vkb::core::BufferC> buffer_negative;
|
||||
} uniform_buffers;
|
||||
|
||||
// pipeline infrastructure
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout models = VK_NULL_HANDLE;
|
||||
} pipeline_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSetLayout models = VK_NULL_HANDLE;
|
||||
} descriptor_set_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet descriptor_positive = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descriptor_negative = VK_NULL_HANDLE;
|
||||
} descriptor_sets;
|
||||
|
||||
VkPipeline sample_pipeline{};
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_primitive_clipping();
|
||||
|
Before Width: | Height: | Size: 153 KiB |
@@ -1,36 +0,0 @@
|
||||
# Copyright (c) 2020-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.
|
||||
#
|
||||
|
||||
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 "Holochip"
|
||||
NAME "Dynamic Rendering"
|
||||
DESCRIPTION "Demonstrates the dynamic rendering extension to streamline render passes and avoid the requirement of render pass objects"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_rendering/glsl/gbuffer.vert"
|
||||
"dynamic_rendering/glsl/gbuffer.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"dynamic_rendering/hlsl/gbuffer.vert.hlsl"
|
||||
"dynamic_rendering/hlsl/gbuffer.frag.hlsl"
|
||||
SHADER_FILES_SLANG
|
||||
"dynamic_rendering/slang/gbuffer.vert.slang"
|
||||
"dynamic_rendering/slang/gbuffer.frag.slang")
|
||||
@@ -1,158 +0,0 @@
|
||||
////
|
||||
Copyright (c) 2021-2023, Holochip
|
||||
|
||||
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 Rendering
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/dynamic_rendering[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates how to use the `VK_KHR_dynamic_rendering` extension, which eliminates the need to create render passes and improves flexibility while developing render pipelines.
|
||||
|
||||
This extension changes how rendering resources are managed.
|
||||
Rather than using render pass objects, this extension allows the developer to directly reference rendering attachments prior to the start of rendering.
|
||||
|
||||
Below is a comparison of the common Vulkan render pass construction and dynamic rendering.
|
||||
|
||||
|===
|
||||
| Vulkan 1.0 | Dynamic Rendering
|
||||
|
||||
| Rendering begins with `vkCmdBeginRenderPass`
|
||||
| Rendering begins with `vkCmdBeginRenderingKHR`
|
||||
|
||||
| Rendering struct is `VkRenderPassBeginInfo`
|
||||
| Rendering struct is `VkRenderingInfoKHR`
|
||||
|
||||
| Attachments are referenced by `VkFramebuffer`
|
||||
| Attachments are referenced by `VkRenderingAttachmentInfoKHR`
|
||||
|
||||
| `VkFramebuffer` objects are heap-allocated and opaque
|
||||
| `VkRenderingAttachmentInfoKHR` objects are stack-allocated
|
||||
|
||||
| Graphics pipeline creation references a `VkRenderPass`
|
||||
| Graphics pipeline creation references a `VkPipelineRenderingCreateInfoKHR`
|
||||
|
||||
|
|
||||
|
|
||||
|===
|
||||
|
||||
More detail is provided in the sections that follow.
|
||||
|
||||
== Rendering Attachments
|
||||
|
||||
Previously, developers had to create render passes and framebuffers, which would be referenced in `VkRenderPassBeginInfo`.
|
||||
This is illustrated in the non-dynamic version of the command buffer construction sample code:
|
||||
|
||||
[,C++]
|
||||
----
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 3;
|
||||
render_pass_begin_info.pClearValues = clear_values.data();
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
draw_scene();
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffer);
|
||||
----
|
||||
|
||||
However, with dynamic rendering, the render pass and framebuffer structs are replaced by `VkRenderingAttachmentInfoKHR`, which contains information about color, depth, and stencil attachments, and `VkRenderingInfoKHR`, which references the attachments.
|
||||
These structs are used at the start of rendering with the new command `vkCmdBeginRenderingKHR`, as shown in the dynamic version of the command buffer construction sample code:
|
||||
|
||||
[,C++]
|
||||
----
|
||||
VkRenderingAttachmentInfoKHR color_attachment_info = vkb::initializers::rendering_attachment_info();
|
||||
color_attachment_info.imageView = swapchain_buffers[i].view; // color_attachment.image_view;
|
||||
...
|
||||
|
||||
VkRenderingAttachmentInfoKHR depth_attachment_info = vkb::initializers::rendering_attachment_info();
|
||||
depth_attachment_info.imageView = depth_stencil.view;
|
||||
...
|
||||
|
||||
auto render_area = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}};
|
||||
auto render_info = vkb::initializers::rendering_info(render_area, 1, &color_attachment_info);
|
||||
render_info.layerCount = 1;
|
||||
render_info.pDepthAttachment = &depth_attachment_info;
|
||||
render_info.pStencilAttachment = &depth_attachment_info;
|
||||
|
||||
vkCmdBeginRenderingKHR(draw_cmd_buffer, &render_info);
|
||||
draw_scene();
|
||||
vkCmdEndRenderingKHR(draw_cmd_buffer);
|
||||
----
|
||||
|
||||
== Pipelines
|
||||
|
||||
Dynamic rendering changes how graphics pipelines are created.
|
||||
Whereas before, the `VkGraphicsPipelineCreateInfo` struct was required to reference a non-null pointer to a `VkRenderPass` object, the dynamic rendering information is instead contained in a `VkPipelineRenderingCreateInfoKHR` struct referenced by `pNext` of the graphics pipeline create info:
|
||||
|
||||
[,C++]
|
||||
----
|
||||
// Provide information for dynamic rendering
|
||||
VkPipelineRenderingCreateInfoKHR pipeline_create{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
|
||||
pipeline_create.pNext = VK_NULL_HANDLE;
|
||||
pipeline_create.colorAttachmentCount = 1;
|
||||
pipeline_create.pColorAttachmentFormats = &color_rendering_format;
|
||||
pipeline_create.depthAttachmentFormat = depth_format;
|
||||
pipeline_create.stencilAttachmentFormat = depth_format;
|
||||
|
||||
// Use the pNext to point to the rendering create struct
|
||||
VkGraphicsPipelineCreateInfo graphics_create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
graphics_create.pNext = &pipeline_create; // reference the new dynamic structure
|
||||
graphics_create.renderPass = VK_NULL_HANDLE; // previously required non-null
|
||||
----
|
||||
|
||||
During graphics pipeline construction, the `VkPipelineRenderingCreateInfoKHR` structure does not contain pointers to the actual attachment images (as the pointers aren't required until `VkRenderingAttachmentInfoKHR`);
|
||||
instead, only the number and format of the attachments are required.
|
||||
|
||||
== Enabling the Extension
|
||||
|
||||
The dynamic rendering api is provided in Vulkan 1.2.197 and the appropriate headers / SDK is required.
|
||||
|
||||
In addition, since dynamic rendering is provided as an extension and may have varying levels of support, the developer must query availability for each device used.
|
||||
|
||||
The device extension is provided by `VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME`, and additional features are provided by the `VkPhysicalDeviceDynamicRenderingFeaturesKHR` struct:
|
||||
|
||||
[,C++]
|
||||
----
|
||||
typedef struct VkPhysicalDeviceDynamicRenderingFeaturesKHR {
|
||||
VkStructureType sType;
|
||||
void* pNext;
|
||||
VkBool32 dynamicRendering;
|
||||
} VkPhysicalDeviceDynamicRenderingFeaturesKHR;
|
||||
----
|
||||
|
||||
In addition to enabling the extension, developers may need to dynamically query the function pointers for `vkCmdBeginRenderingKHR` and `vkCmdEndRenderingKHR` if the preprocessor macro `VK_NO_PROTOTYPES` is enabled.
|
||||
This can be achieved through `vkGetInstanceProcAddr`:
|
||||
|
||||
[,C++]
|
||||
----
|
||||
VkInstance instance = get_device().get_gpu().get_instance().get_handle();
|
||||
assert(!!instance);
|
||||
vkCmdBeginRenderingKHR = (PFN_vkCmdBeginRenderingKHR) vkGetInstanceProcAddr(instance, "vkCmdBeginRenderingKHR");
|
||||
vkCmdEndRenderingKHR = (PFN_vkCmdEndRenderingKHR) vkGetInstanceProcAddr(instance, "vkCmdEndRenderingKHR");
|
||||
if (!vkCmdBeginRenderingKHR || !vkCmdEndRenderingKHR)
|
||||
{
|
||||
throw std::runtime_error("Unable to dynamically load vkCmdBeginRenderingKHR and vkCmdEndRenderingKHR");
|
||||
}
|
||||
----
|
||||
@@ -1,472 +0,0 @@
|
||||
/*
|
||||
* 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 "dynamic_rendering.h"
|
||||
|
||||
DynamicRendering::DynamicRendering() :
|
||||
enable_dynamic(true)
|
||||
{
|
||||
title = "Dynamic Rendering";
|
||||
|
||||
// Dynamic Rendering is a Vulkan 1.2 extension
|
||||
set_api_version(VK_API_VERSION_1_2);
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
DynamicRendering::~DynamicRendering()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroySampler(get_device().get_handle(), textures.envmap.sampler, VK_NULL_HANDLE);
|
||||
textures = {};
|
||||
skybox.reset();
|
||||
object.reset();
|
||||
ubo.reset();
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), model_pipeline, VK_NULL_HANDLE);
|
||||
vkDestroyPipeline(get_device().get_handle(), skybox_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);
|
||||
vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool, VK_NULL_HANDLE);
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicRendering::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if VK_NO_PROTOTYPES
|
||||
if (enable_dynamic)
|
||||
{
|
||||
VkInstance instance = get_device().get_gpu().get_instance().get_handle();
|
||||
assert(!!instance);
|
||||
vkCmdBeginRenderingKHR = reinterpret_cast<PFN_vkCmdBeginRenderingKHR>(vkGetInstanceProcAddr(instance, "vkCmdBeginRenderingKHR"));
|
||||
vkCmdEndRenderingKHR = reinterpret_cast<PFN_vkCmdEndRenderingKHR>(vkGetInstanceProcAddr(instance, "vkCmdEndRenderingKHR"));
|
||||
if (!vkCmdBeginRenderingKHR || !vkCmdEndRenderingKHR)
|
||||
{
|
||||
throw std::runtime_error("Unable to dynamically load vkCmdBeginRenderingKHR and vkCmdEndRenderingKHR");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.f, 0.f, -4.f});
|
||||
camera.set_rotation({0.f, 180.f, 0.f});
|
||||
camera.set_perspective(60.f, static_cast<float>(width) / static_cast<float>(height), 256.f, 0.1f);
|
||||
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
create_descriptor_pool();
|
||||
setup_descriptor_set_layout();
|
||||
create_descriptor_sets();
|
||||
if (!enable_dynamic)
|
||||
{
|
||||
create_render_pass_non_dynamic();
|
||||
}
|
||||
create_pipeline();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicRendering::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
if (enable_dynamic)
|
||||
{
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceDynamicRenderingFeaturesKHR,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR,
|
||||
dynamicRendering);
|
||||
}
|
||||
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicRendering::load_assets()
|
||||
{
|
||||
// Models
|
||||
skybox = load_model("scenes/cube.gltf");
|
||||
object = load_model("scenes/geosphere.gltf");
|
||||
|
||||
// Load HDR cube map
|
||||
textures.envmap = load_texture_cubemap("textures/uffizi_rgba16f_cube.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
void DynamicRendering::prepare_uniform_buffers()
|
||||
{
|
||||
ubo = 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 DynamicRendering::update_uniform_buffers()
|
||||
{
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.modelview = camera.matrices.view * glm::mat4(1.f);
|
||||
ubo_vs.inverse_modelview = glm::inverse(camera.matrices.view);
|
||||
ubo_vs.skybox_modelview = camera.matrices.view;
|
||||
ubo->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
void DynamicRendering::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),
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
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 DynamicRendering::create_descriptor_sets()
|
||||
{
|
||||
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 matrix_buffer_descriptor = create_descriptor(*ubo);
|
||||
VkDescriptorImageInfo environment_image_descriptor = create_descriptor(textures.envmap);
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_buffer_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &environment_image_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void DynamicRendering::create_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_COMBINED_IMAGE_SAMPLER, 2)};
|
||||
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 DynamicRendering::create_pipeline()
|
||||
{
|
||||
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);
|
||||
|
||||
const auto color_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);
|
||||
color_blend_state.attachmentCount = 1;
|
||||
color_blend_state.pAttachments = &color_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);
|
||||
|
||||
// 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();
|
||||
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
|
||||
shader_stages[0] = load_shader("dynamic_rendering", "gbuffer.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("dynamic_rendering", "gbuffer.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Create graphics pipeline for dynamic rendering
|
||||
VkFormat color_rendering_format = get_render_context().get_format();
|
||||
|
||||
// Provide information for dynamic rendering
|
||||
VkPipelineRenderingCreateInfoKHR pipeline_create{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
|
||||
pipeline_create.pNext = VK_NULL_HANDLE;
|
||||
pipeline_create.colorAttachmentCount = 1;
|
||||
pipeline_create.pColorAttachmentFormats = &color_rendering_format;
|
||||
pipeline_create.depthAttachmentFormat = depth_format;
|
||||
if (!vkb::is_depth_only_format(depth_format))
|
||||
{
|
||||
pipeline_create.stencilAttachmentFormat = depth_format;
|
||||
}
|
||||
|
||||
// Use the pNext to point to the rendering create struct
|
||||
VkGraphicsPipelineCreateInfo graphics_create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
graphics_create.pNext = &pipeline_create;
|
||||
graphics_create.renderPass = VK_NULL_HANDLE;
|
||||
graphics_create.pInputAssemblyState = &input_assembly_state;
|
||||
graphics_create.pRasterizationState = &rasterization_state;
|
||||
graphics_create.pColorBlendState = &color_blend_state;
|
||||
graphics_create.pMultisampleState = &multisample_state;
|
||||
graphics_create.pViewportState = &viewport_state;
|
||||
graphics_create.pDepthStencilState = &depth_stencil_state;
|
||||
graphics_create.pDynamicState = &dynamic_state;
|
||||
graphics_create.pVertexInputState = &vertex_input_state;
|
||||
graphics_create.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
graphics_create.pStages = shader_stages.data();
|
||||
graphics_create.layout = pipeline_layout;
|
||||
|
||||
// Skybox pipeline (background cube)
|
||||
VkSpecializationInfo specialization_info;
|
||||
std::array<VkSpecializationMapEntry, 1> specialization_map_entries{};
|
||||
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;
|
||||
|
||||
if (!enable_dynamic)
|
||||
{
|
||||
graphics_create.pNext = VK_NULL_HANDLE;
|
||||
graphics_create.renderPass = render_pass;
|
||||
}
|
||||
|
||||
vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &graphics_create, VK_NULL_HANDLE, &skybox_pipeline);
|
||||
|
||||
// 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;
|
||||
vkCreateGraphicsPipelines(get_device().get_handle(), VK_NULL_HANDLE, 1, &graphics_create, VK_NULL_HANDLE, &model_pipeline);
|
||||
}
|
||||
|
||||
void DynamicRendering::create_render_pass_non_dynamic()
|
||||
{
|
||||
}
|
||||
|
||||
void DynamicRendering::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();
|
||||
}
|
||||
|
||||
void DynamicRendering::build_command_buffers()
|
||||
{
|
||||
std::array<VkClearValue, 2> clear_values{};
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
int i = -1;
|
||||
for (auto &draw_cmd_buffer : draw_cmd_buffers)
|
||||
{
|
||||
i++;
|
||||
auto command_begin = vkb::initializers::command_buffer_begin_info();
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffer, &command_begin));
|
||||
|
||||
auto draw_scene = [&] {
|
||||
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffer, 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(static_cast<int>(width), static_cast<int>(height), 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffer, 0, 1, &scissor);
|
||||
|
||||
// One descriptor set is used, and the draw type is toggled by a specialization constant
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, nullptr);
|
||||
|
||||
// skybox
|
||||
vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, skybox_pipeline);
|
||||
draw_model(skybox, draw_cmd_buffer);
|
||||
|
||||
// object
|
||||
vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, model_pipeline);
|
||||
draw_model(object, draw_cmd_buffer);
|
||||
|
||||
// Note: This sample does not render a UI, as the framework's UI overlay doesn't handle dynamic rendering
|
||||
};
|
||||
|
||||
VkImageSubresourceRange range{};
|
||||
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
range.baseMipLevel = 0;
|
||||
range.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
range.baseArrayLayer = 0;
|
||||
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
|
||||
|
||||
VkImageSubresourceRange depth_range{range};
|
||||
depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
if (enable_dynamic)
|
||||
{
|
||||
vkb::image_layout_transition(draw_cmd_buffer,
|
||||
swapchain_buffers[i].image,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
0,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
range);
|
||||
|
||||
vkb::image_layout_transition(draw_cmd_buffer,
|
||||
depth_stencil.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
|
||||
depth_range);
|
||||
|
||||
VkRenderingAttachmentInfoKHR color_attachment_info = vkb::initializers::rendering_attachment_info();
|
||||
color_attachment_info.imageView = swapchain_buffers[i].view; // color_attachment.image_view;
|
||||
color_attachment_info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
color_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE;
|
||||
color_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
color_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
color_attachment_info.clearValue = clear_values[0];
|
||||
|
||||
VkRenderingAttachmentInfoKHR depth_attachment_info = vkb::initializers::rendering_attachment_info();
|
||||
depth_attachment_info.imageView = depth_stencil.view;
|
||||
depth_attachment_info.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
|
||||
depth_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE;
|
||||
depth_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
depth_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
depth_attachment_info.clearValue = clear_values[1];
|
||||
|
||||
auto render_area = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}};
|
||||
auto render_info = vkb::initializers::rendering_info(render_area, 1, &color_attachment_info);
|
||||
render_info.layerCount = 1;
|
||||
render_info.pDepthAttachment = &depth_attachment_info;
|
||||
if (!vkb::is_depth_only_format(depth_format))
|
||||
{
|
||||
render_info.pStencilAttachment = &depth_attachment_info;
|
||||
}
|
||||
|
||||
vkCmdBeginRenderingKHR(draw_cmd_buffer, &render_info);
|
||||
draw_scene();
|
||||
vkCmdEndRenderingKHR(draw_cmd_buffer);
|
||||
|
||||
vkb::image_layout_transition(draw_cmd_buffer,
|
||||
swapchain_buffers[i].image,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
range);
|
||||
}
|
||||
else
|
||||
{
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 3;
|
||||
render_pass_begin_info.pClearValues = clear_values.data();
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
draw_scene();
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffer);
|
||||
}
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffer));
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicRendering::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicRendering::view_changed()
|
||||
{
|
||||
}
|
||||
|
||||
void DynamicRendering::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_rendering()
|
||||
{
|
||||
return std::make_unique<DynamicRendering>();
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2024, 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class DynamicRendering : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
DynamicRendering();
|
||||
~DynamicRendering() override;
|
||||
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
void view_changed() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
|
||||
private:
|
||||
void load_assets();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void setup_descriptor_set_layout();
|
||||
void create_descriptor_sets();
|
||||
void create_descriptor_pool();
|
||||
void create_pipeline();
|
||||
void create_render_pass_non_dynamic();
|
||||
void draw();
|
||||
|
||||
struct
|
||||
{
|
||||
Texture envmap;
|
||||
} textures;
|
||||
|
||||
struct UBOVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skybox_modelview;
|
||||
glm::mat4 inverse_modelview;
|
||||
float modelscale = 0.05f;
|
||||
} ubo_vs;
|
||||
|
||||
std::unique_ptr<vkb::sg::SubMesh> skybox;
|
||||
std::unique_ptr<vkb::sg::SubMesh> object;
|
||||
std::unique_ptr<vkb::core::BufferC> ubo;
|
||||
|
||||
VkPipeline model_pipeline{VK_NULL_HANDLE};
|
||||
VkPipeline skybox_pipeline{VK_NULL_HANDLE};
|
||||
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
|
||||
VkDescriptorSet descriptor_set{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE};
|
||||
VkDescriptorPool descriptor_pool{VK_NULL_HANDLE};
|
||||
|
||||
#if VK_NO_PROTOTYPES
|
||||
PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR{VK_NULL_HANDLE};
|
||||
PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR{VK_NULL_HANDLE};
|
||||
#endif
|
||||
bool enable_dynamic;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_dynamic_rendering();
|
||||
@@ -1,48 +0,0 @@
|
||||
# Copyright (c) 2024-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 "Sascha Willems"
|
||||
NAME "Dynamic Rendering local reads"
|
||||
DESCRIPTION "Demonstrates the dynamic rendering local read extension to use input attachments with dynamic rendering"
|
||||
SHADER_FILES_GLSL
|
||||
"dynamic_rendering_local_read/glsl/composition.frag"
|
||||
"dynamic_rendering_local_read/glsl/composition.vert"
|
||||
"dynamic_rendering_local_read/glsl/scene_opaque.vert"
|
||||
"dynamic_rendering_local_read/glsl/scene_opaque.frag"
|
||||
"dynamic_rendering_local_read/glsl/scene_transparent.vert"
|
||||
"dynamic_rendering_local_read/glsl/scene_transparent.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"dynamic_rendering_local_read/hlsl/composition.frag.hlsl"
|
||||
"dynamic_rendering_local_read/hlsl/composition.vert.hlsl"
|
||||
"dynamic_rendering_local_read/hlsl/scene_opaque.vert.hlsl"
|
||||
"dynamic_rendering_local_read/hlsl/scene_opaque.frag.hlsl"
|
||||
"dynamic_rendering_local_read/hlsl/scene_transparent.vert.hlsl"
|
||||
"dynamic_rendering_local_read/hlsl/scene_transparent.frag.hlsl"
|
||||
SHADER_FILES_SLANG
|
||||
"dynamic_rendering_local_read/slang/composition.frag.slang"
|
||||
"dynamic_rendering_local_read/slang/composition.vert.slang"
|
||||
"dynamic_rendering_local_read/slang/scene_opaque.vert.slang"
|
||||
"dynamic_rendering_local_read/slang/scene_opaque.frag.slang"
|
||||
"dynamic_rendering_local_read/slang/scene_transparent.vert.slang"
|
||||
"dynamic_rendering_local_read/slang/scene_transparent.frag.slang")
|
||||
@@ -1,121 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2024-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 Rendering local read
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/dynamic_rendering_local_read[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
image::./images/sample.png[Sample]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates how to use the `VK_KHR_dynamic_rendering_local_read` extension in conjunction with the `VK_KHR_dynamic_rendering` extension. This combination can replace core render and subpasses, making it possible to do local reads via input attachments with dynamic rendering.
|
||||
|
||||
== Toggling between dynamic rendering and renderpasses
|
||||
|
||||
To make it easy to compare the two different approaches of using either dynamic rendering + local reads or renderpasses + subpasses, this sample has code for both rendering paths.
|
||||
|
||||
A define in `dynamic_rendering_local_read.h` can be used to toggle between the two techniques:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
#define USE_DYNAMIC_RENDERING
|
||||
----
|
||||
|
||||
This is enabled by default, making the sample use dynamic rendering with local reads. If you want to use renderpass + subpasses instead, comment this define out and compile the sample.
|
||||
|
||||
== Comparison
|
||||
|
||||
For a primer on the differences between renderpasses and dynamic rendering, see the readme of the xref:/samples/extensions/dynamic_rendering/README.adoc[dynamic rendering sample].
|
||||
|
||||
Here is the comparison table from that example extended with the newly added features from `VK_KHR_dynamic_rendering_local_read` in *bold*:
|
||||
|
||||
|===
|
||||
| Vulkan 1.0 | Dynamic Rendering
|
||||
|
||||
| Rendering begins with `vkCmdBeginRenderPass`
|
||||
| Rendering begins with `vkCmdBeginRenderingKHR`
|
||||
|
||||
| Rendering struct is `VkRenderPassBeginInfo`
|
||||
| Rendering struct is `VkRenderingInfoKHR`
|
||||
|
||||
| Attachments are referenced by `VkFramebuffer`
|
||||
| Attachments are referenced by `VkRenderingAttachmentInfoKHR`
|
||||
|
||||
| `VkFramebuffer` objects are heap-allocated and opaque
|
||||
| `VkRenderingAttachmentInfoKHR` objects are stack-allocated
|
||||
|
||||
| Graphics pipeline creation references a `VkRenderPass`
|
||||
| Graphics pipeline creation references a `VkPipelineRenderingCreateInfoKHR`
|
||||
|
||||
| *Subpasses are advanced with `vkCmdNextSubpass`*
|
||||
| *`VkImageMemoryBarrier` to `VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR` image layout*
|
||||
|
||||
| *Local reads in shaders use `subpassLoad`*
|
||||
| *Local reads in shaders use `subpassLoad`*
|
||||
|===
|
||||
|
||||
== The sample
|
||||
|
||||
With subpasses it's possible to do pixel local reads within a single renderpass. Local read means that you can't freely sample (like with a texture + sampler) but are instead limited to reading the pixel value from the previous subpass at the exact same position. This is based on how esp. tile based GPU architectures work. On such architectures workloads that don't need to sample arbitrarily can improve performance using subpasses and pixel local reads using input attachments. One such example is a deferred renderer with a composition pass. First multiple attachments are filled with different information (albedo, normals, world space position) and then at a later point those attachments are combined into a single image. This composition step reads those attachments at the exact same position that the current pass is operating on, so instead of sampling from these we can use them as input attachments instead and do only pixel local reads.
|
||||
|
||||
The rendering setup for this sample looks like this:
|
||||
|
||||
image::./images/deferred_setup.png[Deferred setup describing subpasses]
|
||||
|
||||
A big criticism with renderpasses was how involved esp. the setup is. Getting renderpasses and subpasses incl. dependencies correct can be tricky and renderpasses are kinda hard to integrate into a dynamically changing setup, making them a hard fit for complex Vulkan projects like game engines. With dynamic rendering, setup is far less involved and moves mostly to command buffer creation. If you look at the sample you can easily spot how much code required by looking at the parts that are deactivated via the `dynamic_rendering_local_read` C++ define. More on this can be found in the xref:/samples/extensions/dynamic_rendering/README.adoc[dynamic rendering sample] readme. For this sample we'll only look at draw time.
|
||||
|
||||
== Replacing subpasses for local reads
|
||||
|
||||
=== Input attachments
|
||||
|
||||
Just like local reads in subpasses, dynamic rendering local read also makes use of input attachments. That should make it easy to convert existing code to this new extension. So unless you do advanced things like input attachment reordering, the changes required to add pixel local reads to dynamic rendering are minimal and only affect the application side. There are no changes to the shader interface, so shaders that have been used with renderpasses + subpasses can be used without any changes. Even with dynamic rendering and local reads you use `subpassInput` and `subpassLoad`.
|
||||
|
||||
=== Self-Dependencies
|
||||
|
||||
With the `dynamicRenderingLocalReads` feature enabled, it's now possible to use pipeline barriers within dynamic rendering if they include the `VK_DEPENDENCY_BY_REGION_BIT`. Such a barrier makes attachments before the barrier readable as input attachments afterwards. The extension also introduces the new image layout `VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR` that can be used for storage images and attachments to make writes to those visible via input attachments.
|
||||
|
||||
=== Renderpasses with subpasses
|
||||
|
||||
. Start a new renderpass with `vkCmdBeginRenderPass` (this also starts the first subpass)
|
||||
. Fill G-Buffer attachments
|
||||
. Start the second subpass with `vkCmdNextSubpass`
|
||||
. Combine G-Buffer attachments using input attachments (and draw to screen using a full-screen quad)
|
||||
. Start the third subpass with `vkCmdNextSubpass`
|
||||
. Draw transparent geometry with a forward pass reading depth from an attachment
|
||||
. End renderpass with `vkCmdEndRenderPass`
|
||||
|
||||
=== Dynamic render with local read
|
||||
|
||||
. Start dynamic rendering with `vkCmdBeginRenderingKHR`
|
||||
. Fill G-Buffer attachments
|
||||
. Insert a memory barrier with the "by region" bit set to make attachment writes visible for input attachment reads for the next draw call
|
||||
. Combine G-Buffer attachments using input attachments (and draw to screen using a full-screen quad)
|
||||
. Draw transparent geometry with a forward pass reading depth from an attachment
|
||||
. End dynamic rendering with `vkCmdEndRenderingKHR`
|
||||
|
||||
== Conclusion
|
||||
|
||||
With the addition of `VK_KHR_dynamic_rendering_local_read` it's now finally possible to fully replace renderpasses, including those that have multiple subpasses. This makes dynamic rendering a fully fledged replacement for renderpasses on all implementations, including tile based architectures.
|
||||
|
||||
== Additional information
|
||||
|
||||
* https://docs.vulkan.org/features/latest/features/proposals/VK_KHR_dynamic_rendering_local_read.html[Extension proposal]
|
||||
* https://www.khronos.org/blog/streamlining-subpasses[Extension blog post]
|
||||
@@ -1,117 +0,0 @@
|
||||
/* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include "gltf_loader.h"
|
||||
|
||||
// Can be used to toggle between renderpasses + subpasses and dynamic rendering + local read
|
||||
#define USE_DYNAMIC_RENDERING
|
||||
|
||||
class DynamicRenderingLocalRead : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
DynamicRenderingLocalRead();
|
||||
virtual ~DynamicRenderingLocalRead();
|
||||
void prepare_pipelines();
|
||||
void build_command_buffers() override;
|
||||
void render(float delta_time) override;
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
|
||||
private:
|
||||
struct Scenes
|
||||
{
|
||||
std::unique_ptr<vkb::sg::Scene> opaque;
|
||||
std::unique_ptr<vkb::sg::Scene> transparent;
|
||||
} scenes;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture transparent_glass;
|
||||
} textures;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
glm::mat4 view;
|
||||
} shader_data_vs;
|
||||
|
||||
struct Light
|
||||
{
|
||||
glm::vec4 position;
|
||||
glm::vec3 color;
|
||||
float radius{0};
|
||||
};
|
||||
|
||||
std::array<Light, 64> lights;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> ubo_vs;
|
||||
std::unique_ptr<vkb::core::BufferC> ssbo_lights;
|
||||
} buffers;
|
||||
|
||||
struct PushConstantSceneNode
|
||||
{
|
||||
glm::mat4 matrix;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
struct Pass
|
||||
{
|
||||
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
|
||||
VkPipeline pipeline{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE};
|
||||
VkDescriptorSet descriptor_set{VK_NULL_HANDLE};
|
||||
} scene_opaque_pass, scene_transparent_pass, composition_pass;
|
||||
|
||||
struct FrameBufferAttachment
|
||||
{
|
||||
VkImage image{VK_NULL_HANDLE};
|
||||
VkDeviceMemory memory{VK_NULL_HANDLE};
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
VkFormat format{VK_FORMAT_UNDEFINED};
|
||||
};
|
||||
struct Attachments
|
||||
{
|
||||
FrameBufferAttachment positionDepth, normal, albedo;
|
||||
} attachments;
|
||||
|
||||
int32_t attachment_width{0};
|
||||
int32_t attachment_height{0};
|
||||
|
||||
void setup_framebuffer() override;
|
||||
void setup_render_pass() override;
|
||||
void prepare_gui() override;
|
||||
|
||||
void load_assets();
|
||||
void create_attachment(VkFormat format, VkImageUsageFlags usage, FrameBufferAttachment &attachment);
|
||||
void destroy_attachment(FrameBufferAttachment &attachment);
|
||||
void create_attachments();
|
||||
void prepare_buffers();
|
||||
void update_lights_buffer();
|
||||
void update_uniform_buffer();
|
||||
void prepare_layouts_and_descriptors();
|
||||
|
||||
void draw_scene(std::unique_ptr<vkb::sg::Scene> &scene, VkCommandBuffer cmd, VkPipelineLayout pipeline_layout);
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSample<vkb::BindingType::C>> create_dynamic_rendering_local_read();
|
||||
|
Before Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 706 KiB |
@@ -1,36 +0,0 @@
|
||||
# Copyright (c) 2023, Mobica Limited
|
||||
#
|
||||
# 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 "Mobica"
|
||||
NAME "Extended Dynamic State 2"
|
||||
DESCRIPTION "Demonstrate how to use depth bias, primitive restart, rasterizer discard and patch control points dynamically, which can reduce the number of pipeline objects that are need to be created."
|
||||
SHADER_FILES_GLSL
|
||||
"extended_dynamic_state2/baseline.vert"
|
||||
"extended_dynamic_state2/baseline.frag"
|
||||
"extended_dynamic_state2/tess.vert"
|
||||
"extended_dynamic_state2/tess.tese"
|
||||
"extended_dynamic_state2/tess.tesc"
|
||||
"extended_dynamic_state2/tess.frag"
|
||||
"extended_dynamic_state2/background.vert"
|
||||
"extended_dynamic_state2/background.frag")
|
||||
@@ -1,276 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, Mobica Limited
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
= Extended dynamic state 2
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/extended_dynamic_state2[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
image::./images/extended_dynamic_state2_screenshot.png[Sample]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample demonstrates how to use `VK_EXT_extended_dynamic_state2` extension, which eliminates the need to create multiple pipelines in case of specific different parameters.
|
||||
|
||||
This extension changes how *Depth Bias*, *Primitive Restart*, *Rasterizer Discard* and *Patch Control Points* are managed.
|
||||
Instead of static description during pipeline creation, this extension allows developers to change those parameters by using a function before every draw.
|
||||
|
||||
Below is a comparison of common Vulkan static and dynamic implementation of those extensions with additional usage of `vkCmdSetPrimitiveTopologyEXT` extension from dynamic state .
|
||||
|
||||
|===
|
||||
| Static/Non-dynamic | Dynamic State 2
|
||||
|
||||
| dynamic_state = {}
|
||||
| dynamic_state = {VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, + VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT}
|
||||
|
||||
| vkCreateGraphicsPipelines(pipeline1) + vkCreateGraphicsPipelines(pipeline2) + vkCreateGraphicsPipelines(pipeline3) + vkCreateGraphicsPipelines(pipeline4)
|
||||
| vkCreateGraphicsPipelines(pipeline1) + vkCreateGraphicsPipelines(pipeline2)
|
||||
|
||||
| draw(model1, pipeline1) + draw(model2, pipeline2) + draw(model3, pipeline3) + draw(model4, pipeline4)
|
||||
| vkCmdSetPrimitiveRestartEnableEXT(commandBuffer1, primitiveBoolParam) + vkCmdSetDepthBiasEnableEXT(commandBuffer1, depthBiasBoolParam) + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer1,rasterizerBoolParam) + vkCmdSetPrimitiveTopologyEXT(commandBuffer1, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) + draw(model1, pipeline1) + vkCmdSetPrimitiveTopologyEXT(commandBuffer2, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP) + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer2, primitiveBoolParam) + draw(model2, pipeline1) + vkCmdSetDepthBiasEnableEXT(commandBuffer3, depthBiasBoolParam) + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer3, primitiveBoolParam) + draw(model3, pipeline1) + vkCmdSetPatchControlPointsEXT(commandBuffer4, patchControlPoints) + draw(model4, pipeline2)
|
||||
|===
|
||||
|
||||
More details are provided in the sections that follow.
|
||||
|
||||
== Pipelines
|
||||
|
||||
Previously developers had to create multiple pipelines for different parameters in Depth Bias, Primitive Restart, Rasterizer Discard and Patch Control Points.
|
||||
This is illustrated in a static/non-dynamic pipeline creation.
|
||||
|
||||
[,C++]
|
||||
----
|
||||
...
|
||||
/* First pipeline creation */
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, /* value used in 1st and 2nd pipeline */
|
||||
0,
|
||||
VK_FALSE); /* primitiveRestartEnable */
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL, /* value used in 1st, 2nd and 3rd pipeline */
|
||||
VK_CULL_MODE_BACK_BIT,
|
||||
VK_FRONT_FACE_CLOCKWISE,
|
||||
0);
|
||||
rasterization_state.depthBiasConstantFactor = 1.0f;
|
||||
rasterization_state.depthBiasSlopeFactor = 1.0f;
|
||||
rasterization_state.depthBiasClamp = 0.0f;
|
||||
|
||||
/* 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, /* depthTestEnable */
|
||||
VK_TRUE, /* depthWriteEnable */
|
||||
VK_COMPARE_OP_GREATER);
|
||||
...
|
||||
|
||||
/* VkGraphicsPipelineCreateInfo for all pipelines, parameters are modified before each pipeline creation */
|
||||
VkGraphicsPipelineCreateInfo graphics_create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
graphics_create.pInputAssemblyState = &input_assembly_state;
|
||||
graphics_create.pRasterizationState = &rasterization_state;
|
||||
graphics_create.pDepthStencilState = &depth_stencil_state;
|
||||
graphics_create.pTessellationState = VK_NULL_HANDLE;
|
||||
...
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipeline1));
|
||||
|
||||
/* Second pipeline creation */
|
||||
rasterization_state.rasterizerDiscardEnable = VK_TRUE;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipeline2));
|
||||
|
||||
/* Third pipeline creation */
|
||||
rasterization_state.rasterizerDiscardEnable = VK_FALSE;
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
input_assembly_state.primitiveRestartEnable = VK_TRUE;
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipeline3));
|
||||
|
||||
/* Fourth pipeline creation */
|
||||
VkPipelineTessellationStateCreateInfo tessellation_state = vkb::initializers::pipeline_tessellation_state_create_info(3);
|
||||
graphics_create.layout = pipeline_layouts.tesselation;
|
||||
graphics_create.pTessellationState = &tessellation_state;
|
||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
input_assembly_state.primitiveRestartEnable = VK_FALSE;
|
||||
rasterization_state.depthBiasEnable = VK_TRUE;
|
||||
if (get_device().get_gpu().get_features().fillModeNonSolid)
|
||||
{
|
||||
rasterization_state.polygonMode = VK_POLYGON_MODE_LINE; /* Wireframe mode */
|
||||
}
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipeline4));
|
||||
----
|
||||
|
||||
In the above approach if developer would like to change the patch control points number, then for each different number a new pipeline would be required.
|
||||
|
||||
However, with `VK_EXT_extended_dynamic_state2` the number of pipelines can be reduced by the possibility to change parameters of Depth Bias, Primitive Restart, Rasterizer Discard and Patch Control Points by calling `vkCmdSetDepthBiasEnableEXT`, `vkCmdSetPrimitiveRestartEnableEXT`, `vkCmdSetRasterizerDiscardEnableEXT` and `vkCmdSetPatchControlPointsEXT` respectively before calling the `draw_model` method.
|
||||
|
||||
With the usage of above functions we can reduce the number of pipelines.
|
||||
Required dynamic states must be enabled and passed to the `VkGraphicsPipelineCreateInfo` structure.
|
||||
|
||||
`VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT` specifies that the topology state in the `VkPipelineInputAssemblyStateCreateInfo` struct only specifies the topology class.
|
||||
The specific topology order and adjacency must be set dynamically with `vkCmdSetPrimitiveTopology` before any drawing commands.
|
||||
|
||||
[,C+]
|
||||
----
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT,
|
||||
};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
static_cast<uint32_t>(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
VkGraphicsPipelineCreateInfo graphics_create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
graphics_create.pInputAssemblyState = &input_assembly_state;
|
||||
graphics_create.pDynamicState = &dynamic_state;
|
||||
...
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipeline.baseline));
|
||||
----
|
||||
|
||||
And now, thanks to `VK_EXT_extended_dynamic_state2`, we can change parameters before each corresponding draw call.
|
||||
|
||||
[,C++]
|
||||
----
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffer, &command_begin));
|
||||
|
||||
...
|
||||
/* Binding baseline pipeline and descriptor sets */
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline_layouts.baseline,
|
||||
0,
|
||||
1,
|
||||
&descriptor_sets.baseline,
|
||||
0,
|
||||
nullptr);
|
||||
vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.baseline);
|
||||
|
||||
/* Setting topology to triangle list and disabling primitive restart functionality */
|
||||
vkCmdSetPrimitiveTopologyEXT(draw_cmd_buffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
vkCmdSetPrimitiveRestartEnableEXT(draw_cmd_buffer, VK_FALSE);
|
||||
|
||||
/* Drawing objects from baseline scene (with rasterizer discard and depth bias functionality) */
|
||||
draw_from_scene(draw_cmd_buffer, scene_elements_baseline);
|
||||
|
||||
/* Changing topology to triangle strip with using primitive restart feature */
|
||||
vkCmdSetPrimitiveTopologyEXT(draw_cmd_buffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
|
||||
vkCmdSetPrimitiveRestartEnableEXT(draw_cmd_buffer, VK_TRUE);
|
||||
|
||||
/* Draw model with primitive restart functionality */
|
||||
draw_created_model(draw_cmd_buffer);
|
||||
|
||||
/* Changing bindings to tessellation pipeline */
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline_layouts.tesselation,
|
||||
0,
|
||||
1,
|
||||
&descriptor_sets.tesselation,
|
||||
0,
|
||||
nullptr);
|
||||
vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.tesselation);
|
||||
|
||||
/* Change topology to patch list and setting patch control points value */
|
||||
vkCmdSetPrimitiveTopologyEXT(draw_cmd_buffer, VK_PRIMITIVE_TOPOLOGY_PATCH_LIST);
|
||||
vkCmdSetPatchControlPointsEXT(draw_cmd_buffer, patch_control_points_triangle);
|
||||
|
||||
/* Drawing scene with objects using tessellation feature */
|
||||
draw_from_scene(draw_cmd_buffer, scene_elements_tess);
|
||||
|
||||
/* Changing bindings to background pipeline */
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffer,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline_layouts.background,
|
||||
0,
|
||||
1,
|
||||
&descriptor_sets.background,
|
||||
0,
|
||||
nullptr);
|
||||
vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.background);
|
||||
|
||||
/* Setting topology to triangle list */
|
||||
vkCmdSetPrimitiveTopologyEXT(draw_cmd_buffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
|
||||
/* Drawing background */
|
||||
draw_model(background_model, draw_cmd_buffer);
|
||||
...
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffer));
|
||||
----
|
||||
|
||||
The usage of depth bias dynamic state is implemented in the `draw_from_scene` function.
|
||||
For each scene element (except Geosphere) the depth bias or the rasterizer discard options are enabled depending on GUI settings.
|
||||
At the end of the function settings are reseted (set to VK_FALSE).
|
||||
|
||||
[,C++]
|
||||
----
|
||||
void ExtendedDynamicState2::draw_from_scene(VkCommandBuffer command_buffer, std::vector<SceneNode> const &scene_node)
|
||||
{
|
||||
for (int i = 0; i < scene_node.size(); ++i)
|
||||
{
|
||||
if (scene_node[i].name != "Geosphere")
|
||||
{
|
||||
vkCmdSetDepthBiasEnableEXT(command_buffer, gui_settings.objects[i].depth_bias);
|
||||
vkCmdSetRasterizerDiscardEnableEXT(command_buffer, gui_settings.objects[i].rasterizer_discard);
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
vkCmdDrawIndexed(command_buffer, scene_node[i].sub_mesh->vertex_indices, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
vkCmdSetDepthBiasEnableEXT(command_buffer, VK_FALSE);
|
||||
vkCmdSetRasterizerDiscardEnableEXT(command_buffer, VK_FALSE);
|
||||
}
|
||||
----
|
||||
|
||||
== Enabling the Extension
|
||||
|
||||
The extended dynamic state 2 api requires Vulkan 1.0 and the appropriate headers / SDK is required.
|
||||
This extension has been https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_extended_dynamic_state2.html#_promotion_to_vulkan_1_3[partially] promoted to Vulkan 1.3.
|
||||
|
||||
The device extension is provided by `VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME`.
|
||||
It also requires `VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME` instance extension to be enabled.
|
||||
|
||||
[,C++]
|
||||
----
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
----
|
||||
|
||||
If the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceExtendedDynamicState2FeaturesEXT.html[`VkPhysicalDeviceExtendedDynamicState2FeaturesEXT`] structure is included in the pNext chain of the `VkPhysicalDeviceFeatures2` structure passed to vkGetPhysicalDeviceFeatures2, it is filled in to indicate whether each corresponding feature is supported.
|
||||
`VkPhysicalDeviceExtendedDynamicState2FeaturesEXT` can also be used in the pNext chain of `VkDeviceCreateInfo` to selectively enable these features.
|
||||
o selectively enable these features.
|
||||
@@ -1,160 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, Mobica Limited
|
||||
*
|
||||
* 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 <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include "scene_graph/components/pbr_material.h"
|
||||
|
||||
class ExtendedDynamicState2 : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
typedef struct ModelDynamicParam
|
||||
{
|
||||
bool depth_bias = false;
|
||||
bool rasterizer_discard = false;
|
||||
} ModelDynamicParam;
|
||||
|
||||
struct
|
||||
{
|
||||
bool tessellation = false;
|
||||
float tess_factor = 1.0f;
|
||||
std::vector<ModelDynamicParam> objects;
|
||||
int selected_obj = 0;
|
||||
bool selection_active = true;
|
||||
bool time_tick = false;
|
||||
} gui_settings;
|
||||
|
||||
/* Buffer used in all pipelines */
|
||||
struct UBOCOMM
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 view;
|
||||
} ubo_common;
|
||||
|
||||
struct UBOBAS
|
||||
{
|
||||
glm::vec4 ambientLightColor = glm::vec4(1.f, 1.f, 1.f, 0.1f);
|
||||
glm::vec4 lightPosition = glm::vec4(-3.0f, -8.0f, 6.0f, -1.0f);
|
||||
glm::vec4 lightColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
float lightIntensity = 50.0f;
|
||||
} ubo_baseline;
|
||||
|
||||
struct UBOTESS
|
||||
{
|
||||
float tessellation_factor = 1.0f;
|
||||
} ubo_tess;
|
||||
|
||||
VkDescriptorPool descriptor_pool{VK_NULL_HANDLE};
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSetLayout baseline{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout tesselation{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout background{VK_NULL_HANDLE};
|
||||
} descriptor_set_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout baseline{VK_NULL_HANDLE};
|
||||
VkPipelineLayout tesselation{VK_NULL_HANDLE};
|
||||
VkPipelineLayout background{VK_NULL_HANDLE};
|
||||
} pipeline_layouts;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet baseline{VK_NULL_HANDLE};
|
||||
VkDescriptorSet tesselation{VK_NULL_HANDLE};
|
||||
VkDescriptorSet background{VK_NULL_HANDLE};
|
||||
} descriptor_sets;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipeline baseline{VK_NULL_HANDLE};
|
||||
VkPipeline tesselation{VK_NULL_HANDLE};
|
||||
VkPipeline background{VK_NULL_HANDLE};
|
||||
} pipeline;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> common;
|
||||
std::unique_ptr<vkb::core::BufferC> baseline;
|
||||
std::unique_ptr<vkb::core::BufferC> tesselation;
|
||||
} uniform_buffers;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture envmap;
|
||||
} textures;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::mat4 model_matrix;
|
||||
glm::vec4 color;
|
||||
} push_const_block;
|
||||
|
||||
struct SceneNode
|
||||
{
|
||||
std::string name;
|
||||
vkb::sg::Node *node;
|
||||
vkb::sg::SubMesh *sub_mesh;
|
||||
};
|
||||
std::vector<SceneNode> scene_elements_baseline;
|
||||
std::vector<SceneNode> scene_elements_tess;
|
||||
|
||||
std::unique_ptr<vkb::sg::SubMesh> background_model;
|
||||
|
||||
struct Cube
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> vertices_pos;
|
||||
std::unique_ptr<vkb::core::BufferC> vertices_norm;
|
||||
std::unique_ptr<vkb::core::BufferC> indices;
|
||||
uint32_t index_count;
|
||||
} cube;
|
||||
|
||||
ExtendedDynamicState2();
|
||||
~ExtendedDynamicState2();
|
||||
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
void update(float delta_time) override;
|
||||
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void create_pipelines();
|
||||
void draw();
|
||||
|
||||
void load_assets();
|
||||
void create_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void create_descriptor_sets();
|
||||
glm::vec4 get_changed_alpha(const vkb::sg::PBRMaterial *original_mat);
|
||||
void scene_pipeline_divide(std::vector<SceneNode> const &scene_node);
|
||||
void draw_from_scene(VkCommandBuffer command_buffer, std::vector<SceneNode> const &scene_node);
|
||||
void draw_created_model(VkCommandBuffer commandBuffer);
|
||||
void model_data_creation();
|
||||
void cube_animation(float delta_time);
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_extended_dynamic_state2();
|
||||
|
Before Width: | Height: | Size: 1.2 MiB |
@@ -1,32 +0,0 @@
|
||||
# Copyright (c) 2023-2024, Mobica Limited
|
||||
#
|
||||
# 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 "Mobica"
|
||||
NAME "Fragment shader barycentric"
|
||||
DESCRIPTION "Demonstrate how to use fragment shader barycentric feature, which allows accessing barycentric coordinates for each processed fragment."
|
||||
SHADER_FILES_GLSL
|
||||
"fragment_shader_barycentric/object.vert"
|
||||
"fragment_shader_barycentric/object.frag"
|
||||
"fragment_shader_barycentric/skybox.vert"
|
||||
"fragment_shader_barycentric/skybox.frag")
|
||||
@@ -1,131 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, Mobica Limited
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
= Fragment shader barycentric
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/fragment_shader_barycentric[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
image::./images/fragment_shader_barycentric_screenshot.png[fragment_shader_barycentric]
|
||||
|
||||
Fragment shader barycentric feature provides support for accessing the barycentric coordinates (linear and perspective) in the fragment shader and vertex attribute with the `pervertexEXT` decoration.
|
||||
|
||||
== Overview
|
||||
|
||||
The https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_fragment_shader_barycentric.html[VK_KHR_fragment_shader_barycentric] extension is based on https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_NV_fragment_shader_barycentric.html[VK_NV_fragment_shader_barycentric].
|
||||
|
||||
The extension provides access to additional built-in variables and decorations:
|
||||
|
||||
|===
|
||||
| Type | GLSL | SPIR-V
|
||||
|
||||
| built-in variable
|
||||
| in vec3 gl_BaryCoordEXT;
|
||||
| BaryCoordKHR
|
||||
|
||||
| built-in variable
|
||||
| in vec3 gl_BaryCoordNoPerspEXT;
|
||||
| BaryCoordNoPerspKHR
|
||||
|
||||
| decoration
|
||||
| pervertexEXT
|
||||
| perVertexKHR
|
||||
|===
|
||||
|
||||
The built-in fragment shader input variables `gl_BaryCoordEXT` and `gl_BaryCoordNoPerspEXT` are three-component floating-point vectors that provide the barycentric coordinates for the fragment.
|
||||
The values for these built-ins are derived as described in https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#interfaces-builtin-variables[the Vulkan API Specifications].
|
||||
The built-in variables hold barycentric weights for the fragment produced using:
|
||||
|
||||
* perspective interpolation: `gl_BaryCoordEXT`
|
||||
* linear interpolation: `gl_BaryCoordNoPerspEXT`
|
||||
|
||||
The fragment shader inputs declared with the `pervertexEXT` decoration get the per-vertex values of the outputs from the previous shader stage declared with the same name.
|
||||
Such inputs must be declared as an array, because they have values for each vertex in the input primitive, e.g.
|
||||
|
||||
----
|
||||
layout(location = 0) pervertexEXT in vec4 perVertexAttr[];
|
||||
----
|
||||
|
||||
Each array element corresponds to one of the vertices of the primitive that produced the fragment.
|
||||
The order of the vertices is defined in https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#primsrast-barycentric[the Vulkan API Specifications].
|
||||
Interpolated values are not available for inputs declared with the https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#shaders-interpolation-decorations-pervertexkhr[`pervertexEXT`].
|
||||
|
||||
The fragment shader barycentric sample demonstrates feature usage by applying different effects on a cube.
|
||||
The effects are implemented using the `pervertexEXT` decoration and built-in variables `gl_BaryCoordEXT` and `gl_BaryCoordNoPerspEXT`.
|
||||
|
||||
The following effects are available from the GUI:
|
||||
|
||||
* Color interpolation - Demonstrates color interpolation using barycentric coordinates and information about color in vertices of the triangle (passed as `pervertexEXT` variable from the vertex shader).
|
||||
* Perspective vs non-perspective - Demonstrates the difference between barycentric perspective and non-perspective coordinates.
|
||||
* Wireframe - Demonstrates rendering a wireframe using barycentric coordinates.
|
||||
* Interpolate to mass center - Demonstrates color interpolation to the triangle's center of mass using barycentric coordinates.
|
||||
* Barycoord texture - Demonstrates the modification of a texture using barycentric coordinates.
|
||||
|
||||
== Enabling the Extension
|
||||
|
||||
Enabling the fragment shader barycentric feature is done using the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR.html[`VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR`] structure, where `fragmentShaderBarycentric` indicates barycentric support in fragment shaders.
|
||||
The structure should be passed to `vkGetPhysicalDeviceFeatures2` in the pNext member of the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceFeatures2.html[`VkPhysicalDeviceFeatures2`] structure.
|
||||
|
||||
[,C++]
|
||||
----
|
||||
VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR requested_fragment_shader_barycentric_features
|
||||
requested_fragment_shader_barycentric_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR;
|
||||
requested_fragment_shader_barycentric_features.fragmentShaderBarycentric = VK_TRUE;
|
||||
----
|
||||
|
||||
In the sample it is done in the `FragmentShaderBarycentric::request_gpu_features` method using the template function `vkb::PhysicalDevice::request_extension_features` provided by the Vulkan-Samples framework.
|
||||
|
||||
== Shaders
|
||||
|
||||
=== Vertex shader
|
||||
|
||||
In the vertex shader a variable `outColor` is declared.
|
||||
It is used in the fragment shader with the `pervertexEXT` decoration:
|
||||
|
||||
[,GLSL]
|
||||
----
|
||||
layout (location = 0) out vec3 outColor;
|
||||
----
|
||||
|
||||
=== Fragment shader
|
||||
|
||||
In the fragment shader the required feature is defined:
|
||||
|
||||
[,GLSL]
|
||||
----
|
||||
#extension GL_EXT_fragment_shader_barycentric : require
|
||||
----
|
||||
|
||||
The color input variable is declared with the `pervertexEXT` decoration and as a matrix (it contains color for three vertices of the triangle for each processed fragment):
|
||||
|
||||
[,GLSL]
|
||||
----
|
||||
layout (location = 0) in pervertexEXT vec3 inColor[];
|
||||
----
|
||||
|
||||
Depending on the effect chosen in the GUI `outColor` is calculated differently in the switch-case statement, e.g.
|
||||
for color interpolation using barycentric perspective coordinates:
|
||||
|
||||
[,GLSL]
|
||||
----
|
||||
outColor.rgb = inColor[0].rgb * gl_BaryCoordEXT.x +
|
||||
inColor[1].rgb * gl_BaryCoordEXT.y +
|
||||
inColor[2].rgb * gl_BaryCoordEXT.z;
|
||||
----
|
||||
@@ -1,405 +0,0 @@
|
||||
/* Copyright (c) 2023-2025, Mobica Limited
|
||||
*
|
||||
* 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 "fragment_shader_barycentric.h"
|
||||
|
||||
FragmentShaderBarycentric::FragmentShaderBarycentric()
|
||||
{
|
||||
title = "Fragment shader barycentric";
|
||||
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
FragmentShaderBarycentric::~FragmentShaderBarycentric()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroySampler(get_device().get_handle(), textures.envmap.sampler, VK_NULL_HANDLE);
|
||||
vkDestroySampler(get_device().get_handle(), textures.cube.sampler, VK_NULL_HANDLE);
|
||||
textures = {};
|
||||
skybox.reset();
|
||||
object.reset();
|
||||
ubo.reset();
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.object, VK_NULL_HANDLE);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.skybox, VK_NULL_HANDLE);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, VK_NULL_HANDLE);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, VK_NULL_HANDLE);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @fn bool FragmentShaderBarycentric::prepare(const vkb::ApplicationOptions &options)
|
||||
* @brief Configuring all sample specific settings, creating descriptor sets/pool, pipelines, generating or loading models etc.
|
||||
*/
|
||||
bool FragmentShaderBarycentric::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set up camera properties */
|
||||
camera.type = vkb::CameraType::LookAt;
|
||||
camera.set_position({0.f, 1.0f, -6.0f});
|
||||
camera.set_rotation({0.f, 0.f, 0.f});
|
||||
camera.set_perspective(60.f, static_cast<float>(width) / static_cast<float>(height), 256.f, 0.1f);
|
||||
|
||||
load_assets();
|
||||
prepare_uniform_buffers();
|
||||
create_descriptor_pool();
|
||||
setup_descriptor_set_layout();
|
||||
create_descriptor_sets();
|
||||
create_pipeline();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::load_assets()
|
||||
* @brief Loading extra models, textures from assets
|
||||
*/
|
||||
void FragmentShaderBarycentric::load_assets()
|
||||
{
|
||||
// Loading models
|
||||
skybox = load_model("scenes/cube.gltf"); // background
|
||||
object = load_model("scenes/textured_unit_cube.gltf"); // cube in the center of the scene
|
||||
|
||||
// Loading textures
|
||||
textures.envmap = load_texture_cubemap("textures/uffizi_rgba16f_cube.ktx", vkb::sg::Image::Color);
|
||||
textures.cube = load_texture("textures/checkerboard_rgba.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::prepare_uniform_buffers()
|
||||
* @brief Preparing uniform buffer and updating UB data
|
||||
*/
|
||||
void FragmentShaderBarycentric::prepare_uniform_buffers()
|
||||
{
|
||||
ubo = 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::create_descriptor_pool()
|
||||
* @brief Creating descriptor pool with size adjusted to use uniform buffer and image sampler
|
||||
*/
|
||||
void FragmentShaderBarycentric::create_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_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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::update_uniform_buffers()
|
||||
* @brief Updating data from application to GPU uniform buffer
|
||||
*/
|
||||
void FragmentShaderBarycentric::update_uniform_buffers()
|
||||
{
|
||||
ubo_vs.projection = camera.matrices.perspective;
|
||||
ubo_vs.modelview = camera.matrices.view;
|
||||
ubo->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::setup_descriptor_set_layout()
|
||||
* @brief Creating layout for descriptor sets
|
||||
*/
|
||||
void FragmentShaderBarycentric::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_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, &descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
// Pass selected effect information via push constants
|
||||
VkPushConstantRange push_constant_range =
|
||||
vkb::initializers::push_constant_range(VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(gui_settings.selected_effect), 0);
|
||||
pipeline_layout_create_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_create_info.pPushConstantRanges = &push_constant_range;
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::create_descriptor_sets()
|
||||
* @brief Creating descriptor sets for two models
|
||||
*/
|
||||
void FragmentShaderBarycentric::create_descriptor_sets()
|
||||
{
|
||||
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_sets.skybox));
|
||||
|
||||
VkDescriptorBufferInfo matrix_buffer_descriptor = create_descriptor(*ubo);
|
||||
VkDescriptorImageInfo environment_image_descriptor = create_descriptor(textures.envmap);
|
||||
std::vector<VkWriteDescriptorSet> 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)};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.object));
|
||||
VkDescriptorImageInfo cube_image_descriptor = create_descriptor(textures.cube);
|
||||
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, &cube_image_descriptor)};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::create_pipeline()
|
||||
* @brief Creating graphical pipeline
|
||||
*/
|
||||
void FragmentShaderBarycentric::create_pipeline()
|
||||
{
|
||||
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);
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
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, 2> shader_stages{};
|
||||
shader_stages[0] = load_shader("fragment_shader_barycentric/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("fragment_shader_barycentric/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Use the pNext to point to the rendering create struct
|
||||
VkGraphicsPipelineCreateInfo graphics_create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
||||
graphics_create.pNext = VK_NULL_HANDLE;
|
||||
graphics_create.renderPass = render_pass;
|
||||
graphics_create.pInputAssemblyState = &input_assembly_state;
|
||||
graphics_create.pRasterizationState = &rasterization_state;
|
||||
graphics_create.pColorBlendState = &color_blend_state;
|
||||
graphics_create.pMultisampleState = &multisample_state;
|
||||
graphics_create.pViewportState = &viewport_state;
|
||||
graphics_create.pDepthStencilState = &depth_stencil_state;
|
||||
graphics_create.pDynamicState = &dynamic_state;
|
||||
graphics_create.pVertexInputState = &vertex_input_state;
|
||||
graphics_create.stageCount = static_cast<uint32_t>(shader_stages.size());
|
||||
graphics_create.pStages = shader_stages.data();
|
||||
graphics_create.layout = pipeline_layout;
|
||||
|
||||
// Skybox pipeline (background cube)
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipelines.skybox));
|
||||
|
||||
// Object pipeline
|
||||
shader_stages[0] = load_shader("fragment_shader_barycentric/object.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("fragment_shader_barycentric/object.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Flip cull mode
|
||||
rasterization_state.cullMode = VK_CULL_MODE_FRONT_BIT;
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipelines.object));
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::draw()
|
||||
* @brief Preparing frame and submitting it to the present queue
|
||||
*/
|
||||
void FragmentShaderBarycentric::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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::build_command_buffers()
|
||||
* @brief Creating command buffers and drawing background and model on window
|
||||
*/
|
||||
void FragmentShaderBarycentric::build_command_buffers()
|
||||
{
|
||||
std::array<VkClearValue, 2> clear_values{};
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
int i = -1;
|
||||
for (auto &draw_cmd_buffer : draw_cmd_buffers)
|
||||
{
|
||||
i++;
|
||||
auto command_begin = vkb::initializers::command_buffer_begin_info();
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffer, &command_begin));
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = static_cast<uint32_t>(clear_values.size());
|
||||
render_pass_begin_info.pClearValues = clear_values.data();
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffer, &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_buffer, 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(static_cast<int>(width), static_cast<int>(height), 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffer, 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_sets.skybox, 0, nullptr);
|
||||
|
||||
// skybox
|
||||
vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
|
||||
draw_model(skybox, draw_cmd_buffer);
|
||||
|
||||
// object
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_sets.object, 0, nullptr);
|
||||
vkCmdPushConstants(draw_cmd_buffer, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(gui_settings.selected_effect), &gui_settings.selected_effect);
|
||||
vkCmdBindPipeline(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.object);
|
||||
draw_model(object, draw_cmd_buffer);
|
||||
|
||||
// UI
|
||||
draw_ui(draw_cmd_buffer);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffer);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::render(float delta_time)
|
||||
* @brief Drawing frames and/or updating uniform buffers when camera position/rotation was changed
|
||||
*/
|
||||
void FragmentShaderBarycentric::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
* @brief Projecting GUI and transferring data between GUI and application
|
||||
*/
|
||||
void FragmentShaderBarycentric::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.combo_box("Effects", &gui_settings.selected_effect, gui_settings.effect_names))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void FragmentShaderBarycentric::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
* @brief Enabling features related to Vulkan extensions
|
||||
*/
|
||||
void FragmentShaderBarycentric::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR,
|
||||
fragmentShaderBarycentric);
|
||||
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_fragment_shader_barycentric()
|
||||
{
|
||||
return std::make_unique<FragmentShaderBarycentric>();
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/* Copyright (c) 2023-2024, Mobica Limited
|
||||
*
|
||||
* 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"
|
||||
|
||||
class FragmentShaderBarycentric : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
struct
|
||||
{
|
||||
Texture envmap;
|
||||
Texture cube;
|
||||
} textures;
|
||||
|
||||
struct UBOVS
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
} ubo_vs;
|
||||
|
||||
struct GUI_settings
|
||||
{
|
||||
int selected_effect = 0; /* default interpolation */
|
||||
const std::vector<std::string> effect_names = {"Color interpolation",
|
||||
"Perspective vs non-perspective",
|
||||
"Wireframe",
|
||||
"Interpolate to mass center",
|
||||
"Barycoord texture"};
|
||||
} gui_settings;
|
||||
|
||||
std::unique_ptr<vkb::sg::SubMesh> skybox;
|
||||
std::unique_ptr<vkb::sg::SubMesh> object;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> ubo;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipeline object{VK_NULL_HANDLE};
|
||||
VkPipeline skybox{VK_NULL_HANDLE};
|
||||
} pipelines;
|
||||
|
||||
struct
|
||||
{
|
||||
VkDescriptorSet skybox{VK_NULL_HANDLE};
|
||||
VkDescriptorSet object{VK_NULL_HANDLE};
|
||||
} descriptor_sets;
|
||||
|
||||
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE};
|
||||
|
||||
FragmentShaderBarycentric();
|
||||
~FragmentShaderBarycentric() override;
|
||||
|
||||
bool prepare(const vkb::ApplicationOptions &options) 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 load_assets();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void setup_descriptor_set_layout();
|
||||
void create_descriptor_sets();
|
||||
void create_descriptor_pool();
|
||||
void create_pipeline();
|
||||
void draw();
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_fragment_shader_barycentric();
|
||||
|
Before Width: | Height: | Size: 1.6 MiB |
@@ -1,34 +0,0 @@
|
||||
# Copyright (c) 2020-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 "Fragment shading rate"
|
||||
DESCRIPTION "Using variable fragment shading rate patterns via VK_KHR_fragment_shading_rate"
|
||||
SHADER_FILES_GLSL
|
||||
"fragment_shading_rate/glsl/scene.vert"
|
||||
"fragment_shading_rate/glsl/scene.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"fragment_shading_rate/hlsl/scene.vert.hlsl"
|
||||
"fragment_shading_rate/hlsl/scene.frag.hlsl"
|
||||
DXC_ADDITIONAL_ARGUMENTS "-fspv-extension=SPV_KHR_fragment_shading_rate")
|
||||
@@ -1,31 +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.
|
||||
-
|
||||
////
|
||||
|
||||
= Fragment shading rate
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/fragment_shading_rate[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
*Extension*: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_fragment_shading_rate.html[`VK_KHR_fragment_shading_rate`]
|
||||
|
||||
Uses a special framebuffer attachment to control fragment shading rates for different framebuffer regions.
|
||||
This allows explicit control over the number of fragment shader invocations for each pixel covered by a fragment, which is e.g.
|
||||
useful for foveated rendering.
|
||||
@@ -1,746 +0,0 @@
|
||||
/* Copyright (c) 2020-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Using variable fragment shading rates from a subpass attachment with VK_KHR_fragment_shading_rate
|
||||
* This sample creates an image that contains different shading rates, which are then sampled during rendering
|
||||
*/
|
||||
|
||||
#include "fragment_shading_rate.h"
|
||||
|
||||
FragmentShadingRate::FragmentShadingRate()
|
||||
{
|
||||
title = "Fragment shading rate";
|
||||
// Enable instance and device extensions required to use VK_KHR_fragment_shading_rate
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_MAINTENANCE2_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
FragmentShadingRate::~FragmentShadingRate()
|
||||
{
|
||||
if (has_device())
|
||||
{
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.sphere, nullptr);
|
||||
vkDestroyPipeline(get_device().get_handle(), pipelines.skysphere, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), textures.scene.sampler, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), textures.skysphere.sampler, nullptr);
|
||||
uniform_buffers.scene.reset();
|
||||
invalidate_shading_rate_attachment();
|
||||
}
|
||||
}
|
||||
|
||||
void FragmentShadingRate::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
// Enable the shading rate attachment feature required by this sample
|
||||
// These are passed to device creation via a pNext structure chain
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceFragmentShadingRateFeaturesKHR,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR,
|
||||
attachmentFragmentShadingRate);
|
||||
|
||||
// Enable anisotropic filtering if supported
|
||||
if (gpu.get_features().samplerAnisotropy)
|
||||
{
|
||||
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create an image that contains the values used to determine the shading rates to apply during scene rendering
|
||||
*/
|
||||
void FragmentShadingRate::create_shading_rate_attachment()
|
||||
{
|
||||
// Check if the requested format for the shading rate attachment supports the required flag
|
||||
VkFormat requested_format = VK_FORMAT_R8_UINT;
|
||||
VkFormatProperties format_properties;
|
||||
vkGetPhysicalDeviceFormatProperties(get_device().get_gpu().get_handle(), requested_format, &format_properties);
|
||||
if (!(format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR))
|
||||
{
|
||||
throw std::runtime_error("Selected shading rate attachment image format does not support required formate featureflag");
|
||||
}
|
||||
|
||||
// Shading rate image size depends on shading rate texel size
|
||||
// For each texel in the target image, there is a corresponding shading texel size width x height block in the shading rate image
|
||||
VkExtent3D image_extent{};
|
||||
image_extent.width = static_cast<uint32_t>(ceil(width / static_cast<float>(physical_device_fragment_shading_rate_properties.maxFragmentShadingRateAttachmentTexelSize.width)));
|
||||
image_extent.height = static_cast<uint32_t>(ceil(height / static_cast<float>(physical_device_fragment_shading_rate_properties.maxFragmentShadingRateAttachmentTexelSize.height)));
|
||||
image_extent.depth = 1;
|
||||
|
||||
VkImageCreateInfo image_create_info{};
|
||||
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = VK_FORMAT_R8_UINT;
|
||||
image_create_info.extent = image_extent;
|
||||
image_create_info.mipLevels = 1;
|
||||
image_create_info.arrayLayers = 1;
|
||||
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_create_info.usage = VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image_create_info, nullptr, &shading_rate_image.image));
|
||||
VkMemoryRequirements memory_requirements{};
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), shading_rate_image.image, &memory_requirements);
|
||||
|
||||
VkMemoryAllocateInfo memory_allocate_info{};
|
||||
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
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, &shading_rate_image.memory));
|
||||
VK_CHECK(vkBindImageMemory(get_device().get_handle(), shading_rate_image.image, shading_rate_image.memory, 0));
|
||||
|
||||
VkImageViewCreateInfo image_view_create_info{};
|
||||
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
image_view_create_info.image = shading_rate_image.image;
|
||||
image_view_create_info.format = VK_FORMAT_R8_UINT;
|
||||
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.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view_create_info, nullptr, &shading_rate_image.view));
|
||||
|
||||
// Allocate a buffer that stores the shading rates
|
||||
VkDeviceSize buffer_size = image_extent.width * image_extent.height * sizeof(uint8_t);
|
||||
|
||||
// Fragment sizes are encoded in a single texel as follows:
|
||||
// size(w) = 2^((texel/4) & 3)
|
||||
// size(h)h = 2^(texel & 3)
|
||||
|
||||
// Populate the buffer with lowest possible shading rate pattern (4x4)
|
||||
uint8_t val = (4 >> 1) | (4 << 1);
|
||||
uint8_t *shading_rate_pattern_data = new uint8_t[buffer_size];
|
||||
memset(shading_rate_pattern_data, val, buffer_size);
|
||||
|
||||
// Create a circular pattern from the available list of fragment shading rates with decreasing sampling rates outwards (max. range, pattern)
|
||||
std::vector<VkPhysicalDeviceFragmentShadingRateKHR> fragment_shading_rates{};
|
||||
uint32_t fragment_shading_rate_count = 0;
|
||||
vkGetPhysicalDeviceFragmentShadingRatesKHR(get_device().get_gpu().get_handle(), &fragment_shading_rate_count, nullptr);
|
||||
if (fragment_shading_rate_count > 0)
|
||||
{
|
||||
fragment_shading_rates.resize(fragment_shading_rate_count);
|
||||
for (VkPhysicalDeviceFragmentShadingRateKHR &fragment_shading_rate : fragment_shading_rates)
|
||||
{
|
||||
// As per spec, the sType member of each shading rate array entry must be set
|
||||
fragment_shading_rate.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR;
|
||||
}
|
||||
vkGetPhysicalDeviceFragmentShadingRatesKHR(get_device().get_gpu().get_handle(), &fragment_shading_rate_count, fragment_shading_rates.data());
|
||||
}
|
||||
// Shading rates returned by vkGetPhysicalDeviceFragmentShadingRatesKHR are ordered from largest to smallest
|
||||
std::map<float, uint8_t> pattern_lookup = {};
|
||||
float range = 25.0f / static_cast<uint32_t>(fragment_shading_rates.size());
|
||||
float current_range = 8.0f;
|
||||
for (size_t i = fragment_shading_rates.size() - 1; i > 0; i--)
|
||||
{
|
||||
uint32_t rate_v = fragment_shading_rates[i].fragmentSize.width == 1 ? 0 : (fragment_shading_rates[i].fragmentSize.width >> 1);
|
||||
uint32_t rate_h = fragment_shading_rates[i].fragmentSize.height == 1 ? 0 : (fragment_shading_rates[i].fragmentSize.height << 1);
|
||||
pattern_lookup[current_range] = rate_v | rate_h;
|
||||
current_range += range;
|
||||
}
|
||||
|
||||
uint8_t *ptrData = shading_rate_pattern_data;
|
||||
for (uint32_t y = 0; y < image_extent.height; y++)
|
||||
{
|
||||
for (uint32_t x = 0; x < image_extent.width; x++)
|
||||
{
|
||||
const float deltaX = (static_cast<float>(image_extent.width) / 2.0f - static_cast<float>(x)) / image_extent.width * 100.0f;
|
||||
const float deltaY = (static_cast<float>(image_extent.height) / 2.0f - static_cast<float>(y)) / image_extent.height * 100.0f;
|
||||
const float dist = std::sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
for (auto pattern : pattern_lookup)
|
||||
{
|
||||
if (dist < pattern.first)
|
||||
{
|
||||
*ptrData = pattern.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ptrData++;
|
||||
}
|
||||
}
|
||||
|
||||
// Move shading rate pattern data to staging buffer
|
||||
vkb::core::BufferC staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), buffer_size, shading_rate_pattern_data);
|
||||
delete[] shading_rate_pattern_data;
|
||||
|
||||
// Upload the buffer containing the shading rates to the image that'll be used as the shading rate attachment inside our renderpass
|
||||
VkCommandBuffer copy_cmd = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
vkb::image_layout_transition(copy_cmd, shading_rate_image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
VkBufferImageCopy buffer_copy_region = {};
|
||||
buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
buffer_copy_region.imageSubresource.layerCount = 1;
|
||||
buffer_copy_region.imageExtent.width = image_extent.width;
|
||||
buffer_copy_region.imageExtent.height = image_extent.height;
|
||||
buffer_copy_region.imageExtent.depth = 1;
|
||||
vkCmdCopyBufferToImage(copy_cmd, staging_buffer.get_handle(), shading_rate_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_copy_region);
|
||||
|
||||
// Transfer image layout to fragment shading rate attachment layout required to access this in the renderpass
|
||||
vkb::image_layout_transition(
|
||||
copy_cmd, shading_rate_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR);
|
||||
|
||||
get_device().flush_command_buffer(copy_cmd, queue, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* The shading rate image needs to be invalidated and recreated when the frame buffer is resized
|
||||
*/
|
||||
void FragmentShadingRate::invalidate_shading_rate_attachment()
|
||||
{
|
||||
get_device().wait_idle();
|
||||
vkDestroyImageView(get_device().get_handle(), shading_rate_image.view, nullptr);
|
||||
vkDestroyImage(get_device().get_handle(), shading_rate_image.image, nullptr);
|
||||
vkFreeMemory(get_device().get_handle(), shading_rate_image.memory, nullptr);
|
||||
shading_rate_image.view = VK_NULL_HANDLE;
|
||||
shading_rate_image.image = VK_NULL_HANDLE;
|
||||
shading_rate_image.memory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
/*
|
||||
* This sample uses a custom render pass setup, as the shading rate image needs to be passed to the sample's render / sub pass
|
||||
*/
|
||||
void FragmentShadingRate::setup_render_pass()
|
||||
{
|
||||
// Query the fragment shading rate properties of the current implementation, we will need them later on
|
||||
physical_device_fragment_shading_rate_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
|
||||
VkPhysicalDeviceProperties2KHR device_properties{};
|
||||
device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
||||
device_properties.pNext = &physical_device_fragment_shading_rate_properties;
|
||||
vkGetPhysicalDeviceProperties2KHR(get_device().get_gpu().get_handle(), &device_properties);
|
||||
|
||||
std::array<VkAttachmentDescription2KHR, 3> attachments = {};
|
||||
// Color attachment
|
||||
attachments[0].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
|
||||
attachments[0].format = get_render_context().get_format();
|
||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
// Depth attachment
|
||||
attachments[1].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
|
||||
attachments[1].format = depth_format;
|
||||
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
// Fragment shading rate attachment
|
||||
attachments[2].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
|
||||
attachments[2].format = VK_FORMAT_R8_UINT;
|
||||
attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[2].initialLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
|
||||
attachments[2].finalLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
|
||||
|
||||
VkAttachmentReference2KHR color_reference = {};
|
||||
color_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2;
|
||||
color_reference.attachment = 0;
|
||||
color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
color_reference.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
|
||||
VkAttachmentReference2KHR depth_reference = {};
|
||||
depth_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2;
|
||||
depth_reference.attachment = 1;
|
||||
depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
depth_reference.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
// Setup the attachment reference for the shading rate image attachment in slot 2
|
||||
VkAttachmentReference2 fragment_shading_rate_reference = {};
|
||||
fragment_shading_rate_reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2;
|
||||
fragment_shading_rate_reference.attachment = 2;
|
||||
fragment_shading_rate_reference.layout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR;
|
||||
|
||||
// Setup the attachment info for the shading rate image, which will be added to the sub pass via structure chaining (in pNext)
|
||||
VkFragmentShadingRateAttachmentInfoKHR fragment_shading_rate_attachment_info = {};
|
||||
fragment_shading_rate_attachment_info.sType = VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR;
|
||||
fragment_shading_rate_attachment_info.pFragmentShadingRateAttachment = &fragment_shading_rate_reference;
|
||||
fragment_shading_rate_attachment_info.shadingRateAttachmentTexelSize.width = physical_device_fragment_shading_rate_properties.maxFragmentShadingRateAttachmentTexelSize.width;
|
||||
fragment_shading_rate_attachment_info.shadingRateAttachmentTexelSize.height = physical_device_fragment_shading_rate_properties.maxFragmentShadingRateAttachmentTexelSize.height;
|
||||
|
||||
VkSubpassDescription2KHR subpass_description = {};
|
||||
subpass_description.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2;
|
||||
subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass_description.colorAttachmentCount = 1;
|
||||
subpass_description.pColorAttachments = &color_reference;
|
||||
subpass_description.pDepthStencilAttachment = &depth_reference;
|
||||
subpass_description.inputAttachmentCount = 0;
|
||||
subpass_description.pInputAttachments = nullptr;
|
||||
subpass_description.preserveAttachmentCount = 0;
|
||||
subpass_description.pPreserveAttachments = nullptr;
|
||||
subpass_description.pResolveAttachments = nullptr;
|
||||
subpass_description.pNext = &fragment_shading_rate_attachment_info;
|
||||
|
||||
// Subpass dependencies for layout transitions
|
||||
std::array<VkSubpassDependency2KHR, 2> dependencies = {};
|
||||
|
||||
dependencies[0].sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2;
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
dependencies[1].sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2;
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
VkRenderPassCreateInfo2KHR render_pass_create_info = {};
|
||||
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2;
|
||||
render_pass_create_info.attachmentCount = static_cast<uint32_t>(attachments.size());
|
||||
render_pass_create_info.pAttachments = attachments.data();
|
||||
render_pass_create_info.subpassCount = 1;
|
||||
render_pass_create_info.pSubpasses = &subpass_description;
|
||||
render_pass_create_info.dependencyCount = static_cast<uint32_t>(dependencies.size());
|
||||
render_pass_create_info.pDependencies = dependencies.data();
|
||||
|
||||
VK_CHECK(vkCreateRenderPass2KHR(get_device().get_handle(), &render_pass_create_info, nullptr, &render_pass));
|
||||
}
|
||||
|
||||
/*
|
||||
* This sample uses a custom frame buffer setup, that includes the fragment shading rate image attachment
|
||||
*/
|
||||
void FragmentShadingRate::setup_framebuffer()
|
||||
{
|
||||
// Create ths shading rate image attachment if not defined (first run and resize)
|
||||
if (shading_rate_image.image == VK_NULL_HANDLE)
|
||||
{
|
||||
create_shading_rate_attachment();
|
||||
}
|
||||
|
||||
VkImageView attachments[3];
|
||||
// Depth/Stencil attachment is the same for all frame buffers
|
||||
attachments[1] = depth_stencil.view;
|
||||
// Fragment shading rate attachment
|
||||
attachments[2] = shading_rate_image.view;
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_create_info = {};
|
||||
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebuffer_create_info.renderPass = render_pass;
|
||||
framebuffer_create_info.attachmentCount = 3;
|
||||
framebuffer_create_info.pAttachments = attachments;
|
||||
framebuffer_create_info.width = get_render_context().get_surface_extent().width;
|
||||
framebuffer_create_info.height = get_render_context().get_surface_extent().height;
|
||||
framebuffer_create_info.layers = 1;
|
||||
|
||||
// Delete existing frame buffers
|
||||
if (!framebuffers.empty())
|
||||
{
|
||||
for (auto &framebuffer : framebuffers)
|
||||
{
|
||||
if (framebuffer != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyFramebuffer(get_device().get_handle(), framebuffer, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create frame buffers for every swap chain image
|
||||
framebuffers.resize(get_render_context().get_render_frames().size());
|
||||
for (uint32_t i = 0; i < framebuffers.size(); i++)
|
||||
{
|
||||
attachments[0] = swapchain_buffers[i].view;
|
||||
VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, &framebuffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void FragmentShadingRate::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[3];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
clear_values[2].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
|
||||
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 = 3;
|
||||
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));
|
||||
|
||||
VkClearValue clear_values[3];
|
||||
clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
clear_values[2].color = {{0.0f, 0.0f, 0.0f, 0.0f}};
|
||||
|
||||
// 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 = 3;
|
||||
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_layout, 0, 1, &descriptor_set, 0, nullptr);
|
||||
|
||||
// Set the fragment shading rate state for the current pipeline
|
||||
VkExtent2D fragment_size = {1, 1};
|
||||
VkFragmentShadingRateCombinerOpKHR combiner_ops[2];
|
||||
// The combiners determine how the different shading rate values for the pipeline, primitives and attachment are combined
|
||||
if (enable_attachment_shading_rate)
|
||||
{
|
||||
// If shading rate from attachment is enabled, we set the combiner, so that the values from the attachment are used
|
||||
// Combiner for pipeline (A) and primitive (B) - Not used in this sample
|
||||
combiner_ops[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
|
||||
// Combiner for pipeline (A) and attachment (B), replace the pipeline default value (fragment_size) with the fragment sizes stored in the attachment
|
||||
combiner_ops[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If shading rate from attachment is disabled, we keep the value set via the dynamic state
|
||||
combiner_ops[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
|
||||
combiner_ops[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR;
|
||||
}
|
||||
vkCmdSetFragmentShadingRateKHR(draw_cmd_buffers[i], &fragment_size, combiner_ops);
|
||||
|
||||
if (display_skysphere)
|
||||
{
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skysphere);
|
||||
push_const_block.object_type = 0;
|
||||
vkCmdPushConstants(draw_cmd_buffers[i], pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(push_const_block), &push_const_block);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, nullptr);
|
||||
draw_model(models.skysphere, draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.sphere);
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, nullptr);
|
||||
std::vector<glm::vec3> mesh_offsets = {
|
||||
glm::vec3(-2.5f, 0.0f, 0.0f),
|
||||
glm::vec3(0.0f, 0.0f, 0.0f),
|
||||
glm::vec3(2.5f, 0.0f, 0.0f),
|
||||
};
|
||||
for (uint32_t j = 0; j < 3; j++)
|
||||
{
|
||||
push_const_block.object_type = 1;
|
||||
push_const_block.offset = glm::vec4(mesh_offsets[j], 0.0f);
|
||||
vkCmdPushConstants(draw_cmd_buffers[i], pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(push_const_block), &push_const_block);
|
||||
draw_model(models.scene, draw_cmd_buffers[i]);
|
||||
}
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void FragmentShadingRate::load_assets()
|
||||
{
|
||||
models.skysphere = load_model("scenes/geosphere.gltf");
|
||||
textures.skysphere = load_texture("textures/skysphere_rgba.ktx", vkb::sg::Image::Color);
|
||||
models.scene = load_model("scenes/textured_unit_cube.gltf");
|
||||
textures.scene = load_texture("textures/metalplate01_rgba.ktx", vkb::sg::Image::Color);
|
||||
}
|
||||
|
||||
void FragmentShadingRate::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 FragmentShadingRate::setup_descriptor_set_layout()
|
||||
{
|
||||
// Scene rendering 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),
|
||||
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 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_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
|
||||
vkb::initializers::pipeline_layout_create_info(&descriptor_set_layout, 1);
|
||||
|
||||
// Pass object offset and color via push constant
|
||||
VkPushConstantRange push_constant_range = vkb::initializers::push_constant_range(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(push_const_block), 0);
|
||||
pipeline_layout_create_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_create_info.pPushConstantRanges = &push_constant_range;
|
||||
|
||||
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
|
||||
}
|
||||
|
||||
void FragmentShadingRate::setup_descriptor_sets()
|
||||
{
|
||||
// Shared model object 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 scene_buffer_descriptor = create_descriptor(*uniform_buffers.scene);
|
||||
VkDescriptorImageInfo environment_image_descriptor = create_descriptor(textures.skysphere);
|
||||
VkDescriptorImageInfo sphere_image_descriptor = create_descriptor(textures.scene);
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &scene_buffer_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &environment_image_descriptor),
|
||||
vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &sphere_image_descriptor),
|
||||
};
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void FragmentShadingRate::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,
|
||||
// Add fragment shading rate dynamic state, so we can easily toggle this at runtime
|
||||
VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR};
|
||||
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_layout,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
std::vector<VkPipelineColorBlendAttachmentState> blend_attachment_states = {
|
||||
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();
|
||||
|
||||
// Vertex bindings an attributes for model rendering
|
||||
std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
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();
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
|
||||
pipeline_create_info.layout = pipeline_layout;
|
||||
pipeline_create_info.renderPass = render_pass;
|
||||
|
||||
// Skysphere
|
||||
shader_stages[0] = load_shader("fragment_shading_rate", "scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("fragment_shading_rate", "scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipelines.skysphere));
|
||||
|
||||
// Objects
|
||||
// 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.sphere));
|
||||
}
|
||||
|
||||
void FragmentShadingRate::prepare_uniform_buffers()
|
||||
{
|
||||
uniform_buffers.scene = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_scene),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void FragmentShadingRate::update_uniform_buffers()
|
||||
{
|
||||
ubo_scene.projection = camera.matrices.perspective;
|
||||
ubo_scene.modelview = camera.matrices.view * glm::mat4(1.0f);
|
||||
ubo_scene.skysphere_modelview = camera.matrices.view;
|
||||
ubo_scene.color_shading_rate = color_shading_rate;
|
||||
uniform_buffers.scene->convert_and_update(ubo_scene);
|
||||
}
|
||||
|
||||
void FragmentShadingRate::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 FragmentShadingRate::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
camera.type = vkb::CameraType::FirstPerson;
|
||||
camera.set_position(glm::vec3(0.0f, 0.0f, -4.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();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_sets();
|
||||
build_command_buffers();
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FragmentShadingRate::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
draw();
|
||||
if (camera.updated)
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
void FragmentShadingRate::on_update_ui_overlay(vkb::Drawer &drawer)
|
||||
{
|
||||
if (drawer.header("Settings"))
|
||||
{
|
||||
if (drawer.checkbox("Enable attachment shading rate", &enable_attachment_shading_rate))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
if (drawer.checkbox("Color shading rates", &color_shading_rate))
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
if (drawer.checkbox("skysphere", &display_skysphere))
|
||||
{
|
||||
rebuild_command_buffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FragmentShadingRate::resize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
invalidate_shading_rate_attachment();
|
||||
if (!ApiVulkanSample::resize(width, height))
|
||||
{
|
||||
setup_framebuffer();
|
||||
}
|
||||
update_uniform_buffers();
|
||||
rebuild_command_buffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_fragment_shading_rate()
|
||||
{
|
||||
return std::make_unique<FragmentShadingRate>();
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/* Copyright (c) 2020-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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Using variable fragment shading rates from a subpass attachment with VK_KHR_fragment_shading_rate
|
||||
* This sample creates an image that contains different shading rates, which are then sampled during rendering
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class FragmentShadingRate : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
bool enable_attachment_shading_rate = true;
|
||||
bool color_shading_rate = false;
|
||||
bool display_skysphere = true;
|
||||
|
||||
VkPhysicalDeviceFragmentShadingRatePropertiesKHR physical_device_fragment_shading_rate_properties{};
|
||||
VkPhysicalDeviceFragmentShadingRateFeaturesKHR enabled_physical_device_fragment_shading_rate_features{};
|
||||
|
||||
struct ShadingRateImage
|
||||
{
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
} shading_rate_image;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture skysphere;
|
||||
Texture scene;
|
||||
} textures;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::sg::SubMesh> skysphere;
|
||||
std::unique_ptr<vkb::sg::SubMesh> scene;
|
||||
} models;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> scene;
|
||||
} uniform_buffers;
|
||||
|
||||
struct UBOScene
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skysphere_modelview;
|
||||
int32_t color_shading_rate;
|
||||
} ubo_scene;
|
||||
|
||||
VkPipelineLayout pipeline_layout;
|
||||
struct Pipelines
|
||||
{
|
||||
VkPipeline skysphere;
|
||||
VkPipeline sphere;
|
||||
} pipelines;
|
||||
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
VkDescriptorSet descriptor_set;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::vec4 offset;
|
||||
uint32_t object_type;
|
||||
} push_const_block;
|
||||
|
||||
FragmentShadingRate();
|
||||
~FragmentShadingRate();
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void create_shading_rate_attachment();
|
||||
void invalidate_shading_rate_attachment();
|
||||
void setup_render_pass() override;
|
||||
void setup_framebuffer() override;
|
||||
void build_command_buffers() override;
|
||||
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 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::VulkanSampleC> create_fragment_shading_rate();
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (c) 2021, Holochip
|
||||
#
|
||||
# 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 "Holochip"
|
||||
NAME "Dynamic fragment shading rate"
|
||||
DESCRIPTION "Using dynamically-generated fragment shading rate patterns generated each frame")
|
||||
@@ -1,96 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2022-2023, Holochip
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
|
||||
= Fragment Shading Rate
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/fragment_shading_rate_dynamic[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
The KHR fragment shading rate extension introduces the ability to selectively render at different sample rates within the same rendered image.
|
||||
This can be useful when rendering at very high resolutions or when the frequency content is not evenly spread through the rendered image.
|
||||
This tutorial demonstrates one way of controlling that sample rate by estimating the frequency content of each pixel of the rendered image.
|
||||
|
||||
The fragment shading rate extension can be enabled through the https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_fragment_shading_rate.html[`VK_KHR_fragment_shading_rate`] device extension and the https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceFragmentShadingRateFeaturesKHR.html[`VkPhysicalDeviceFragmentShadingRateFeaturesKHR`] device features.
|
||||
This sample demonstrates the attachment capability, in which the render pass directly references the shading rate image through a https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkFragmentShadingRateAttachmentInfoKHR.html[`VkFragmentShadingRateAttachmentInfoKHR`] struct attached to the `.pNext` of a sub-pass.
|
||||
|
||||
The image below shows the scene from this sample.
|
||||
Note the areas of high image variation in the center of each cube face, and the areas of low image variation in the sky and plain corners of each cube face.
|
||||
|
||||
image::./rendered.png[Rendered image]
|
||||
|
||||
== Shading Rate Image
|
||||
|
||||
When used as an attachment, each pixel within the shading rate image controls a "texel", or fixed region within the output image, specified by `shadingRateAttachmentTexelSize`.
|
||||
For example, each pixel in the shading rate image might control the shading rate of a 4x4 texel within the rendered image, since all output pixels of the texel are shaded at the same rate, the shading rate image has a lower resolution.
|
||||
The number and type of possible shading rates is controlled by the device and can be queried through the https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetPhysicalDeviceFragmentShadingRatesKHR.html[`vkGetPhysicalDeviceFragmentShadingRatesKHR`] function, and may include shading rates that vary in both the x- and y-directions, for instance 1x2 or 4x2 pixel texels.
|
||||
These supported shading rate values are provided to the "compute shader" when determining the optimal shading rate.
|
||||
|
||||
This sample demonstrates how to use a dynamic shading rate that responds to the frequency content of the image.
|
||||
To achieve this, an attachment is added to the fragment shader at `location=1`, where the x- and y- derivatives are estimated using the `dFdx` and `dFdy` functions.
|
||||
A separate compute shader then processes all the pixels within the texel of the derivative image to determine the new fragment shading rate for the next image: texels with higher squared derivatives calculated using `dFdx` and `dFdy` will be afforded a higher shading rate during the next frame compared to texels with lower derivatives.
|
||||
|
||||
The frequency information is visualized below and can be accessed by selecting "Frequency" in the data visualization dropdown in the sample GUI.
|
||||
|
||||
image::./frequency.png[Frequency information]
|
||||
|
||||
== Encoding the Shading Rate
|
||||
|
||||
Shading rates are encoded using 8-bit unsigned integers.
|
||||
For instance, the lowest shading rate of a 4x4 texel is given by: `(4>>1) | (4+++<<+++1)`, and the general case of a `rate_x` x `rate_y` texel is `(rate_x>>1)|(rate_y+++<<+++1)`.
|
||||
Although devices vary in their supported texel sizes, the maximum texel size is guaranteed to be no larger than 4x4.
|
||||
|
||||
The image below shows the shading rate image, where lighter colors indicate areas that will have a high sample density.
|
||||
This visualization can be selected by choosing the "Shading Rates" dropdown selection in the sample GUI.
|
||||
|
||||
image::./shading_rate.png[Shading rate image]
|
||||
|
||||
== Implementation Details
|
||||
|
||||
This sample introduces a separate compute pipeline and several images to achieve dynamic shading rate:
|
||||
|
||||
. `shading_rate_image`, the input image to the fragment shader to control shading rate,
|
||||
. `frequency_content_image`, the output attachment from the fragment shader to record the derivatives at each pixel,
|
||||
. `shading_rate_image_compute`, the output image from the "compute shader"
|
||||
|
||||
The `frequency_content_image` has type `vec2` to store the squared x- and y-derivatives, which are used by the "compute shader" to estimate the desired shading rate.
|
||||
Once the desired shading rate is estimated, the "compute shader" iterates through the supported shading rates to find the one closest to the desired.
|
||||
This is converted to the 8-bit representation of the format of the shading rate image:
|
||||
|
||||
----
|
||||
uint rate_code = uint(optimal_rate_x >> 1) | (optimal_rate_y << 1);
|
||||
----
|
||||
|
||||
This rate code is placed into `shading_rate_image_compute`.
|
||||
|
||||
Because of the device format requirements of the fragment shading rate extension (and the incompatibility of usage flags), the `shading_rate_image` and `shading_rate_image_compute` images are separate and have different usage flags: `VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR` for the shading rate attachment image and `VK_IMAGE_USAGE_STORAGE_BIT` for the "compute" image (in addition to transfer bits).
|
||||
However, their content is identical, and after the "compute shader" has completed, the contents from `shading_rate_image_compute` are copied to `shading_rate_image`.
|
||||
|
||||
== Calculating Frequency Information in a Separate Renderpass
|
||||
|
||||
image::./diagram.png[Diagram of renderpass]
|
||||
|
||||
One problem with calculating the frequency information during the render pass is that each frame is using the previous frame's shading rate, which results in a feedback loop that lead to unstable or "stutter" in the calculated frequency information.
|
||||
To prevent this problem, this sample introduces a separate renderpass that calculates the frequency information without using the shading rate attachment.
|
||||
To maintain performance, this renderpass is performed at a lower resolution controllable by the "Subpass size reduction" option in the sample GUI.
|
||||
In production systems, MSAA can also be disabled.
|
||||
|
||||
For instance, at a 4x4 reduction with MSAA disabled, the frequency information calculation is performed over only 1/128 as many samples as the full resolution with 8 MSAA samples.
|
||||
If the resulting shading rate image is used to reduce the full-resolution samples by half, then the total sample reduction is still greater than 40%.
|
||||
|
Before Width: | Height: | Size: 94 KiB |
@@ -1,152 +0,0 @@
|
||||
/* Copyright (c) 2021-2024, Holochip
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This sample is demonstrates using a dynamic shading rate map generated each frame based on the
|
||||
* frequency content of the previous frame.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
|
||||
class FragmentShadingRateDynamic : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
FragmentShadingRateDynamic();
|
||||
~FragmentShadingRateDynamic() override;
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
bool resize(uint32_t new_width, uint32_t new_height) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
void render(float delta_time) override;
|
||||
void build_command_buffers() override;
|
||||
void setup_framebuffer() override;
|
||||
void setup_render_pass() override;
|
||||
void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
|
||||
private:
|
||||
void create_shading_rate_attachment();
|
||||
void invalidate_shading_rate_attachment();
|
||||
void load_assets();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void setup_descriptor_set_layout();
|
||||
void prepare_pipelines();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_sets();
|
||||
void create_compute_pipeline();
|
||||
void update_compute_pipeline();
|
||||
void draw();
|
||||
|
||||
bool enable_attachment_shading_rate = true;
|
||||
bool display_sky_sphere = true;
|
||||
bool debug_utils_supported = false;
|
||||
|
||||
VkPhysicalDeviceFragmentShadingRatePropertiesKHR physical_device_fragment_shading_rate_properties{};
|
||||
std::vector<VkPhysicalDeviceFragmentShadingRateKHR> fragment_shading_rates{};
|
||||
VkRenderPass fragment_render_pass{VK_NULL_HANDLE};
|
||||
std::vector<VkFramebuffer> fragment_framebuffers;
|
||||
VkCommandPool command_pool{VK_NULL_HANDLE};
|
||||
|
||||
// Shading rate image is an input to the graphics pipeline
|
||||
// and is produced by the "compute shader."
|
||||
// It has a lower resolution than the framebuffer
|
||||
struct ComputeBuffers
|
||||
{
|
||||
std::unique_ptr<vkb::core::Image> shading_rate_image;
|
||||
std::unique_ptr<vkb::core::ImageView> shading_rate_image_view;
|
||||
|
||||
// Frequency content image is an output of the graphics pipeline
|
||||
// and is consumed by the "compute shader" to produce the shading rate image.
|
||||
// It has the same resolution as the framebuffer
|
||||
std::unique_ptr<vkb::core::Image> frequency_content_image;
|
||||
std::unique_ptr<vkb::core::ImageView> frequency_content_image_view;
|
||||
|
||||
std::unique_ptr<vkb::core::Image> shading_rate_image_compute;
|
||||
std::unique_ptr<vkb::core::ImageView> shading_rate_image_compute_view;
|
||||
|
||||
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
||||
};
|
||||
std::vector<ComputeBuffers> compute_buffers;
|
||||
std::vector<VkCommandBuffer> small_command_buffers;
|
||||
VkExtent2D subpass_extent;
|
||||
uint32_t subpass_extent_ratio = 4;
|
||||
|
||||
VkFence compute_fence{VK_NULL_HANDLE};
|
||||
|
||||
struct FrequencyInformation
|
||||
{
|
||||
glm::uvec2 frame_dimensions;
|
||||
glm::uvec2 shading_rate_dimensions;
|
||||
glm::uvec2 max_rates;
|
||||
uint32_t n_rates;
|
||||
uint32_t _pad;
|
||||
};
|
||||
std::unique_ptr<vkb::core::BufferC> frequency_information_params;
|
||||
|
||||
struct
|
||||
{
|
||||
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
|
||||
} compute;
|
||||
|
||||
struct
|
||||
{
|
||||
Texture skysphere;
|
||||
Texture scene;
|
||||
} textures;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::sg::SubMesh> skysphere;
|
||||
std::unique_ptr<vkb::sg::SubMesh> scene;
|
||||
} models;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<vkb::core::BufferC> scene;
|
||||
} uniform_buffers;
|
||||
|
||||
struct UBOScene
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 modelview;
|
||||
glm::mat4 skysphere_modelview;
|
||||
int32_t color_shading_rate;
|
||||
} ubo_scene;
|
||||
|
||||
VkPipelineLayout pipeline_layout;
|
||||
struct Pipelines
|
||||
{
|
||||
VkPipeline skysphere;
|
||||
VkPipeline sphere;
|
||||
} pipelines;
|
||||
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
std::vector<VkDescriptorSet> render_descriptor_sets;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::vec4 offset;
|
||||
uint32_t object_type;
|
||||
} push_const_block;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_fragment_shading_rate_dynamic();
|
||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2023, 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.
|
||||
#
|
||||
|
||||
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)
|
||||
|
||||
#Full Screen exclusive is a windows only supported extension. We'll disable finding the sample on other platforms.
|
||||
if (WIN32)
|
||||
# Windows 11 fixed the reason for using full screen exclusive.
|
||||
# We'll test for that here and not list the sample unless it's a candidate version of windows.
|
||||
if (CMAKE_SYSTEM_VERSION)
|
||||
set(ver ${CMAKE_SYSTEM_VERSION})
|
||||
string(REGEX MATCH "^([0-9]+)" verMajor ${ver})
|
||||
if ("${verMajor}" LESS_EQUAL "10")
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Holochip Corporation"
|
||||
NAME "Full Screen Exclusive"
|
||||
DESCRIPTION "Demonstrates and showcases full_screen_exclusive related functionalities."
|
||||
SHADER_FILES_GLSL
|
||||
"triangle.vert"
|
||||
"triangle.frag")
|
||||
endif ()
|
||||
endif ()
|
||||
endif ()
|
||||
@@ -1,46 +0,0 @@
|
||||
////
|
||||
- Copyright (c) 2023, 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.
|
||||
-
|
||||
////
|
||||
= Full Screen Exclusive
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/full_screen_exclusive[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
This code sample demonstrates how to incorporate the Vulkan extension `VK_EXT_full_screen_exclusive`.
|
||||
This extension provides a solution for the full screen exclusion issue on Windows prior to the 11 version.
|
||||
Windows prior to 11 cannot correctly get an exclusive full screen window, `VK_EXT_full_screen_exclusive` is applicable on Windows prior to version 11 platform alone.
|
||||
|
||||
== Introduction
|
||||
|
||||
This sample provides a detailed procedure to activate full screen exclusive mode on Windows applications.
|
||||
Users can switch display modes from: 1) windowed, 2) borderless fullscreen, and 3) exclusive fullscreen using keyboard inputs.
|
||||
|
||||
== *Reminder
|
||||
|
||||
Configuring the `swapchain create info` using full screen exclusive extension *DOES NOT* automatically set the application window to full screen mode.
|
||||
The following procedure shows how to activate full screen exclusive mode correctly:
|
||||
|
||||
1) recreate the `swapchain` using `full screen exclusive`.
|
||||
2) recreate the `frame buffers` with the new `swapchain`.
|
||||
3) configure the application window to *fullscreen mode* 4) execute the `acquire full screen exclusive EXT` call.
|
||||
|
||||
* More details can be found in the link: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow[MonitorFromWindow function (winuser.h)]
|
||||