diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc index 69e0e59a90..c5e5d21980 100644 --- a/antora/modules/ROOT/nav.adoc +++ b/antora/modules/ROOT/nav.adoc @@ -1,5 +1,5 @@ //// -- Copyright (c) 2023, Holochip Inc +- Copyright (c) 2023-2024, Holochip Inc - Copyright (c) 2023-2024, Sascha Willems - - SPDX-License-Identifier: Apache-2.0 @@ -56,6 +56,7 @@ ** xref:samples/extensions/descriptor_buffer_basic/README.adoc[Descriptor buffer basic] ** xref:samples/extensions/descriptor_indexing/README.adoc[Descriptor indexing] ** xref:samples/extensions/dynamic_line_rasterization/README.adoc[Dynamic line rasterization] +** xref:samples/extensions/dynamic_primitive_clipping/README.adoc[Dynamic primitive clipping] ** xref:samples/extensions/dynamic_rendering/README.adoc[Dynamic rendering] ** xref:samples/extensions/extended_dynamic_state2/README.adoc[Extended dynamic state2] ** xref:samples/extensions/fragment_shader_barycentric/README.adoc[Fragment shader barycentric] diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index fd0d73f6cc..255905cf1d 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -90,6 +90,7 @@ set(ORDER_LIST "gshader_to_mshader" "color_write_enable" "sparse_image" + "dynamic_primitive_clipping" #Performance Samples "swapchain_images" diff --git a/samples/extensions/README.adoc b/samples/extensions/README.adoc index ea7576e473..42c88e6e85 100644 --- a/samples/extensions/README.adoc +++ b/samples/extensions/README.adoc @@ -266,4 +266,10 @@ Demonstrate methods for dynamically customizing the appearance of the rendered l *Extension*: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_non_semantic_info.html[`VK_KHR_shader_non_semantic_info`] -Demonstrates how to use https://en.wikipedia.org/wiki/Printf[Printf] statements in a shader to output per-invocation values. This can help find issues with shaders in combination with graphics debugging tools. \ No newline at end of file +Demonstrates how to use https://en.wikipedia.org/wiki/Printf[Printf] statements in a shader to output per-invocation values. This can help find issues with shaders in combination with graphics debugging tools. + +=== xref:./{extension_samplespath}dynamic_primitive_clipping/README.adoc[Dynamic depth clipping and primitive clipping] + +*Extension:* https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_extended_dynamic_state3.html[`VK_EXT_extended_dynamic_state3`] + +Rendering using primitive clipping and depth clipping configured by dynamic pipeline state. diff --git a/samples/extensions/dynamic_primitive_clipping/CMakeLists.txt b/samples/extensions/dynamic_primitive_clipping/CMakeLists.txt new file mode 100644 index 0000000000..9f97b21880 --- /dev/null +++ b/samples/extensions/dynamic_primitive_clipping/CMakeLists.txt @@ -0,0 +1,30 @@ +# 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/primitive_clipping.vert" + "dynamic_primitive_clipping/primitive_clipping.frag") diff --git a/samples/extensions/dynamic_primitive_clipping/README.adoc b/samples/extensions/dynamic_primitive_clipping/README.adoc new file mode 100644 index 0000000000..59842db4e4 --- /dev/null +++ b/samples/extensions/dynamic_primitive_clipping/README.adoc @@ -0,0 +1,122 @@ +//// +- 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. diff --git a/samples/extensions/dynamic_primitive_clipping/dynamic_primitive_clipping.cpp b/samples/extensions/dynamic_primitive_clipping/dynamic_primitive_clipping.cpp new file mode 100644 index 0000000000..a070ab81a4 --- /dev/null +++ b/samples/extensions/dynamic_primitive_clipping/dynamic_primitive_clipping.cpp @@ -0,0 +1,393 @@ +/* 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 + */ + +#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(width) / static_cast(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(). + { + auto &features = gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT); + features.depthClipEnable = VK_TRUE; + } + { + auto &features = gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT); + features.extendedDynamicState3DepthClipEnable = VK_TRUE; + } +} + +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(width), static_cast(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 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 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(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 vertex_input_bindings = { + vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), + }; + + // Attribute descriptions. + std::vector 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(vertex_input_bindings.size()); + vertex_input.pVertexBindingDescriptions = vertex_input_bindings.data(); + vertex_input.vertexAttributeDescriptionCount = static_cast(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 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 shader_stages{}; + shader_stages[0] = load_shader("dynamic_primitive_clipping/primitive_clipping.vert", VK_SHADER_STAGE_VERTEX_BIT); + shader_stages[1] = load_shader("dynamic_primitive_clipping/primitive_clipping.frag", 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(get_device(), + sizeof(UBOVS), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + uniform_buffers.buffer_negative = std::make_unique(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 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(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 descriptor_buffer_infos = { + create_descriptor(*uniform_buffers.buffer_positive), + create_descriptor(*uniform_buffers.buffer_negative)}; + std::vector 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(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL); +} + +std::unique_ptr> create_dynamic_primitive_clipping() +{ + return std::make_unique(); +} diff --git a/samples/extensions/dynamic_primitive_clipping/dynamic_primitive_clipping.h b/samples/extensions/dynamic_primitive_clipping/dynamic_primitive_clipping.h new file mode 100644 index 0000000000..7c546ba8eb --- /dev/null +++ b/samples/extensions/dynamic_primitive_clipping/dynamic_primitive_clipping.h @@ -0,0 +1,106 @@ +/* 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> objects; + std::vector transforms; + int32_t object_index = 0; + } models; + + std::vector model_names; + std::vector 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 buffer_positive; + std::unique_ptr 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> create_dynamic_primitive_clipping(); diff --git a/samples/extensions/dynamic_primitive_clipping/screenshot.png b/samples/extensions/dynamic_primitive_clipping/screenshot.png new file mode 100644 index 0000000000..45c0a2b420 Binary files /dev/null and b/samples/extensions/dynamic_primitive_clipping/screenshot.png differ diff --git a/shaders/dynamic_primitive_clipping/primitive_clipping.frag b/shaders/dynamic_primitive_clipping/primitive_clipping.frag new file mode 100644 index 0000000000..b1b2ad4a9c --- /dev/null +++ b/shaders/dynamic_primitive_clipping/primitive_clipping.frag @@ -0,0 +1,38 @@ +#version 450 +/* 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. + */ + + +layout (location = 0) in vec3 inNormal; + +layout (binding = 0) uniform Ubo +{ + mat4 projection; + mat4 view; + mat4 model; + vec4 colorTransformation; + ivec2 sceneTransformation; +} ubo; + +layout (location = 0) out vec4 outColor; + +void main() +{ + outColor = vec4( 0.5 * inNormal + vec3(0.5), 1); + + outColor.xyz = ubo.colorTransformation.x * outColor.xyz + vec3(ubo.colorTransformation.y); +} diff --git a/shaders/dynamic_primitive_clipping/primitive_clipping.vert b/shaders/dynamic_primitive_clipping/primitive_clipping.vert new file mode 100644 index 0000000000..190d9e6ac7 --- /dev/null +++ b/shaders/dynamic_primitive_clipping/primitive_clipping.vert @@ -0,0 +1,109 @@ +#version 450 +/* 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. + */ + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec3 inNormal; +layout (location = 2) in vec2 inUV; + +// In a fixed pipeline approach which you can still find in OpenGL implementations you could define +// up to maximum 6 clipping half-spaces and a distance to each clipping half-space was stored in gl_ClipDistance. +// That's why gl_ClipDistance[] is an array which maximum size is limited by maxClipDistances. +// +// In our example we show you how to define such half-spaces using plane equation: Ax+By+Cz+D=0 +// You could transmit information about values of A,B,C and D using UBO for example, but for simplicity we will use const declaration: + +const vec4 planeValues = vec4(0.0, 1.0, 0.0, 0.0); + +// Additionally we will show you that you can use more than half-spaces only. +// We will use some more advanced math functions to calculate gl_ClipDistance. + +out gl_PerVertex +{ + vec4 gl_Position; + float gl_ClipDistance[1]; +}; + +layout (binding = 0) uniform Ubo +{ + mat4 projection; + mat4 view; + mat4 model; + vec4 colorTransformation; + ivec2 sceneTransformation; + float usePrimitiveClipping; +} ubo; + +layout (location = 0) out vec3 outNormal; + +// Cases 0 and 1 present how to use world space coordinates with more advanced functions +// Case 2 shows how to use half-space in world space coordinates +// Case 3 shows how to use half-space in clip space coordinates +// Cases 4-6 present how to use clip space coordinates with more advanced functions +// Cases 0,1,4,5 use sin() function to create strips in which values of gl_ClipDistance below 0 cause triangle primitives +// to be clipped according to Vulkan specification chapter 27.4 +// Cases 6-8 use different types of distance functions from center of the screen ( Euclidean, Manhattan, Chebyshev ) + +void main() +{ + vec4 worldPosition = ubo.model * vec4(inPos, 1.0); + gl_Position = ubo.projection * ubo.view * worldPosition; + + float clipResult = 1.0, tmp; + float distance = 0.4; + + // Primitive clipping does not have any vkCmd* command that turns it off. + // If we want to turn it off - we have to transfer this information to shader through UBO variable and + // then use code similar to this: + if(ubo.usePrimitiveClipping > 0.0) + { + switch(ubo.sceneTransformation.x) + { + case 0: + clipResult = sin(worldPosition.x * 0.1 * 2.0 * 3.1415); + break; + case 1: + clipResult = sin(worldPosition.y * 0.1 * 2.0 * 3.1415); + break; + case 2: + clipResult = dot(worldPosition, planeValues ); + break; + case 3: + clipResult = dot(gl_Position, planeValues); + break; + case 4: + clipResult = sin(gl_Position.x / gl_Position.w * 3.0 * 2.0 * 3.1415); + break; + case 5: + clipResult = sin(gl_Position.y / gl_Position.w * 3.0 * 2.0 * 3.1415); + break; + case 6: + clipResult = (gl_Position.x*gl_Position.x + gl_Position.y*gl_Position.y) / (gl_Position.w*gl_Position.w) - distance*distance; + break; + case 7: + clipResult = (abs(gl_Position.x) + abs(gl_Position.y)) / gl_Position.w - distance; + break; + case 8: + clipResult = max(abs(gl_Position.x), abs(gl_Position.y)) / gl_Position.w - distance; + break; + } + + gl_ClipDistance[0] = clipResult * float(ubo.sceneTransformation.y); + } + + outNormal = normalize(mat3(ubo.view * ubo.model) * inNormal); +}