/* 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 #include #include #include "extended_dynamic_state2.h" #include "gltf_loader.h" #include "scene_graph/components/mesh.h" #include "scene_graph/components/sub_mesh.h" ExtendedDynamicState2::ExtendedDynamicState2() { title = "Extended Dynamic State2"; add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); add_device_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); add_device_extension(VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME); } ExtendedDynamicState2::~ExtendedDynamicState2() { if (has_device()) { uniform_buffers.common.reset(); uniform_buffers.baseline.reset(); uniform_buffers.tesselation.reset(); vkDestroySampler(get_device().get_handle(), textures.envmap.sampler, VK_NULL_HANDLE); textures = {}; vkDestroyPipeline(get_device().get_handle(), pipeline.tesselation, VK_NULL_HANDLE); vkDestroyPipeline(get_device().get_handle(), pipeline.baseline, VK_NULL_HANDLE); vkDestroyPipeline(get_device().get_handle(), pipeline.background, VK_NULL_HANDLE); vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.tesselation, VK_NULL_HANDLE); vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.baseline, VK_NULL_HANDLE); vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layouts.background, VK_NULL_HANDLE); vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.tesselation, VK_NULL_HANDLE); vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.baseline, VK_NULL_HANDLE); vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layouts.background, VK_NULL_HANDLE); vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool, VK_NULL_HANDLE); } } /** * @fn bool ExtendedDynamicState2::prepare(const vkb::ApplicationOptions &options) * @brief Configuring all sample specific settings, creating descriptor sets/pool, pipelines, generating or loading models etc. */ bool ExtendedDynamicState2::prepare(const vkb::ApplicationOptions &options) { if (!ApiVulkanSample::prepare(options)) { return false; } camera.type = vkb::CameraType::LookAt; camera.set_position({2.0f, -4.0f, -10.0f}); camera.set_rotation({-15.0f, 190.0f, 0.0f}); camera.set_perspective(60.0f, static_cast(width) / static_cast(height), 256.0f, 0.1f); load_assets(); model_data_creation(); prepare_uniform_buffers(); create_descriptor_pool(); setup_descriptor_set_layout(); create_descriptor_sets(); create_pipelines(); build_command_buffers(); prepared = true; return true; } /** * @fn void ExtendedDynamicState2::load_assets() * @brief Loading extra models, textures from assets */ void ExtendedDynamicState2::load_assets() { load_scene("scenes/primitives/primitives.gltf"); std::vector scene_elements; std::vector node_scene_list = {&(get_scene().get_root_node())}; vkb::sg::Node *node = nullptr; for (size_t list_it = 0; node_scene_list.size() > list_it; ++list_it) { node = node_scene_list[list_it]; // Add node children to vector if they exist if (!node->get_children().empty()) { node_scene_list.insert( node_scene_list.end(), node->get_children().begin(), node->get_children().end()); } // If current node have mesh add it to scene_elements if (node->has_component()) { vkb::sg::Mesh &mesh = node->get_component(); ModelDynamicParam object_param{}; gui_settings.objects.push_back(object_param); for (auto &sub_mesh : mesh.get_submeshes()) { scene_elements.push_back({mesh.get_name(), node, sub_mesh}); } } } /* Split scene */ scene_pipeline_divide(scene_elements); background_model = load_model("scenes/cube.gltf"); /* Load HDR cube map */ textures.envmap = load_texture_cubemap("textures/uffizi_rgba16f_cube.ktx", vkb::sg::Image::Color); } /** * @fn void ExtendedDynamicState2::draw() * @brief Preparing frame and submitting it to the present queue */ void ExtendedDynamicState2::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 ExtendedDynamicState2::render(float delta_time) * @brief Drawing frames and/or updating uniform buffers when camera position/rotation was changed */ void ExtendedDynamicState2::render(float delta_time) { if (!prepared) { return; } draw(); if (camera.updated) { update_uniform_buffers(); } } /** * @fn void ExtendedDynamicState2::prepare_uniform_buffers() * @brief Preparing uniform buffer (setting bits) and updating UB data */ void ExtendedDynamicState2::prepare_uniform_buffers() { uniform_buffers.common = std::make_unique(get_device(), sizeof(ubo_common), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); uniform_buffers.baseline = std::make_unique(get_device(), sizeof(ubo_baseline), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); uniform_buffers.tesselation = std::make_unique(get_device(), sizeof(ubo_tess), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); update_uniform_buffers(); } /** * @fn void ExtendedDynamicState2::update_uniform_buffers() * @brief Updating data from application to GPU uniform buffer */ void ExtendedDynamicState2::update_uniform_buffers() { /* Common uniform buffer */ ubo_common.projection = camera.matrices.perspective; ubo_common.view = camera.matrices.view; uniform_buffers.common->convert_and_update(ubo_common); /* Baseline uniform buffer */ uniform_buffers.baseline->convert_and_update(ubo_baseline); /* Tessellation uniform buffer */ ubo_tess.tessellation_factor = gui_settings.tess_factor; if (!gui_settings.tessellation) { // Setting this to zero sets all tessellation factors to 1.0 in the shader ubo_tess.tessellation_factor = 0.0f; } uniform_buffers.tesselation->convert_and_update(ubo_tess); } /** * @fn void ExtendedDynamicState2::create_pipelines() * @brief Creating graphical pipelines: baseline, background, tessellation. */ void ExtendedDynamicState2::create_pipelines() { /* Setup for first 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_CLOCKWISE, 0); rasterization_state.depthBiasConstantFactor = 1.0f; rasterization_state.depthBiasSlopeFactor = 1.0f; 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); /* 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); VkPipelineTessellationStateCreateInfo tessellation_state = vkb::initializers::pipeline_tessellation_state_create_info(3); std::vector 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(dynamic_state_enables.size()), 0); /* Vertex bindings and attributes for model rendering */ /* Binding description */ std::vector 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 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(vertex_input_bindings.size()); vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data(); vertex_input_state.vertexAttributeDescriptionCount = static_cast(vertex_input_attributes.size()); vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data(); std::array shader_stages{}; shader_stages[0] = load_shader("extended_dynamic_state2/baseline.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shader_stages[1] = load_shader("extended_dynamic_state2/baseline.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 = 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.pTessellationState = VK_NULL_HANDLE; graphics_create.stageCount = 2; graphics_create.pStages = shader_stages.data(); graphics_create.layout = pipeline_layouts.baseline; graphics_create.pNext = VK_NULL_HANDLE; graphics_create.renderPass = render_pass; VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipeline.baseline)); /* Setup for second pipeline */ graphics_create.layout = pipeline_layouts.background; std::vector dynamic_state_enables_background = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; dynamic_state.pDynamicStates = dynamic_state_enables_background.data(); dynamic_state.dynamicStateCount = static_cast(dynamic_state_enables_background.size()); /* Binding description */ std::vector vertex_input_bindings_background = { vkb::initializers::vertex_input_binding_description(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), }; /* Attribute descriptions */ std::vector vertex_input_attributes_background = { 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, offsetof(Vertex, normal)), // Normal }; vertex_input_state.vertexBindingDescriptionCount = static_cast(vertex_input_bindings_background.size()); vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings_background.data(); vertex_input_state.vertexAttributeDescriptionCount = static_cast(vertex_input_attributes_background.size()); vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes_background.data(); rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; shader_stages[0] = load_shader("extended_dynamic_state2/background.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shader_stages[1] = load_shader("extended_dynamic_state2/background.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &graphics_create, VK_NULL_HANDLE, &pipeline.background)); /* Setup for third pipeline */ graphics_create.pTessellationState = &tessellation_state; graphics_create.layout = pipeline_layouts.tesselation; input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; dynamic_state_enables.push_back(VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT); dynamic_state.pDynamicStates = dynamic_state_enables.data(); dynamic_state.dynamicStateCount = static_cast(dynamic_state_enables.size()); vertex_input_state.vertexBindingDescriptionCount = static_cast(vertex_input_bindings.size()); vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data(); vertex_input_state.vertexAttributeDescriptionCount = static_cast(vertex_input_attributes.size()); vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data(); /* Wireframe mode */ if (get_device().get_gpu().get_features().fillModeNonSolid) { rasterization_state.polygonMode = VK_POLYGON_MODE_LINE; } shader_stages[0] = load_shader("extended_dynamic_state2/tess.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shader_stages[1] = load_shader("extended_dynamic_state2/tess.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); shader_stages[2] = load_shader("extended_dynamic_state2/tess.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT); shader_stages[3] = load_shader("extended_dynamic_state2/tess.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT); graphics_create.stageCount = static_cast(shader_stages.size()); graphics_create.pStages = shader_stages.data(); /* 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, &graphics_create, VK_NULL_HANDLE, &pipeline.tesselation)); } /** * @fn void ExtendedDynamicState2::build_command_buffers() * @brief Creating command buffers and drawing particular elements on window. * @details Drawing object list: * - models from baseline scene * - model from tessellation scene * - background model * - primitive restart model */ void ExtendedDynamicState2::build_command_buffers() { std::array clear_values{}; clear_values[0].color = {{0.0f, 0.0f, 0.0f, 0.0f}}; clear_values[1].depthStencil = {0.0f, 0}; constexpr uint32_t patch_control_points_triangle = 3; /* Geosphere model is based on triangle patches */ int i = -1; /* Required for accessing element in framebuffers vector */ 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(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(width), static_cast(height), 0.0f, 1.0f); vkCmdSetViewport(draw_cmd_buffer, 0, 1, &viewport); VkRect2D scissor = vkb::initializers::rect2D(static_cast(width), static_cast(height), 0, 0); vkCmdSetScissor(draw_cmd_buffer, 0, 1, &scissor); /* Binding background pipeline and descriptor sets */ 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); /* Drawing background */ draw_model(background_model, draw_cmd_buffer); /* Changing bindings to 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); /* UI */ draw_ui(draw_cmd_buffer); vkCmdEndRenderPass(draw_cmd_buffer); VK_CHECK(vkEndCommandBuffer(draw_cmd_buffer)); } } /** * @fn void ExtendedDynamicState2::create_descriptor_pool() * @brief Creating descriptor pool with size adjusted to use uniform buffer and image sampler */ void ExtendedDynamicState2::create_descriptor_pool() { std::vector pool_sizes = { vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 5), vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1), }; VkDescriptorPoolCreateInfo descriptor_pool_create_info = vkb::initializers::descriptor_pool_create_info( static_cast(pool_sizes.size()), pool_sizes.data(), 3); VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool)); } /** * @fn void ExtendedDynamicState2::setup_descriptor_set_layout() * @brief Creating layout for descriptor sets */ void ExtendedDynamicState2::setup_descriptor_set_layout() { /* First descriptor set */ std::vector set_layout_bindings = { vkb::initializers::descriptor_set_layout_binding( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), vkb::initializers::descriptor_set_layout_binding( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 1), }; 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.baseline)); VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info( &descriptor_set_layouts.baseline, 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_layouts.baseline)); /* Second descriptor set */ set_layout_bindings = { vkb::initializers::descriptor_set_layout_binding( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_VERTEX_BIT, 0), vkb::initializers::descriptor_set_layout_binding( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, 1), }; descriptor_layout_create_info.pBindings = set_layout_bindings.data(); descriptor_layout_create_info.bindingCount = static_cast(set_layout_bindings.size()); VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layouts.tesselation)); pipeline_layout_create_info.pSetLayouts = &descriptor_set_layouts.tesselation; pipeline_layout_create_info.setLayoutCount = 1; push_constant_range.stageFlags = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.tesselation)); /* Third descriptor set */ 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), }; descriptor_layout_create_info.pBindings = set_layout_bindings.data(); descriptor_layout_create_info.bindingCount = static_cast(set_layout_bindings.size()); VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &descriptor_set_layouts.background)); pipeline_layout_create_info.pSetLayouts = &descriptor_set_layouts.background; pipeline_layout_create_info.setLayoutCount = 1; pipeline_layout_create_info.pushConstantRangeCount = 0; pipeline_layout_create_info.pPushConstantRanges = VK_NULL_HANDLE; VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layouts.background)); } /** * @fn void ExtendedDynamicState2::create_descriptor_sets() * @brief Creating descriptor sets for 3 separate pipelines. */ void ExtendedDynamicState2::create_descriptor_sets() { /* First descriptor set */ VkDescriptorSetAllocateInfo alloc_info = vkb::initializers::descriptor_set_allocate_info( descriptor_pool, &descriptor_set_layouts.baseline, 1); VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.baseline)); VkDescriptorBufferInfo matrix_common_buffer_descriptor = create_descriptor(*uniform_buffers.common); VkDescriptorBufferInfo matrix_baseline_buffer_descriptor = create_descriptor(*uniform_buffers.baseline); std::vector write_descriptor_sets = { vkb::initializers::write_descriptor_set( descriptor_sets.baseline, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_common_buffer_descriptor), vkb::initializers::write_descriptor_set( descriptor_sets.baseline, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &matrix_baseline_buffer_descriptor)}; vkUpdateDescriptorSets(get_device().get_handle(), static_cast(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, VK_NULL_HANDLE); /* Second descriptor set */ alloc_info = vkb::initializers::descriptor_set_allocate_info( descriptor_pool, &descriptor_set_layouts.tesselation, 1); VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.tesselation)); VkDescriptorBufferInfo matrix_tess_buffer_descriptor = create_descriptor(*uniform_buffers.tesselation); write_descriptor_sets = { vkb::initializers::write_descriptor_set( descriptor_sets.tesselation, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_common_buffer_descriptor), vkb::initializers::write_descriptor_set( descriptor_sets.tesselation, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &matrix_tess_buffer_descriptor)}; vkUpdateDescriptorSets(get_device().get_handle(), static_cast(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, VK_NULL_HANDLE); /* Third descriptor set */ alloc_info = vkb::initializers::descriptor_set_allocate_info( descriptor_pool, &descriptor_set_layouts.background, 1); VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_sets.background)); VkDescriptorImageInfo background_image_descriptor = create_descriptor(textures.envmap); write_descriptor_sets = { vkb::initializers::write_descriptor_set( descriptor_sets.background, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &matrix_common_buffer_descriptor), vkb::initializers::write_descriptor_set( descriptor_sets.background, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &background_image_descriptor)}; vkUpdateDescriptorSets(get_device().get_handle(), static_cast(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, VK_NULL_HANDLE); } /** * @fn void ExtendedDynamicState2::request_gpu_features(vkb::PhysicalDevice &gpu) * @brief Enabling features related to Vulkan extensions */ void ExtendedDynamicState2::request_gpu_features(vkb::PhysicalDevice &gpu) { /* Enable extension features required by this sample These are passed to device creation via a pNext structure chain */ REQUEST_REQUIRED_FEATURE( gpu, VkPhysicalDeviceExtendedDynamicState2FeaturesEXT, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT, extendedDynamicState2); REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceExtendedDynamicState2FeaturesEXT, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT, extendedDynamicState2PatchControlPoints); REQUEST_REQUIRED_FEATURE( gpu, VkPhysicalDeviceExtendedDynamicStateFeaturesEXT, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, extendedDynamicState); REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT, primitiveTopologyListRestart); // Tessellation shader support is required for this example auto &requested_features = gpu.get_mutable_requested_features(); if (gpu.get_features().tessellationShader) { requested_features.tessellationShader = VK_TRUE; } else { throw vkb::VulkanException(VK_ERROR_FEATURE_NOT_PRESENT, "Selected GPU does not support tessellation shaders!"); } if (gpu.get_features().fillModeNonSolid) { requested_features.fillModeNonSolid = VK_TRUE; } if (gpu.get_features().samplerAnisotropy) { gpu.get_mutable_requested_features().samplerAnisotropy = true; } } /** * @fn void ExtendedDynamicState2::on_update_ui_overlay(vkb::Drawer &drawer) * @brief Projecting GUI and transferring data between GUI and app */ void ExtendedDynamicState2::on_update_ui_overlay(vkb::Drawer &drawer) { if (drawer.header("Settings")) { if (drawer.checkbox("Tessellation Enable", &gui_settings.tessellation)) { update_uniform_buffers(); } /* Maximum tessellation factor is set to 4.0 */ if (drawer.slider_float("Tessellation Factor", &gui_settings.tess_factor, 1.0f, 4.0f)) { update_uniform_buffers(); } } if (drawer.header("Models")) { drawer.checkbox("Selection effect active", &gui_settings.selection_active); const int obj_cnt = static_cast(scene_elements_baseline.size()); std::vector obj_names; for (int i = 0; i < obj_cnt; ++i) { obj_names.push_back((scene_elements_baseline[i].name).c_str()); } drawer.combo_box("Name", &gui_settings.selected_obj, obj_names); drawer.checkbox("Depth Bias Enable", &gui_settings.objects[gui_settings.selected_obj].depth_bias); drawer.checkbox("Rasterizer Discard", &gui_settings.objects[gui_settings.selected_obj].rasterizer_discard); } } /** * @fn void ExtendedDynamicState2::update(float delta_time) * @brief Function which was called in every frame. * @details For presenting z-fighting, a small animation was implemented (cube_animation) */ void ExtendedDynamicState2::update(float delta_time) { cube_animation(delta_time); ApiVulkanSample::update(delta_time); } /** * @fn glm::vec4 ExtendedDynamicState2::get_changed_alpha(const vkb::sg::PBRMaterial *original_mat) * @brief Changing alpha value to create blinking effect on selected model * @returns Color of original_mat with changed alpha value */ glm::vec4 ExtendedDynamicState2::get_changed_alpha(const vkb::sg::PBRMaterial *original_mat) { static bool rise = false; static int previous_obj_id = gui_settings.selected_obj; static float accumulated_diff = 0.0f; constexpr float alpha_step = 0.075f; constexpr float alpha_max = 0.98f; constexpr float alpha_min = 0.3f; glm::vec4 color = original_mat->base_color_factor; /* Change alpha value */ if (gui_settings.time_tick == true) { accumulated_diff += rise ? alpha_step : -alpha_step; gui_settings.time_tick = false; } color.w += accumulated_diff; /* Detecting change of selected object */ if (previous_obj_id != gui_settings.selected_obj) { accumulated_diff = 0.0f; previous_obj_id = gui_settings.selected_obj; } /* Determine if alpha need to increase or decrease */ if (color.w < alpha_min) { rise = true; } else if (color.w > alpha_max) { rise = false; } return color; } /** * @fn void ExtendedDynamicState2::scene_pipeline_divide(std::vector const &scene_node) * @brief Spliting main scene into two separate. * @details This operation is required to use same "draw_from_scene" function to draw models that are using different * pipelines (baseline and tessellation) */ void ExtendedDynamicState2::scene_pipeline_divide(std::vector const &scene_node) { /* Divide main scene to two (baseline and tessellation) */ for (int i = 0; i < scene_node.size(); ++i) { if (scene_node[i].name == "Geosphere") { scene_elements_tess.push_back(scene_node[i]); } else { scene_elements_baseline.push_back(scene_node[i]); } } } /** * @fn void ExtendedDynamicState2::draw_from_scene(VkCommandBuffer command_buffer, std::vector const &scene_node) * @brief Drawing all objects included to scene that is passed as argument of this function */ void ExtendedDynamicState2::draw_from_scene(VkCommandBuffer command_buffer, std::vector const &scene_node) { for (int i = 0; i < scene_node.size(); ++i) { const auto &vertex_buffer_pos = scene_node[i].sub_mesh->vertex_buffers.at("position"); const auto &vertex_buffer_normal = scene_node[i].sub_mesh->vertex_buffers.at("normal"); auto &index_buffer = scene_node[i].sub_mesh->index_buffer; if (scene_node[i].name != "Geosphere") { vkCmdSetDepthBiasEnableEXT(command_buffer, gui_settings.objects[i].depth_bias); vkCmdSetRasterizerDiscardEnableEXT(command_buffer, gui_settings.objects[i].rasterizer_discard); } /* Pass data for the current node via push commands */ auto node_material = dynamic_cast(scene_node[i].sub_mesh->get_material()); push_const_block.model_matrix = scene_node[i].node->get_transform().get_world_matrix(); if (gui_settings.selection_active && i == gui_settings.selected_obj) { push_const_block.color = get_changed_alpha(node_material); } else { push_const_block.color = node_material->base_color_factor; } vkCmdPushConstants(command_buffer, pipeline_layouts.baseline, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_const_block), &push_const_block); VkDeviceSize offsets[1] = {0}; vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffer_pos.get(), offsets); vkCmdBindVertexBuffers(command_buffer, 1, 1, vertex_buffer_normal.get(), offsets); vkCmdBindIndexBuffer(command_buffer, index_buffer->get_handle(), 0, scene_node[i].sub_mesh->index_type); vkCmdDrawIndexed(command_buffer, scene_node[i].sub_mesh->vertex_indices, 1, 0, 0, 0); } vkCmdSetDepthBiasEnableEXT(command_buffer, VK_FALSE); vkCmdSetRasterizerDiscardEnableEXT(command_buffer, VK_FALSE); } /** * @fn void ExtendedDynamicState2::draw_created_model(VkCommandBuffer commandBuffer) * @brief Drawing model created in function "model_data_creation" */ void ExtendedDynamicState2::draw_created_model(VkCommandBuffer commandBuffer) { VkDeviceSize offsets[1] = {0}; push_const_block.color = glm::vec4{0.5f, 1.0f, 1.0f, 1.0f}; vkCmdPushConstants(commandBuffer, pipeline_layouts.baseline, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_const_block), &push_const_block); vkCmdBindVertexBuffers(commandBuffer, 0, 1, cube.vertices_pos->get(), offsets); vkCmdBindVertexBuffers(commandBuffer, 1, 1, cube.vertices_norm->get(), offsets); vkCmdBindIndexBuffer(commandBuffer, cube.indices->get_handle(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(commandBuffer, cube.index_count, 1, 0, 0, 0); } /** * @fn void ExtendedDynamicState2::model_data_creation() * @brief Creating model (basic cube) vertex data */ void ExtendedDynamicState2::model_data_creation() { constexpr uint32_t vertex_count = 8; std::array vertices_pos; std::array vertices_norm; vertices_pos[0] = {0.0f, 0.0f, 0.0f}; vertices_pos[1] = {1.0f, 0.0f, 0.0f}; vertices_pos[2] = {1.0f, 1.0f, 0.0f}; vertices_pos[3] = {0.0f, 1.0f, 0.0f}; vertices_pos[4] = {0.0f, 0.0f, 1.0f}; vertices_pos[5] = {1.0f, 0.0f, 1.0f}; vertices_pos[6] = {1.0f, 1.0f, 1.0f}; vertices_pos[7] = {0.0f, 1.0f, 1.0f}; /* Normalized normal vectors for each face of cube */ glm::vec3 Xp = {1.0, 0.0, 0.0}; glm::vec3 Xm = {-1.0, 0.0, 0.0}; glm::vec3 Yp = {0.0, 1.0, 0.0}; glm::vec3 Ym = {0.0, -1.0, 0.0}; glm::vec3 Zp = {0.0, 0.0, 1.0}; glm::vec3 Zm = {0.0, 0.0, -1.0}; /* Normalized normal vectors for each vertex (created by sum of corresponding faces) */ vertices_norm[0] = glm::normalize(Xm + Ym + Zm); vertices_norm[1] = glm::normalize(Xp + Ym + Zm); vertices_norm[2] = glm::normalize(Xp + Yp + Zm); vertices_norm[3] = glm::normalize(Xm + Yp + Zm); vertices_norm[4] = glm::normalize(Xm + Ym + Zp); vertices_norm[5] = glm::normalize(Xp + Ym + Zp); vertices_norm[6] = glm::normalize(Xp + Yp + Zp); vertices_norm[7] = glm::normalize(Xm + Yp + Zp); /* Scaling and position transform */ for (int i = 0; i < vertex_count; ++i) { vertices_pos[i] *= glm::vec3(4.0f, 4.0f, 4.0f); vertices_pos[i] += glm::vec3(15.0f, 2.0f, 0.0f); } constexpr uint32_t index_count = 29; uint32_t vertex_buffer_size = vertex_count * sizeof(glm::vec3); uint32_t index_buffer_size = index_count * sizeof(uint32_t); cube.index_count = index_count; /* Array with vertices indexes for corresponding triangles */ std::array indices{0, 4, 3, 7, UINT32_MAX, 1, 0, 2, 3, UINT32_MAX, 2, 6, 1, 5, UINT32_MAX, 1, 5, 0, 4, UINT32_MAX, 4, 5, 7, 6, UINT32_MAX, 2, 3, 6, 7}; vkb::core::BufferC vertex_pos_staging = vkb::core::BufferC::create_staging_buffer(get_device(), vertices_pos); vkb::core::BufferC vertex_norm_staging = vkb::core::BufferC::create_staging_buffer(get_device(), vertices_norm); vkb::core::BufferC index_staging = vkb::core::BufferC::create_staging_buffer(get_device(), indices); cube.vertices_pos = std::make_unique(get_device(), vertex_buffer_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY); cube.vertices_norm = std::make_unique(get_device(), vertex_buffer_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY); cube.indices = std::make_unique(get_device(), index_buffer_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY); /* Copy from staging buffers */ VkCommandBuffer copy_command = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkBufferCopy copy_region = {}; copy_region.size = vertex_buffer_size; vkCmdCopyBuffer( copy_command, vertex_pos_staging.get_handle(), cube.vertices_pos->get_handle(), 1, ©_region); vkCmdCopyBuffer( copy_command, vertex_norm_staging.get_handle(), cube.vertices_norm->get_handle(), 1, ©_region); copy_region.size = index_buffer_size; vkCmdCopyBuffer( copy_command, index_staging.get_handle(), cube.indices->get_handle(), 1, ©_region); get_device().flush_command_buffer(copy_command, queue, true); } /** * @fn void ExtendedDynamicState2::cube_animation(float delta_time) * @brief Changing position of one z-fighting cube (visualize negative phenomenon z-fighting) */ void ExtendedDynamicState2::cube_animation(float delta_time) { constexpr float tick_limit = 0.05f; constexpr float delta = 0.05f; constexpr float move_step = 0.0005f; static float time_pass = 0.0f; time_pass += delta_time; static auto &transform = std::ranges::find_if(scene_elements_baseline, [](SceneNode const &scene_node) { return scene_node.node->get_name() == "Cube_1"; }) ->node->get_transform(); static auto translation = transform.get_translation(); static float difference = 0.0f; static bool rising = true; /* Checking if tick time passed away */ if (time_pass > tick_limit) { /* Determine direction of x axis */ if (difference < -delta) { rising = true; } else if (difference > delta) { rising = false; } /* Move object by step value */ if (rising == true) { translation.x += move_step; difference += move_step; } else { translation.x -= move_step; difference -= move_step; } time_pass = 0; /* Write new position to object */ transform.set_translation(translation); gui_settings.time_tick = true; rebuild_command_buffers(); } } std::unique_ptr create_extended_dynamic_state2() { return std::make_unique(); }