diff --git a/CMakeLists.txt b/CMakeLists.txt index a052a8437..ca650bcce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,3 +87,5 @@ endif() # Add vulkan app (runs all samples) add_subdirectory(app) + +add_subdirectory(antora) diff --git a/antora/CMakeLists.txt b/antora/CMakeLists.txt new file mode 100644 index 000000000..50c19bec5 --- /dev/null +++ b/antora/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright 2022-2023 The Khronos Group Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Configure Vulkan Guide Antora tree with transformed markup files. +# Branch selection may come later. For now it is the current branch. + +function(gatherAntoraAssets) + set(DIRS_TO_SEARCH + app + assets + components + docs + framework + samples + scripts + shaders + tests + ) + set(PAGES_DIR_SEARCH) + set(IMAGES_DIR_SEARCH) + foreach (DIR ${DIRS_TO_SEARCH}) + list(APPEND PAGES_DIR_SEARCH ${CMAKE_SOURCE_DIR}/${DIR}/*.adoc) + list(APPEND IMAGES_DIR_SEARCH ${CMAKE_SOURCE_DIR}/${DIR}/*.jpg ${CMAKE_SOURCE_DIR}/${DIR}/*.png ${CMAKE_SOURCE_DIR}/${DIR}/*.gif) + endforeach () + file(GLOB PAGES ${CMAKE_SOURCE_DIR}/*.adoc) + file(GLOB IMAGES ${CMAKE_SOURCE_DIR}/*.jpg ${CMAKE_SOURCE_DIR}/*.png ${CMAKE_SOURCE_DIR}/*.gif) + file(GLOB_RECURSE PAGES_R ${PAGES_DIR_SEARCH}) + file(GLOB_RECURSE IMAGES_R ${IMAGES_DIR_SEARCH}) + + foreach(page ${PAGES}) + file(COPY ${page} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/modules/ROOT/pages) + endforeach () + + foreach(image ${IMAGES}) + file(COPY ${image} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/modules/ROOT/images) + endforeach () + + foreach (page ${PAGES_R}) + file(RELATIVE_PATH relpage ${CMAKE_SOURCE_DIR} ${page}) + get_filename_component(directory ${relpage} DIRECTORY) + file(COPY ${page} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/modules/ROOT/pages/${directory}) + endforeach () + + foreach(image ${IMAGES_R}) + file(RELATIVE_PATH relpage ${CMAKE_SOURCE_DIR} ${image}) + get_filename_component(directory ${relpage} DIRECTORY) + file(COPY ${image} DESTINATION ${CMAKE_CURRENT_LIST_DIR}/modules/ROOT/images/${directory}) + endforeach () +endfunction() + +if(VKB_GENERATE_ANTORA_SITE) + gatherAntoraAssets() +endif() \ No newline at end of file diff --git a/antora/antora.yml b/antora/antora.yml new file mode 100644 index 000000000..63585d2f5 --- /dev/null +++ b/antora/antora.yml @@ -0,0 +1,13 @@ +# Copyright 2022-2023 The Khronos Group Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: samples +title: Vulkan Samples +version: latest +start_page: README.adoc +asciidoc: + attributes: + source-language: asciidoc@ + table-caption: false +nav: + - modules/ROOT/nav.adoc diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc new file mode 100644 index 000000000..cb9ca365f --- /dev/null +++ b/antora/modules/ROOT/nav.adoc @@ -0,0 +1,107 @@ +//// +- Copyright (c) 2023, Holochip Inc +- +- 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. +- +//// +* xref:README.adoc[Readme] +* xref:vulkan_basics.adoc[Vulkan basics] +* xref:framework/README.adoc[Sample framework] +* xref:api/README.adoc[Api usage samples] +** xref:api/compute_nbody/README.adoc[Compute N-body❕] +*** xref:api/hpp_compute_nbody/README.adoc[hpp compute nbody❕] +** xref:api/dynamic_uniform_buffers/README.adoc[Dynamic uniform buffers❕] +*** xref:api/hpp_dynamic_uniform_buffers/README.adoc[hpp dynamic uniform buffers❕] +** xref:api/hdr/README.adoc[HDR❕] +*** xref:api/hpp_hdr/README.adoc[hpp hdr❕] +** xref:api/hello_triangle/README.adoc[Hello Triangle❕] +*** xref:api/hpp_hello_triangle/README.adoc[hpp hello triangle❕] +** xref:api/hlsl_shaders/README.adoc[HLSL Shaders] +*** xref:api/hpp_hlsl_shaders/README.adoc[hpp hlsl shaders] +** xref:api/instancing/README.adoc[Instancing❕] +*** xref:api/hpp_instancing/README.adoc[hpp instancing❕] +** xref:api/separate_image_sampler/README.adoc[Separate image sampler] +*** xref:api/hpp_separate_image_sampler/README.adoc[hpp separate image sampler] +** xref:api/terrain_tessellation/README.adoc[Terrain tessellation❕] +*** xref:api/hpp_terrain_tessellation/README.adoc[hpp terrain tessellation❕] +** xref:api/texture_loading/README.adoc[Texture loading❕] +*** xref:api/hpp_texture_loading/README.adoc[hpp texture loading❕] +** xref:api/texture_mipmap_generation/README.adoc[Texture mipmap generation] +*** xref:api/hpp_texture_mipmap_generation/README.adoc[hpp texture mipmap generation] +** xref:api/timestamp_queries/README.adoc[Timestamp queries] +*** xref:api/hpp_timestamp_queries/README.adoc[hpp timestamp queries] +* xref:extensions/README.adoc[Extension usage samples] +** xref:extensions/buffer_device_address/README.adoc[Buffer device address] +** xref:extensions/calibrated_timestamps/README.adoc[Calibrated timestamps] +** xref:extensions/conditional_rendering/README.adoc[Conditional rendering] +** xref:extensions/conservative_rasterization/README.adoc[Conservative rasterization❕] +** xref:extensions/debug_utils/README.adoc[Debug utils] +** xref:extensions/descriptor_buffer_basic/README.adoc[Descriptor buffer basic] +** xref:extensions/descriptor_indexing/README.adoc[Descriptor indexing] +** xref:extensions/dynamic_rendering/README.adoc[Dynamic rendering] +** xref:extensions/extended_dynamic_state2/README.adoc[Extended dynamic state2] +** xref:extensions/fragment_shader_barycentric/README.adoc[Fragment shader barycentric] +** xref:extensions/fragment_shading_rate/README.adoc[Fragment shading rate❕] +** xref:extensions/fragment_shading_rate_dynamic/README.adoc[Fragment shading rate dynamic] +** xref:extensions/full_screen_exclusive/README.adoc[Full screen exclusive] +** xref:extensions/graphics_pipeline_library/README.adoc[Graphics pipeline library] +** xref:extensions/logic_op_dynamic_state/README.adoc[Logic op dynamic state] +** xref:extensions/memory_budget/README.adoc[Memory budget] +** xref:extensions/mesh_shader_culling/README.adoc[Mesh shader culling] +** xref:extensions/mesh_shading/README.adoc[Mesh shading❕] +** xref:extensions/open_cl_interop/README.adoc[OpenCL interop (Cross-vendor)] +** xref:extensions/open_cl_interop_arm/README.adoc[OpenCl interop (Arm)] +** xref:extensions/open_gl_interop/README.adoc[OpenGL interop❕] +** xref:extensions/portability/README.adoc[Portability] +** xref:extensions/push_descriptors/README.adoc[Push descriptors❕] +** xref:extensions/raytracing_basic/README.adoc[Raytracing basic❕] +** xref:extensions/raytracing_extended/README.adoc[Raytracing extended] +** xref:extensions/ray_queries/README.adoc[Ray queries❕] +** xref:extensions/ray_tracing_reflection/README.adoc[Ray tracing reflection] +** xref:extensions/synchronization_2/README.adoc[Synchronization 2❕] +** xref:extensions/timeline_semaphore/README.adoc[Timeline semaphore] +** xref:extensions/vertex_dynamic_state/README.adoc[Vertex dynamic state] +* xref:performance/README.adoc[Performance samples] +** xref:performance/16bit_arithmetic/README.adoc[16bit arithmetic] +** xref:performance/16bit_storage_input_output/README.adoc[16bit storage input output] +** xref:performance/afbc/README.adoc[AFBC] +** xref:performance/async_compute/README.adoc[Async compute] +** xref:performance/command_buffer_usage/README.adoc[Command buffer usage] +** xref:performance/constant_data/README.adoc[Constant data] +** xref:performance/descriptor_management/README.adoc[Descriptor management] +** xref:performance/layout_transitions/README.adoc[Layout transitions] +** xref:performance/msaa/README.adoc[MSAA] +** xref:performance/multithreading_render_passes/README.adoc[Multithreading render passes] +** xref:performance/multi_draw_indirect/README.adoc[Multi draw indirect] +** xref:performance/pipeline_barriers/README.adoc[Pipeline barriers] +** xref:performance/pipeline_cache/README.adoc[Pipeline cache] +*** xref:performance/hpp_pipeline_cache/README.adoc[hpp pipeline cache] +** xref:performance/render_passes/README.adoc[Render passes] +** xref:performance/specialization_constants/README.adoc[Specialization constants] +** xref:performance/subpasses/README.adoc[Subpasses] +** xref:performance/surface_rotation/README.adoc[Surface rotation] +** xref:performance/swapchain_images/README.adoc[Swapchain images] +*** xref:performance/hpp_swapchain_images/README.adoc[hpp swapchain images] +** xref:performance/texture_compression_basisu/README.adoc[Texture compression basisu] +** xref:performance/texture_compression_comparison/README.adoc[Texture compression comparison❕] +** xref:performance/wait_idle/README.adoc[Wait idle] +* xref:tooling/README.adoc[Tooling samples] +** xref:tooling/profiles/README.adoc[Profiles] +* xref:docs/README.adoc[General documentation] +** xref:docs/build.adoc[Build guide] +** xref:docs/create_sample.adoc[Creating a new sample] +** xref:docs/debug_graphs.adoc[Debug graphics] +** xref:docs/memory_limits.adoc[Memory limits] +** xref:docs/misc.adoc[Miscellaneous] \ No newline at end of file diff --git a/app/android/java/com/khronos/vulkan_samples/NativeSampleActivity.java b/app/android/java/com/khronos/vulkan_samples/NativeSampleActivity.java index 6b3280538..ad1dcb559 100644 --- a/app/android/java/com/khronos/vulkan_samples/NativeSampleActivity.java +++ b/app/android/java/com/khronos/vulkan_samples/NativeSampleActivity.java @@ -78,7 +78,7 @@ public void fatalError(final String log_file) { intent.setDataAndType(path, context.getContentResolver().getType(path)); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, Notifications.CHANNEL_ID) .setSmallIcon(R.drawable.icon) diff --git a/framework/CMakeLists.txt b/framework/CMakeLists.txt index 7caebac8a..594cc822c 100644 --- a/framework/CMakeLists.txt +++ b/framework/CMakeLists.txt @@ -304,6 +304,7 @@ set(CORE_FILES core/hpp_buffer.cpp core/hpp_command_buffer.cpp core/hpp_command_pool.cpp + core/hpp_debug.cpp core/hpp_device.cpp core/hpp_image.cpp core/hpp_image_view.cpp @@ -313,7 +314,6 @@ set(CORE_FILES core/hpp_queue.cpp core/hpp_sampler.cpp core/hpp_swapchain.cpp - core/hpp_vulkan_resource.cpp ) set(PLATFORM_FILES diff --git a/framework/buffer_pool.cpp b/framework/buffer_pool.cpp index 658ddd9ea..df99d7083 100644 --- a/framework/buffer_pool.cpp +++ b/framework/buffer_pool.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2019-2022, Arm Limited and Contributors +/* Copyright (c) 2019-2023, Arm Limited and Contributors * * SPDX-License-Identifier: Apache-2.0 * @@ -50,21 +50,29 @@ BufferBlock::BufferBlock(Device &device, VkDeviceSize size, VkBufferUsageFlags u } } -BufferAllocation BufferBlock::allocate(const uint32_t allocate_size) +VkDeviceSize BufferBlock::aligned_offset() const { - assert(allocate_size > 0 && "Allocation size must be greater than zero"); + return (offset + alignment - 1) & ~(alignment - 1); +} - auto aligned_offset = (offset + alignment - 1) & ~(alignment - 1); +bool BufferBlock::can_allocate(VkDeviceSize size) const +{ + assert(size > 0 && "Allocation size must be greater than zero"); + return (aligned_offset() + size <= buffer.get_size()); +} - if (aligned_offset + allocate_size > buffer.get_size()) +BufferAllocation BufferBlock::allocate(VkDeviceSize size) +{ + if (can_allocate(size)) { - // No more space available from the underlying buffer, return empty allocation - return BufferAllocation{}; + // Move the current offset and return an allocation + auto aligned = aligned_offset(); + offset = aligned + size; + return BufferAllocation{buffer, size, aligned}; } - // Move the current offset and return an allocation - offset = aligned_offset + allocate_size; - return BufferAllocation{buffer, allocate_size, aligned_offset}; + // No more space available from the underlying buffer, return empty allocation + return BufferAllocation{}; } VkDeviceSize BufferBlock::get_size() const @@ -87,28 +95,25 @@ BufferPool::BufferPool(Device &device, VkDeviceSize block_size, VkBufferUsageFla BufferBlock &BufferPool::request_buffer_block(const VkDeviceSize minimum_size, bool minimal) { - // Find the first block in the range of the inactive blocks - // which can fit the minimum size - auto it = std::upper_bound(buffer_blocks.begin() + active_buffer_block_count, buffer_blocks.end(), minimum_size, - [](const VkDeviceSize &a, const std::unique_ptr &b) -> bool { return a <= b->get_size(); }); - - if (it != buffer_blocks.end()) + // Find a block in the range of the blocks which can fit the minimum size + auto it = minimal ? std::find_if(buffer_blocks.begin(), + buffer_blocks.end(), + [&minimum_size](const std::unique_ptr &buffer_block) { return (buffer_block->get_size() == minimum_size) && buffer_block->can_allocate(minimum_size); }) : + std::find_if(buffer_blocks.begin(), + buffer_blocks.end(), + [&minimum_size](const std::unique_ptr &buffer_block) { return buffer_block->can_allocate(minimum_size); }); + + if (it == buffer_blocks.end()) { - // Recycle inactive block - active_buffer_block_count++; - return *it->get(); - } - - LOGD("Building #{} buffer block ({})", buffer_blocks.size(), usage); - - VkDeviceSize new_block_size = minimal ? minimum_size : std::max(block_size, minimum_size); + LOGD("Building #{} buffer block ({})", buffer_blocks.size(), usage); - // Create a new block, store and return it - buffer_blocks.emplace_back(std::make_unique(device, new_block_size, usage, memory_usage)); + VkDeviceSize new_block_size = minimal ? minimum_size : std::max(block_size, minimum_size); - auto &block = buffer_blocks[active_buffer_block_count++]; + // Create a new block and get the iterator on it + it = buffer_blocks.emplace(buffer_blocks.end(), std::make_unique(device, new_block_size, usage, memory_usage)); + } - return *block.get(); + return *it->get(); } void BufferPool::reset() @@ -117,8 +122,6 @@ void BufferPool::reset() { buffer_block->reset(); } - - active_buffer_block_count = 0; } BufferAllocation::BufferAllocation(core::Buffer &buffer, VkDeviceSize size, VkDeviceSize offset) : diff --git a/framework/buffer_pool.h b/framework/buffer_pool.h index 1ece24db6..ab0cd2ee2 100644 --- a/framework/buffer_pool.h +++ b/framework/buffer_pool.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2019-2022, Arm Limited and Contributors +/* Copyright (c) 2019-2023, Arm Limited and Contributors * * SPDX-License-Identifier: Apache-2.0 * @@ -75,15 +75,29 @@ class BufferBlock public: BufferBlock(Device &device, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memory_usage); + /** + * @brief check if this BufferBlock can allocate a given amount of memory + * @param size the number of bytes to check + * @return \c true if \a size bytes can be allocated from this \c BufferBlock, otherwise \c false. + */ + bool can_allocate(VkDeviceSize size) const; + /** * @return An usable view on a portion of the underlying buffer */ - BufferAllocation allocate(uint32_t size); + BufferAllocation allocate(VkDeviceSize size); VkDeviceSize get_size() const; void reset(); + private: + /** + * @ brief Determine the current aligned offset. + * @return The current aligned offset. + */ + VkDeviceSize aligned_offset() const; + private: core::Buffer buffer; @@ -131,8 +145,5 @@ class BufferPool VkBufferUsageFlags usage{}; VmaMemoryUsage memory_usage{}; - - /// Numbers of active blocks from the start of buffer_blocks - uint32_t active_buffer_block_count{0}; }; } // namespace vkb diff --git a/framework/common/hpp_vk_common.h b/framework/common/hpp_vk_common.h index 53c79a875..070a95da5 100644 --- a/framework/common/hpp_vk_common.h +++ b/framework/common/hpp_vk_common.h @@ -176,9 +176,11 @@ inline vk::Framebuffer create_framebuffer(vk::Device device, vk::RenderPass rend inline vk::Pipeline create_graphics_pipeline(vk::Device device, vk::PipelineCache pipeline_cache, - std::array const &shader_stages, + std::vector const &shader_stages, vk::PipelineVertexInputStateCreateInfo const &vertex_input_state, vk::PrimitiveTopology primitive_topology, + uint32_t patch_control_points, + vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode, vk::FrontFace front_face, std::vector const &blend_attachment_states, @@ -188,10 +190,12 @@ inline vk::Pipeline create_graphics_pipeline(vk::Device { vk::PipelineInputAssemblyStateCreateInfo input_assembly_state({}, primitive_topology, false); + vk::PipelineTessellationStateCreateInfo tessellation_state({}, patch_control_points); + vk::PipelineViewportStateCreateInfo viewport_state({}, 1, nullptr, 1, nullptr); vk::PipelineRasterizationStateCreateInfo rasterization_state; - rasterization_state.polygonMode = vk::PolygonMode::eFill; + rasterization_state.polygonMode = polygon_mode; rasterization_state.cullMode = cull_mode; rasterization_state.frontFace = front_face; rasterization_state.lineWidth = 1.0f; @@ -208,7 +212,7 @@ inline vk::Pipeline create_graphics_pipeline(vk::Device shader_stages, &vertex_input_state, &input_assembly_state, - {}, + &tessellation_state, &viewport_state, &rasterization_state, &multisample_state, diff --git a/framework/core/hpp_debug.cpp b/framework/core/hpp_debug.cpp new file mode 100644 index 000000000..76eb3f6d8 --- /dev/null +++ b/framework/core/hpp_debug.cpp @@ -0,0 +1,115 @@ +/* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. + * + * 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 "hpp_debug.h" +#include "core/hpp_command_buffer.h" +#include "core/hpp_device.h" + +namespace vkb +{ +namespace core +{ +void HPPDebugUtilsExtDebugUtils::set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const +{ + vk::DebugUtilsObjectNameInfoEXT name_info(object_type, object_handle, name); + device.setDebugUtilsObjectNameEXT(name_info); +} + +void HPPDebugUtilsExtDebugUtils::set_debug_tag( + vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const +{ + vk::DebugUtilsObjectTagInfoEXT tag_info(object_type, object_handle, tag_name, tag_data_size, tag_data); + device.setDebugUtilsObjectTagEXT(tag_info); +} + +void HPPDebugUtilsExtDebugUtils::cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const +{ + vk::DebugUtilsLabelEXT label_info(name, reinterpret_cast const &>(color)); + command_buffer.beginDebugUtilsLabelEXT(label_info); +} + +void HPPDebugUtilsExtDebugUtils::cmd_end_label(vk::CommandBuffer command_buffer) const +{ + command_buffer.endDebugUtilsLabelEXT(); +} + +void HPPDebugUtilsExtDebugUtils::cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const +{ + vk::DebugUtilsLabelEXT label_info(name, reinterpret_cast const &>(color)); + command_buffer.insertDebugUtilsLabelEXT(label_info); +} + +void HPPDebugMarkerExtDebugUtils::set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const +{ + vk::DebugMarkerObjectNameInfoEXT name_info(vk::debugReportObjectType(object_type), object_handle, name); + device.debugMarkerSetObjectNameEXT(name_info); +} + +void HPPDebugMarkerExtDebugUtils::set_debug_tag( + vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const +{ + vk::DebugMarkerObjectTagInfoEXT tag_info(vk::debugReportObjectType(object_type), object_handle, tag_name, tag_data_size, tag_data); + device.debugMarkerSetObjectTagEXT(tag_info); +} + +void HPPDebugMarkerExtDebugUtils::cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const +{ + vk::DebugMarkerMarkerInfoEXT marker_info(name, reinterpret_cast const &>(color)); + command_buffer.debugMarkerBeginEXT(marker_info); +} + +void HPPDebugMarkerExtDebugUtils::cmd_end_label(vk::CommandBuffer command_buffer) const +{ + command_buffer.debugMarkerEndEXT(); +} + +void HPPDebugMarkerExtDebugUtils::cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const +{ + vk::DebugMarkerMarkerInfoEXT marker_info(name, reinterpret_cast const &>(color)); + command_buffer.debugMarkerInsertEXT(marker_info); +} + +HPPScopedDebugLabel::HPPScopedDebugLabel(const HPPDebugUtils &debug_utils, + vk::CommandBuffer command_buffer, + std::string const &name, + glm::vec4 const &color) : + debug_utils{&debug_utils}, command_buffer{VK_NULL_HANDLE} +{ + if (!name.empty()) + { + assert(command_buffer); + this->command_buffer = command_buffer; + + debug_utils.cmd_begin_label(command_buffer, name.c_str(), color); + } +} + +HPPScopedDebugLabel::HPPScopedDebugLabel(const vkb::core::HPPCommandBuffer &command_buffer, std::string const &name, glm::vec4 const &color) : + HPPScopedDebugLabel{command_buffer.get_device().get_debug_utils(), command_buffer.get_handle(), name, color} +{ +} + +HPPScopedDebugLabel::~HPPScopedDebugLabel() +{ + if (command_buffer) + { + debug_utils->cmd_end_label(command_buffer); + } +} + +} // namespace core +} // namespace vkb \ No newline at end of file diff --git a/framework/core/hpp_debug.h b/framework/core/hpp_debug.h index 945b9f5aa..5a385d714 100644 --- a/framework/core/hpp_debug.h +++ b/framework/core/hpp_debug.h @@ -17,8 +17,7 @@ #pragma once -#include - +#include #include namespace vkb @@ -28,243 +27,123 @@ namespace core class HPPCommandBuffer; /** - * @brief facade class around vkb::DebugUtils, providing a vulkan.hpp-based interface - * - * See vkb::DebugUtils for documentation + * @brief An interface over platform-specific debug extensions. */ -class HPPDebugUtils : private vkb::DebugUtils +class HPPDebugUtils { public: - void set_debug_name(VkDevice device, - VkObjectType object_type, - uint64_t object_handle, - const char *name) const override - { - set_debug_name( - static_cast(device), static_cast(object_type), object_handle, name); - } - - void set_debug_tag(VkDevice device, - VkObjectType object_type, - uint64_t object_handle, - uint64_t tag_name, - const void *tag_data, - size_t tag_data_size) const override - { - set_debug_tag(static_cast(device), - static_cast(object_type), - object_handle, - tag_name, - tag_data, - tag_data_size); - } - - void cmd_begin_label(VkCommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - cmd_begin_label(static_cast(command_buffer), name, color); - } - - void cmd_end_label(VkCommandBuffer command_buffer) const override - { - cmd_end_label(static_cast(command_buffer)); - } - - void cmd_insert_label(VkCommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - cmd_insert_label(static_cast(command_buffer), name, color); - } - - virtual void set_debug_name(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - const char *name) const = 0; - virtual void set_debug_tag(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - uint64_t tag_name, - const void *tag_data, - size_t tag_data_size) const = 0; - virtual void cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color = {}) const = 0; - virtual void cmd_end_label(vk::CommandBuffer command_buffer) const = 0; - virtual void cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color = {}) const = 0; + virtual ~HPPDebugUtils() = default; + + /** + * @brief Sets the debug name for a Vulkan object. + */ + virtual void set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const = 0; + + /** + * @brief Tags the given Vulkan object with some data. + */ + virtual void set_debug_tag( + vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const = 0; + + /** + * @brief Inserts a command to begin a new debug label/marker scope. + */ + virtual void cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color = {}) const = 0; + + /** + * @brief Inserts a command to end the current debug label/marker scope. + */ + virtual void cmd_end_label(vk::CommandBuffer command_buffer) const = 0; + + /** + * @brief Inserts a (non-scoped) debug label/marker in the command buffer. + */ + virtual void cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color = {}) const = 0; }; /** - * @brief facade class around vkb::DebugUtilsExtDebugUtils, providing a vulkan.hpp-based interface - * - * See vkb::DebugUtilsExtDebugUtils for documentation + * @brief HPPDebugUtils implemented on top of VK_EXT_debug_utils. */ class HPPDebugUtilsExtDebugUtils final : public vkb::core::HPPDebugUtils { public: - virtual void set_debug_name(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - const char *name) const override - { - reinterpret_cast(this)->set_debug_name( - static_cast(device), static_cast(object_type), object_handle, name); - } - - virtual void set_debug_tag(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - uint64_t tag_name, - const void *tag_data, - size_t tag_data_size) const override - { - reinterpret_cast(this)->set_debug_tag( - static_cast(device), - static_cast(object_type), - object_handle, - tag_name, - tag_data, - tag_data_size); - } - - virtual void - cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - reinterpret_cast(this)->cmd_begin_label( - static_cast(command_buffer), name, color); - } - - virtual void cmd_end_label(vk::CommandBuffer command_buffer) const override - { - reinterpret_cast(this)->cmd_end_label( - static_cast(command_buffer)); - } - - virtual void - cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - reinterpret_cast(this)->cmd_insert_label( - static_cast(command_buffer), name, color); - } + ~HPPDebugUtilsExtDebugUtils() override = default; + + void set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const override; + + void set_debug_tag( + vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const override; + + void cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const override; + + void cmd_end_label(vk::CommandBuffer command_buffer) const override; + + void cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const override; }; /** - * @brief facade class around vkb::DebugMarkerExtDebugUtils, providing a vulkan.hpp-based interface - * - * See vkb::DebugMarkerExtDebugUtils for documentation + * @brief HPPDebugUtils implemented on top of VK_EXT_debug_marker. */ class HPPDebugMarkerExtDebugUtils final : public vkb::core::HPPDebugUtils { public: - virtual void set_debug_name(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - const char *name) const override - { - reinterpret_cast(this)->set_debug_name( - static_cast(device), static_cast(object_type), object_handle, name); - } - - virtual void set_debug_tag(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - uint64_t tag_name, - const void *tag_data, - size_t tag_data_size) const override - { - reinterpret_cast(this)->set_debug_tag( - static_cast(device), - static_cast(object_type), - object_handle, - tag_name, - tag_data, - tag_data_size); - } - - virtual void - cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - reinterpret_cast(this)->cmd_begin_label( - static_cast(command_buffer), name, color); - } - - virtual void cmd_end_label(vk::CommandBuffer command_buffer) const override - { - reinterpret_cast(this)->cmd_end_label( - static_cast(command_buffer)); - } - - virtual void - cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - reinterpret_cast(this)->cmd_insert_label( - static_cast(command_buffer), name, color); - } + ~HPPDebugMarkerExtDebugUtils() override = default; + + void set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const override; + + void set_debug_tag( + vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const override; + + void cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const override; + + void cmd_end_label(vk::CommandBuffer command_buffer) const override; + + void cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const &color) const override; }; /** - * @brief facade class around vkb::DummyDebugUtils, providing a vulkan.hpp-based interface - * - * See vkb::DummyDebugUtils for documentation + * @brief No-op HPPDebugUtils. */ class HPPDummyDebugUtils final : public vkb::core::HPPDebugUtils { public: - virtual void set_debug_name(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - const char *name) const override - { - reinterpret_cast(this)->set_debug_name( - static_cast(device), static_cast(object_type), object_handle, name); - } - - virtual void set_debug_tag(vk::Device device, - vk::ObjectType object_type, - uint64_t object_handle, - uint64_t tag_name, - const void *tag_data, - size_t tag_data_size) const override - { - reinterpret_cast(this)->set_debug_tag(static_cast(device), - static_cast(object_type), - object_handle, - tag_name, - tag_data, - tag_data_size); - } - - virtual void - cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - reinterpret_cast(this)->cmd_begin_label( - static_cast(command_buffer), name, color); - } - - virtual void cmd_end_label(vk::CommandBuffer command_buffer) const override - { - reinterpret_cast(this)->cmd_end_label( - static_cast(command_buffer)); - } - - virtual void - cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 color) const override - { - reinterpret_cast(this)->cmd_insert_label( - static_cast(command_buffer), name, color); - } + ~HPPDummyDebugUtils() override = default; + + inline void set_debug_name(vk::Device, vk::ObjectType, uint64_t, const char *) const override + {} + + inline void set_debug_tag(vk::Device, vk::ObjectType, uint64_t, uint64_t, const void *, size_t) const override + {} + + inline void cmd_begin_label(vk::CommandBuffer, const char *, glm::vec4 const &) const override + {} + + inline void cmd_end_label(vk::CommandBuffer) const override + {} + + inline void cmd_insert_label(vk::CommandBuffer, const char *, glm::vec4 const &) const override + {} }; /** - * @brief facade class around vkb::DummyDebugUtils, providing a vulkan.hpp-based interface - * - * See vkb::DummyDebugUtils for documentation + * @brief A RAII debug label. + * If any of EXT_debug_utils or EXT_debug_marker is available, this: + * - Begins a debug label / marker on construction + * - Ends it on destruction */ class HPPScopedDebugLabel final { public: - HPPScopedDebugLabel(const vkb::core::HPPCommandBuffer &command_buffer, const char *name, glm::vec4 color = {}) : - scopedDebugLabel(reinterpret_cast(command_buffer), name, color) - {} + HPPScopedDebugLabel(const vkb::core::HPPDebugUtils &debug_utils, vk::CommandBuffer command_buffer, std::string const &name, glm::vec4 const &color = {}); + + HPPScopedDebugLabel(const vkb::core::HPPCommandBuffer &command_buffer, std::string const &name, glm::vec4 const &color = {}); + + ~HPPScopedDebugLabel(); private: - ScopedDebugLabel scopedDebugLabel; + const vkb::core::HPPDebugUtils *debug_utils; + vk::CommandBuffer command_buffer; }; } // namespace core -} // namespace vkb +} // namespace vkb \ No newline at end of file diff --git a/framework/core/hpp_instance.cpp b/framework/core/hpp_instance.cpp index 7bc78db63..d798f9325 100644 --- a/framework/core/hpp_instance.cpp +++ b/framework/core/hpp_instance.cpp @@ -21,11 +21,19 @@ #include #include +#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +# define USE_VALIDATION_LAYERS +#endif + +#if defined(USE_VALIDATION_LAYERS) && (defined(VKB_VALIDATION_LAYERS_GPU_ASSISTED) || defined(VKB_VALIDATION_LAYERS_BEST_PRACTICES) || defined(VKB_VALIDATION_LAYERS_SYNCHRONIZATION)) +# define USE_VALIDATION_LAYER_FEATURES +#endif + namespace vkb { namespace { -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS VKAPI_ATTR VkBool32 VKAPI_CALL debug_utils_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_type, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data) @@ -181,7 +189,7 @@ HPPInstance::HPPInstance(const std::string &applicati { std::vector available_instance_extensions = vk::enumerateInstanceExtensionProperties(); -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS // Check if VK_EXT_debug_utils is supported, which supersedes VK_EXT_Debug_Report const bool has_debug_utils = enable_extension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, available_instance_extensions, enabled_extensions); @@ -204,7 +212,7 @@ HPPInstance::HPPInstance(const std::string &applicati enable_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, available_instance_extensions, enabled_extensions); #endif -#if (defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)) && (defined(VKB_VALIDATION_LAYERS_GPU_ASSISTED) || defined(VKB_VALIDATION_LAYERS_BEST_PRACTICES)) +#ifdef USE_VALIDATION_LAYER_FEATURES bool validation_features = false; { std::vector available_layer_instance_extensions = vk::enumerateInstanceExtensionProperties(std::string("VK_LAYER_KHRONOS_validation")); @@ -270,7 +278,7 @@ HPPInstance::HPPInstance(const std::string &applicati std::vector requested_validation_layers(required_validation_layers); -#ifdef VKB_VALIDATION_LAYERS +#ifdef USE_VALIDATION_LAYERS // Determine the optimal validation layers to enable that are necessary for useful debugging std::vector optimal_validation_layers = get_optimal_validation_layers(supported_validation_layers); requested_validation_layers.insert(requested_validation_layers.end(), optimal_validation_layers.begin(), optimal_validation_layers.end()); @@ -293,7 +301,7 @@ HPPInstance::HPPInstance(const std::string &applicati vk::InstanceCreateInfo instance_info({}, &app_info, requested_validation_layers, enabled_extensions); -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS vk::DebugUtilsMessengerCreateInfoEXT debug_utils_create_info; vk::DebugReportCallbackCreateInfoEXT debug_report_create_info; if (has_debug_utils) @@ -319,7 +327,7 @@ HPPInstance::HPPInstance(const std::string &applicati instance_info.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; #endif -#if (defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)) && (defined(VKB_VALIDATION_LAYERS_GPU_ASSISTED) || defined(VKB_VALIDATION_LAYERS_BEST_PRACTICES)) +#ifdef USE_VALIDATION_LAYER_FEATURES vk::ValidationFeaturesEXT validation_features_info; std::vector enable_features{}; if (validation_features) @@ -346,7 +354,7 @@ HPPInstance::HPPInstance(const std::string &applicati // Need to load volk for all the not-yet Vulkan-Hpp calls volkLoadInstance(handle); -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS if (has_debug_utils) { debug_utils_messenger = handle.createDebugUtilsMessengerEXT(debug_utils_create_info); @@ -375,7 +383,7 @@ HPPInstance::HPPInstance(vk::Instance instance) : HPPInstance::~HPPInstance() { -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS if (debug_utils_messenger) { handle.destroyDebugUtilsMessengerEXT(debug_utils_messenger); diff --git a/framework/core/hpp_vulkan_resource.h b/framework/core/hpp_vulkan_resource.h index 39e27b326..59cb79a27 100644 --- a/framework/core/hpp_vulkan_resource.h +++ b/framework/core/hpp_vulkan_resource.h @@ -26,11 +26,6 @@ namespace core { class HPPDevice; -namespace detail -{ -void set_debug_name(const HPPDevice *device, vk::ObjectType object_type, uint64_t handle, const char *debug_name); -} - /// Inherit this for any Vulkan object with a handle of type `HPPHandle`. /// /// This allows the derived class to store a Vulkan handle, and also a pointer to the parent vkb::core::Device. @@ -114,7 +109,11 @@ class HPPVulkanResource inline void set_debug_name(const std::string &name) { debug_name = name; - detail::set_debug_name(device, HPPHandle::objectType, get_handle_u64(), debug_name.c_str()); + + if (device && !debug_name.empty()) + { + device->get_debug_utils().set_debug_name(device->get_handle(), HPPHandle::objectType, get_handle_u64(), debug_name.c_str()); + } } private: diff --git a/framework/core/instance.cpp b/framework/core/instance.cpp index 70e9465d9..884e17df1 100644 --- a/framework/core/instance.cpp +++ b/framework/core/instance.cpp @@ -22,11 +22,19 @@ #include #include +#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +# define USE_VALIDATION_LAYERS +#endif + +#if defined(USE_VALIDATION_LAYERS) && (defined(VKB_VALIDATION_LAYERS_GPU_ASSISTED) || defined(VKB_VALIDATION_LAYERS_BEST_PRACTICES) || defined(VKB_VALIDATION_LAYERS_SYNCHRONIZATION)) +# define USE_VALIDATION_LAYER_FEATURES +#endif + namespace vkb { namespace { -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS VKAPI_ATTR VkBool32 VKAPI_CALL debug_utils_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_type, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, @@ -185,7 +193,7 @@ Instance::Instance(const std::string &application_nam std::vector available_instance_extensions(instance_extension_count); VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, available_instance_extensions.data())); -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS // Check if VK_EXT_debug_utils is supported, which supersedes VK_EXT_Debug_Report const bool has_debug_utils = enable_extension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, available_instance_extensions, enabled_extensions); @@ -208,7 +216,7 @@ Instance::Instance(const std::string &application_nam enable_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, available_instance_extensions, enabled_extensions); #endif -#if (defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)) +#ifdef USE_VALIDATION_LAYER_FEATURES bool validation_features = false; { uint32_t layer_instance_extension_count; @@ -282,7 +290,7 @@ Instance::Instance(const std::string &application_nam std::vector requested_validation_layers(required_validation_layers); -#ifdef VKB_VALIDATION_LAYERS +#ifdef USE_VALIDATION_LAYERS // Determine the optimal validation layers to enable that are necessary for useful debugging std::vector optimal_validation_layers = get_optimal_validation_layers(supported_validation_layers); requested_validation_layers.insert(requested_validation_layers.end(), optimal_validation_layers.begin(), optimal_validation_layers.end()); @@ -319,7 +327,7 @@ Instance::Instance(const std::string &application_nam instance_info.enabledLayerCount = to_u32(requested_validation_layers.size()); instance_info.ppEnabledLayerNames = requested_validation_layers.data(); -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS VkDebugUtilsMessengerCreateInfoEXT debug_utils_create_info = {VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT}; VkDebugReportCallbackCreateInfoEXT debug_report_create_info = {VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT}; if (has_debug_utils) @@ -338,12 +346,13 @@ Instance::Instance(const std::string &application_nam instance_info.pNext = &debug_report_create_info; } #endif + #if (defined(VKB_ENABLE_PORTABILITY)) instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #endif // Some of the specialized layers need to be enabled explicitly -#if (defined(VKB_VALIDATION_LAYERS_GPU_ASSISTED) || defined(VKB_VALIDATION_LAYERS_BEST_PRACTICES) || defined(VKB_VALIDATION_LAYERS_SYNCHRONIZATION)) +#ifdef USE_VALIDATION_LAYER_FEATURES VkValidationFeaturesEXT validation_features_info = {VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT}; std::vector enable_features{}; if (validation_features) @@ -375,7 +384,7 @@ Instance::Instance(const std::string &application_nam volkLoadInstance(handle); -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS if (has_debug_utils) { result = vkCreateDebugUtilsMessengerEXT(handle, &debug_utils_create_info, nullptr, &debug_utils_messenger); @@ -412,7 +421,7 @@ Instance::Instance(VkInstance instance) : Instance::~Instance() { -#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +#ifdef USE_VALIDATION_LAYERS if (debug_utils_messenger != VK_NULL_HANDLE) { vkDestroyDebugUtilsMessengerEXT(handle, debug_utils_messenger, nullptr); diff --git a/framework/hpp_api_vulkan_sample.cpp b/framework/hpp_api_vulkan_sample.cpp index fcf688586..0fe41ccbf 100644 --- a/framework/hpp_api_vulkan_sample.cpp +++ b/framework/hpp_api_vulkan_sample.cpp @@ -846,7 +846,7 @@ vk::ImageLayout HPPApiVulkanSample::descriptor_type_to_image_layout(vk::Descript } } -HPPTexture HPPApiVulkanSample::load_texture(const std::string &file, vkb::sg::Image::ContentType content_type) +HPPTexture HPPApiVulkanSample::load_texture(const std::string &file, vkb::sg::Image::ContentType content_type, vk::SamplerAddressMode address_mode) { HPPTexture texture; @@ -906,9 +906,9 @@ HPPTexture HPPApiVulkanSample::load_texture(const std::string &file, vkb::sg::Im sampler_create_info.magFilter = vk::Filter::eLinear; sampler_create_info.minFilter = vk::Filter::eLinear; sampler_create_info.mipmapMode = vk::SamplerMipmapMode::eLinear; - sampler_create_info.addressModeU = vk::SamplerAddressMode::eRepeat; - sampler_create_info.addressModeV = vk::SamplerAddressMode::eRepeat; - sampler_create_info.addressModeW = vk::SamplerAddressMode::eRepeat; + sampler_create_info.addressModeU = address_mode; + sampler_create_info.addressModeV = address_mode; + sampler_create_info.addressModeW = address_mode; sampler_create_info.mipLodBias = 0.0f; sampler_create_info.compareOp = vk::CompareOp::eNever; sampler_create_info.minLod = 0.0f; @@ -927,7 +927,7 @@ HPPTexture HPPApiVulkanSample::load_texture(const std::string &file, vkb::sg::Im return texture; } -HPPTexture HPPApiVulkanSample::load_texture_array(const std::string &file, vkb::sg::Image::ContentType content_type) +HPPTexture HPPApiVulkanSample::load_texture_array(const std::string &file, vkb::sg::Image::ContentType content_type, vk::SamplerAddressMode address_mode) { HPPTexture texture{}; @@ -993,9 +993,9 @@ HPPTexture HPPApiVulkanSample::load_texture_array(const std::string &file, vkb:: sampler_create_info.magFilter = vk::Filter::eLinear; sampler_create_info.minFilter = vk::Filter::eLinear; sampler_create_info.mipmapMode = vk::SamplerMipmapMode::eLinear; - sampler_create_info.addressModeU = vk::SamplerAddressMode::eClampToEdge; - sampler_create_info.addressModeV = vk::SamplerAddressMode::eClampToEdge; - sampler_create_info.addressModeW = vk::SamplerAddressMode::eClampToEdge; + sampler_create_info.addressModeU = address_mode; + sampler_create_info.addressModeV = address_mode; + sampler_create_info.addressModeW = address_mode; sampler_create_info.mipLodBias = 0.0f; sampler_create_info.compareOp = vk::CompareOp::eNever; sampler_create_info.minLod = 0.0f; diff --git a/framework/hpp_api_vulkan_sample.h b/framework/hpp_api_vulkan_sample.h index 728fe6cda..a4af8bc7f 100644 --- a/framework/hpp_api_vulkan_sample.h +++ b/framework/hpp_api_vulkan_sample.h @@ -165,15 +165,20 @@ class HPPApiVulkanSample : public vkb::HPPVulkanSample * @brief Loads in a ktx 2D texture * @param file The filename of the texture to load * @param content_type The type of content in the image file + * @param address_mode The address mode to use in u-, v-, and w-direction. Defaults to /c vk::SamplerAddressMode::eRepeat. */ - HPPTexture load_texture(const std::string &file, vkb::sg::Image::ContentType content_type); + HPPTexture + load_texture(const std::string &file, vkb::sg::Image::ContentType content_type, vk::SamplerAddressMode address_mode = vk::SamplerAddressMode::eRepeat); /** * @brief Loads in a ktx 2D texture array * @param file The filename of the texture to load * @param content_type The type of content in the image file + * @param address_mode The address mode to use in u-, v-, and w-direction. Defaults to /c vk::SamplerAddressMode::eClampToEdge. */ - HPPTexture load_texture_array(const std::string &file, vkb::sg::Image::ContentType content_type); + HPPTexture load_texture_array(const std::string &file, + vkb::sg::Image::ContentType content_type, + vk::SamplerAddressMode address_mode = vk::SamplerAddressMode::eClampToEdge); /** * @brief Loads in a ktx 2D texture cubemap diff --git a/framework/rendering/render_frame.cpp b/framework/rendering/render_frame.cpp index 4ce862e9b..e0e6436e1 100644 --- a/framework/rendering/render_frame.cpp +++ b/framework/rendering/render_frame.cpp @@ -289,23 +289,13 @@ BufferAllocation RenderFrame::allocate_buffer(const VkBufferUsageFlags usage, co bool want_minimal_block = buffer_allocation_strategy == BufferAllocationStrategy::OneAllocationPerBuffer; - if (want_minimal_block || !buffer_block) + if (want_minimal_block || !buffer_block || !buffer_block->can_allocate(size)) { - // If there is no block associated with the pool or we are creating a buffer for each allocation, - // request a new buffer block - buffer_block = &buffer_pool.request_buffer_block(to_u32(size), want_minimal_block); + // If we are creating a buffer for each allocation of there is no block associated with the pool or the current block is too small + // for this allocation, request a new buffer block + buffer_block = &buffer_pool.request_buffer_block(size, want_minimal_block); } - auto data = buffer_block->allocate(to_u32(size)); - - // Check if the buffer block can allocate the requested size - if (data.empty()) - { - buffer_block = &buffer_pool.request_buffer_block(to_u32(size), want_minimal_block); - - data = buffer_block->allocate(to_u32(size)); - } - - return data; + return buffer_block->allocate(to_u32(size)); } } // namespace vkb diff --git a/samples/README.adoc b/samples/README.adoc index 64555324a..904fef314 100644 --- a/samples/README.adoc +++ b/samples/README.adoc @@ -481,10 +481,17 @@ Demonstrate how to create multiple color blend attachments and then toggle them Demonstrate how to use shader objects. === link:./extensions/dynamic_blending[Dynamic blending] + *Extension:* https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_extended_dynamic_state.html[`VK_EXT_extended_dynamic_state3`] Demonstrate how to use the blending related functions available in the VK_EXT_extended_dynamic_state3 extension. +=== link:./extensions/dynamic_line_rasterization[Dynamic line rasterization] + +*Extensions:* https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_line_rasterization.html[`VK_EXT_line_rasterization`], https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_extended_dynamic_state3.html[`VK_EXT_extended_dynamic_state3`] + +Demonstrate methods for dynamically customizing the appearance of the rendered lines. + == Tooling Samples The goal of these samples is to demonstrate usage of tooling functions and libraries that are not directly part of the api. diff --git a/samples/api/dynamic_uniform_buffers/dynamic_uniform_buffers.cpp b/samples/api/dynamic_uniform_buffers/dynamic_uniform_buffers.cpp index eb6756144..5baf05e41 100644 --- a/samples/api/dynamic_uniform_buffers/dynamic_uniform_buffers.cpp +++ b/samples/api/dynamic_uniform_buffers/dynamic_uniform_buffers.cpp @@ -213,13 +213,13 @@ void DynamicUniformBuffers::generate_cube() vertex_buffer = std::make_unique(get_device(), vertex_buffer_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VMA_MEMORY_USAGE_GPU_TO_CPU); + VMA_MEMORY_USAGE_CPU_TO_GPU); vertex_buffer->update(vertices.data(), vertex_buffer_size); index_buffer = std::make_unique(get_device(), index_buffer_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - VMA_MEMORY_USAGE_GPU_TO_CPU); + VMA_MEMORY_USAGE_CPU_TO_GPU); index_buffer->update(indices.data(), index_buffer_size); } diff --git a/samples/api/hpp_compute_nbody/hpp_compute_nbody.cpp b/samples/api/hpp_compute_nbody/hpp_compute_nbody.cpp index 40c55c030..e2a16d102 100644 --- a/samples/api/hpp_compute_nbody/hpp_compute_nbody.cpp +++ b/samples/api/hpp_compute_nbody/hpp_compute_nbody.cpp @@ -44,20 +44,20 @@ HPPComputeNBody::~HPPComputeNBody() bool HPPComputeNBody::prepare(const vkb::ApplicationOptions &options) { - if (!HPPApiVulkanSample::prepare(options)) - { - return false; - } + assert(!prepared); - load_assets(); + if (HPPApiVulkanSample::prepare(options)) + { + load_assets(); + descriptor_pool = create_descriptor_pool(); + prepare_graphics(); + prepare_compute(); + build_command_buffers(); - descriptor_pool = create_descriptor_pool(); + prepared = true; + } - prepare_graphics(); - prepare_compute(); - build_command_buffers(); - prepared = true; - return true; + return prepared; } bool HPPComputeNBody::resize(const uint32_t width, const uint32_t height) @@ -145,13 +145,14 @@ void HPPComputeNBody::build_command_buffers() void HPPComputeNBody::render(float delta_time) { - if (!prepared) - return; - draw(); - update_compute_uniform_buffers(delta_time); - if (camera.updated) + if (prepared) { - update_graphics_uniform_buffers(); + draw(); + update_compute_uniform_buffers(delta_time); + if (camera.updated) + { + update_graphics_uniform_buffers(); + } } } @@ -305,8 +306,8 @@ vk::DescriptorSetLayout HPPComputeNBody::create_graphics_descriptor_set_layout() vk::Pipeline HPPComputeNBody::create_graphics_pipeline() { // Load shaders - std::array shader_stages = {{load_shader("compute_nbody/particle.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("compute_nbody/particle.frag", vk::ShaderStageFlagBits::eFragment)}}; + std::vector shader_stages = {load_shader("compute_nbody/particle.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("compute_nbody/particle.frag", vk::ShaderStageFlagBits::eFragment)}; // Vertex bindings and attributes vk::VertexInputBindingDescription vertex_input_bindings(0, sizeof(Particle), vk::VertexInputRate::eVertex); @@ -338,6 +339,8 @@ vk::Pipeline HPPComputeNBody::create_graphics_pipeline() shader_stages, vertex_input_state, vk::PrimitiveTopology::ePointList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, vk::FrontFace::eCounterClockwise, {blend_attachment_state}, diff --git a/samples/api/hpp_dynamic_uniform_buffers/hpp_dynamic_uniform_buffers.cpp b/samples/api/hpp_dynamic_uniform_buffers/hpp_dynamic_uniform_buffers.cpp index c178cd6f7..fd2d6f4e1 100644 --- a/samples/api/hpp_dynamic_uniform_buffers/hpp_dynamic_uniform_buffers.cpp +++ b/samples/api/hpp_dynamic_uniform_buffers/hpp_dynamic_uniform_buffers.cpp @@ -83,25 +83,25 @@ void HPPDynamicUniformBuffers::aligned_free(void *data) bool HPPDynamicUniformBuffers::prepare(const vkb::ApplicationOptions &options) { - if (!HPPApiVulkanSample::prepare(options)) + assert(!prepared); + + if (HPPApiVulkanSample::prepare(options)) { - return false; + prepare_camera(); + generate_cube(); + prepare_uniform_buffers(); + descriptor_set_layout = create_descriptor_set_layout(); + pipeline_layout = get_device()->get_handle().createPipelineLayout({{}, descriptor_set_layout}); + pipeline = create_pipeline(); + descriptor_pool = create_descriptor_pool(); + descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, descriptor_set_layout); + update_descriptor_set(); + build_command_buffers(); + + prepared = true; } - prepare_camera(); - generate_cube(); - prepare_uniform_buffers(); - - descriptor_set_layout = create_descriptor_set_layout(); - pipeline_layout = get_device()->get_handle().createPipelineLayout({{}, descriptor_set_layout}); - pipeline = create_pipeline(); - descriptor_pool = create_descriptor_pool(); - descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, descriptor_set_layout); - - update_descriptor_set(); - build_command_buffers(); - prepared = true; - return true; + return prepared; } bool HPPDynamicUniformBuffers::resize(const uint32_t width, const uint32_t height) @@ -151,18 +151,17 @@ void HPPDynamicUniformBuffers::build_command_buffers() void HPPDynamicUniformBuffers::render(float delta_time) { - if (!prepared) - { - return; - } - draw(); - if (!paused) + if (prepared) { - update_dynamic_uniform_buffer(delta_time); - } - if (camera.updated) - { - update_uniform_buffers(); + draw(); + if (!paused) + { + update_dynamic_uniform_buffer(delta_time); + } + if (camera.updated) + { + update_uniform_buffers(); + } } } @@ -186,9 +185,8 @@ vk::DescriptorSetLayout HPPDynamicUniformBuffers::create_descriptor_set_layout() vk::Pipeline HPPDynamicUniformBuffers::create_pipeline() { // Load shaders - std::array shader_stages = {{load_shader("dynamic_uniform_buffers/base.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("dynamic_uniform_buffers/base.frag", - vk::ShaderStageFlagBits::eFragment)}}; + std::vector shader_stages = {load_shader("dynamic_uniform_buffers/base.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("dynamic_uniform_buffers/base.frag", vk::ShaderStageFlagBits::eFragment)}; // Vertex bindings and attributes vk::VertexInputBindingDescription vertex_input_binding(0, sizeof(Vertex), vk::VertexInputRate::eVertex); @@ -213,6 +211,8 @@ vk::Pipeline HPPDynamicUniformBuffers::create_pipeline() shader_stages, vertex_input_state, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, vk::FrontFace::eCounterClockwise, {blend_attachment_state}, @@ -260,10 +260,10 @@ void HPPDynamicUniformBuffers::generate_cube() // For the sake of simplicity we won't stage the vertex data to the gpu memory // Vertex buffer vertex_buffer = - std::make_unique(*get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_GPU_TO_CPU); + std::make_unique(*get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); vertex_buffer->update(vertices.data(), vertex_buffer_size); - index_buffer = std::make_unique(*get_device(), index_buffer_size, vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_GPU_TO_CPU); + index_buffer = std::make_unique(*get_device(), index_buffer_size, vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); index_buffer->update(indices.data(), index_buffer_size); } diff --git a/samples/api/hpp_hdr/hpp_hdr.cpp b/samples/api/hpp_hdr/hpp_hdr.cpp index a6570127d..d741aad85 100644 --- a/samples/api/hpp_hdr/hpp_hdr.cpp +++ b/samples/api/hpp_hdr/hpp_hdr.cpp @@ -43,23 +43,23 @@ HPPHDR::~HPPHDR() bool HPPHDR::prepare(const vkb::ApplicationOptions &options) { - if (!HPPApiVulkanSample::prepare(options)) + assert(!prepared); + if (HPPApiVulkanSample::prepare(options)) { - return false; + prepare_camera(); + load_assets(); + prepare_uniform_buffers(); + prepare_offscreen_buffer(); + descriptor_pool = create_descriptor_pool(); + setup_bloom(); + setup_composition(); + setup_models(); + build_command_buffers(); + + prepared = true; } - prepare_camera(); - load_assets(); - prepare_uniform_buffers(); - prepare_offscreen_buffer(); - - descriptor_pool = create_descriptor_pool(); - setup_bloom(); - setup_composition(); - setup_models(); - build_command_buffers(); - prepared = true; - return true; + return prepared; } bool HPPHDR::resize(const uint32_t width, const uint32_t height) @@ -210,14 +210,13 @@ void HPPHDR::on_update_ui_overlay(vkb::HPPDrawer &drawer) void HPPHDR::render(float delta_time) { - if (!prepared) - { - return; - } - draw(); - if (camera.updated) + if (prepared) { - update_uniform_buffers(); + draw(); + if (camera.updated) + { + update_uniform_buffers(); + } } } @@ -249,9 +248,8 @@ vk::DescriptorPool HPPHDR::create_descriptor_pool() vk::Pipeline HPPHDR::create_bloom_pipeline(uint32_t direction) { - std::array shader_stages{ - load_shader("hdr/bloom.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("hdr/bloom.frag", vk::ShaderStageFlagBits::eFragment)}; + std::vector shader_stages{load_shader("hdr/bloom.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("hdr/bloom.frag", vk::ShaderStageFlagBits::eFragment)}; // Set constant parameters via specialization constants vk::SpecializationMapEntry specialization_map_entry(0, 0, sizeof(uint32_t)); @@ -282,6 +280,8 @@ vk::Pipeline HPPHDR::create_bloom_pipeline(uint32_t direction) shader_stages, {}, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eFront, vk::FrontFace::eCounterClockwise, {blend_attachment_state}, @@ -292,8 +292,8 @@ vk::Pipeline HPPHDR::create_bloom_pipeline(uint32_t direction) vk::Pipeline HPPHDR::create_composition_pipeline() { - std::array shader_stages{load_shader("hdr/composition.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("hdr/composition.frag", vk::ShaderStageFlagBits::eFragment)}; + std::vector shader_stages{load_shader("hdr/composition.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("hdr/composition.frag", vk::ShaderStageFlagBits::eFragment)}; vk::PipelineColorBlendAttachmentState blend_attachment_state; blend_attachment_state.colorWriteMask = @@ -311,6 +311,8 @@ vk::Pipeline HPPHDR::create_composition_pipeline() shader_stages, {}, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eFront, vk::FrontFace::eCounterClockwise, {blend_attachment_state}, @@ -364,9 +366,8 @@ vk::ImageView HPPHDR::create_image_view(vk::Format format, vk::ImageUsageFlagBit vk::Pipeline HPPHDR::create_models_pipeline(uint32_t shaderType, vk::CullModeFlagBits cullMode, bool depthTestAndWrite) { - std::array shader_stages{ - load_shader("hdr/gbuffer.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("hdr/gbuffer.frag", vk::ShaderStageFlagBits::eFragment)}; + std::vector shader_stages{load_shader("hdr/gbuffer.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("hdr/gbuffer.frag", vk::ShaderStageFlagBits::eFragment)}; // Set constant parameters via specialization constants vk::SpecializationMapEntry specialization_map_entry(0, 0, sizeof(uint32_t)); @@ -404,6 +405,8 @@ vk::Pipeline HPPHDR::create_models_pipeline(uint32_t shaderType, vk::CullModeFla shader_stages, vertex_input_state, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, cullMode, vk::FrontFace::eCounterClockwise, blend_attachment_states, diff --git a/samples/api/hpp_hello_triangle/hpp_hello_triangle.cpp b/samples/api/hpp_hello_triangle/hpp_hello_triangle.cpp index 44df00ccf..0d8c73042 100644 --- a/samples/api/hpp_hello_triangle/hpp_hello_triangle.cpp +++ b/samples/api/hpp_hello_triangle/hpp_hello_triangle.cpp @@ -195,40 +195,38 @@ HPPHelloTriangle::~HPPHelloTriangle() bool HPPHelloTriangle::prepare(const vkb::ApplicationOptions &options) { - if (!Application::prepare(options)) + if (Application::prepare(options)) { - return false; - } - - instance = create_instance({VK_KHR_SURFACE_EXTENSION_NAME}, {}); + instance = create_instance({VK_KHR_SURFACE_EXTENSION_NAME}, {}); #if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) - debug_utils_messenger = instance.createDebugUtilsMessengerEXT(debug_utils_create_info); + debug_utils_messenger = instance.createDebugUtilsMessengerEXT(debug_utils_create_info); #endif - select_physical_device_and_surface(); + select_physical_device_and_surface(); - const vkb::Window::Extent &extent = options.window->get_extent(); - swapchain_data.extent.width = extent.width; - swapchain_data.extent.height = extent.height; + const vkb::Window::Extent &extent = options.window->get_extent(); + swapchain_data.extent.width = extent.width; + swapchain_data.extent.height = extent.height; - // create a device - device = create_device({VK_KHR_SWAPCHAIN_EXTENSION_NAME}); + // create a device + device = create_device({VK_KHR_SWAPCHAIN_EXTENSION_NAME}); - // get the (graphics) queue - queue = device.getQueue(graphics_queue_index, 0); + // get the (graphics) queue + queue = device.getQueue(graphics_queue_index, 0); - init_swapchain(); + init_swapchain(); - // Create the necessary objects for rendering. - render_pass = create_render_pass(); + // Create the necessary objects for rendering. + render_pass = create_render_pass(); - // Create a blank pipeline layout. - // We are not binding any resources to the pipeline in this first sample. - pipeline_layout = device.createPipelineLayout({}); + // Create a blank pipeline layout. + // We are not binding any resources to the pipeline in this first sample. + pipeline_layout = device.createPipelineLayout({}); - pipeline = create_graphics_pipeline(); + pipeline = create_graphics_pipeline(); - init_framebuffers(); + init_framebuffers(); + } return true; } @@ -376,7 +374,7 @@ vk::Device HPPHelloTriangle::create_device(const std::vector &requ vk::Pipeline HPPHelloTriangle::create_graphics_pipeline() { // Load our SPIR-V shaders. - std::array shader_stages{ + std::vector shader_stages{ vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eVertex, create_shader_module("triangle.vert"), "main"), vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eFragment, create_shader_module("triangle.frag"), "main")}; @@ -394,6 +392,8 @@ vk::Pipeline HPPHelloTriangle::create_graphics_pipeline() shader_stages, vertex_input, vk::PrimitiveTopology::eTriangleList, // We will use triangle lists to draw geometry. + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eBack, vk::FrontFace::eClockwise, {blend_attachment}, diff --git a/samples/api/hpp_hlsl_shaders/hpp_hlsl_shaders.cpp b/samples/api/hpp_hlsl_shaders/hpp_hlsl_shaders.cpp index 14b638116..bbab62712 100644 --- a/samples/api/hpp_hlsl_shaders/hpp_hlsl_shaders.cpp +++ b/samples/api/hpp_hlsl_shaders/hpp_hlsl_shaders.cpp @@ -53,36 +53,37 @@ HPPHlslShaders::~HPPHlslShaders() bool HPPHlslShaders::prepare(const vkb::ApplicationOptions &options) { - if (!HPPApiVulkanSample::prepare(options)) + assert(!prepared); + + if (HPPApiVulkanSample::prepare(options)) { - return false; + load_assets(); + generate_quad(); + + // Vertex shader uniform buffer block + uniform_buffer_vs = std::make_unique(*get_device(), + sizeof(ubo_vs), + vk::BufferUsageFlagBits::eUniformBuffer, + VMA_MEMORY_USAGE_CPU_TO_GPU); + update_uniform_buffers(); + + // We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former + base_descriptor_set_layout = create_base_descriptor_set_layout(); + sampler_descriptor_set_layout = create_sampler_descriptor_set_layout(); + + pipeline_layout = create_pipeline_layout(); + shader_modules = {create_shader_module("hlsl_shaders/hlsl_shader.vert", vk::ShaderStageFlagBits::eVertex), + create_shader_module("hlsl_shaders/hlsl_shader.frag", vk::ShaderStageFlagBits::eFragment)}; + pipeline = create_pipeline(); + descriptor_pool = create_descriptor_pool(); + base_descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, base_descriptor_set_layout); + update_descriptor_sets(); + build_command_buffers(); + + prepared = true; } - load_assets(); - generate_quad(); - - // Vertex shader uniform buffer block - uniform_buffer_vs = std::make_unique(*get_device(), - sizeof(ubo_vs), - vk::BufferUsageFlagBits::eUniformBuffer, - VMA_MEMORY_USAGE_CPU_TO_GPU); - update_uniform_buffers(); - - // We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former - base_descriptor_set_layout = create_base_descriptor_set_layout(); - sampler_descriptor_set_layout = create_sampler_descriptor_set_layout(); - - pipeline_layout = create_pipeline_layout(); - shader_modules.push_back(create_shader_module("hlsl_shaders/hlsl_shader.vert", vk::ShaderStageFlagBits::eVertex)); - shader_modules.push_back(create_shader_module("hlsl_shaders/hlsl_shader.frag", vk::ShaderStageFlagBits::eFragment)); - pipeline = create_pipeline(); - - descriptor_pool = create_descriptor_pool(); - base_descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, base_descriptor_set_layout); - update_descriptor_sets(); - build_command_buffers(); - prepared = true; - return true; + return prepared; } // Enable physical device features required for this example @@ -139,11 +140,10 @@ void HPPHlslShaders::build_command_buffers() void HPPHlslShaders::render(float delta_time) { - if (!prepared) + if (prepared) { - return; + draw(); } - draw(); } void HPPHlslShaders::view_changed() @@ -169,9 +169,9 @@ vk::DescriptorPool HPPHlslShaders::create_descriptor_pool() vk::Pipeline HPPHlslShaders::create_pipeline() { - size_t vertex_shader_index = shader_modules.size() - 2; - std::array shader_stages{{{{}, vk::ShaderStageFlagBits::eVertex, shader_modules[vertex_shader_index], "main"}, - {{}, vk::ShaderStageFlagBits::eFragment, shader_modules[vertex_shader_index + 1], "main"}}}; + size_t vertex_shader_index = shader_modules.size() - 2; + std::vector shader_stages{{{}, vk::ShaderStageFlagBits::eVertex, shader_modules[vertex_shader_index], "main"}, + {{}, vk::ShaderStageFlagBits::eFragment, shader_modules[vertex_shader_index + 1], "main"}}; // Vertex bindings and attributes vk::VertexInputBindingDescription vertex_input_binding(0, sizeof(VertexStructure), vk::VertexInputRate::eVertex); @@ -194,6 +194,8 @@ vk::Pipeline HPPHlslShaders::create_pipeline() shader_stages, vertex_input_state, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, vk::FrontFace::eCounterClockwise, {blend_attachment_state}, diff --git a/samples/api/hpp_instancing/hpp_instancing.cpp b/samples/api/hpp_instancing/hpp_instancing.cpp index 8ea7514ca..8dffef914 100644 --- a/samples/api/hpp_instancing/hpp_instancing.cpp +++ b/samples/api/hpp_instancing/hpp_instancing.cpp @@ -46,38 +46,38 @@ HPPInstancing::~HPPInstancing() bool HPPInstancing::prepare(const vkb::ApplicationOptions &options) { - if (!HPPApiVulkanSample::prepare(options)) + assert(!prepared); + + if (HPPApiVulkanSample::prepare(options)) { - return false; + initialize_camera(); + load_assets(); + prepare_instance_data(); + prepare_uniform_buffers(); + vk::Device device = get_device()->get_handle(); + descriptor_set_layout = create_descriptor_set_layout(); + pipeline_layout = device.createPipelineLayout({{}, descriptor_set_layout}); + descriptor_pool = create_descriptor_pool(); + + // setup planet + planet.pipeline = create_planet_pipeline(); + planet.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, descriptor_set_layout); + update_planet_descriptor_set(); + + // setup rocks + rocks.pipeline = create_rocks_pipeline(); + rocks.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, descriptor_set_layout); + update_rocks_descriptor_set(); + + // setup starfield + starfield_pipeline = create_starfield_pipeline(); + + build_command_buffers(); + + prepared = true; } - initialize_camera(); - load_assets(); - prepare_instance_data(); - prepare_uniform_buffers(); - - vk::Device device = get_device()->get_handle(); - - descriptor_set_layout = create_descriptor_set_layout(); - pipeline_layout = device.createPipelineLayout({{}, descriptor_set_layout}); - descriptor_pool = create_descriptor_pool(); - - // setup planet - planet.pipeline = create_planet_pipeline(); - planet.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, descriptor_set_layout); - update_planet_descriptor_set(); - - // setup rocks - rocks.pipeline = create_rocks_pipeline(); - rocks.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, descriptor_set_layout); - update_rocks_descriptor_set(); - - // setup starfield - starfield_pipeline = create_starfield_pipeline(); - - build_command_buffers(); - prepared = true; - return true; + return prepared; } bool HPPInstancing::resize(const uint32_t width, const uint32_t height) @@ -213,8 +213,8 @@ vk::DescriptorSetLayout HPPInstancing::create_descriptor_set_layout() vk::Pipeline HPPInstancing::create_planet_pipeline() { // Planet rendering pipeline - std::array shader_stages = {load_shader("instancing/planet.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("instancing/planet.frag", vk::ShaderStageFlagBits::eFragment)}; + std::vector shader_stages = {load_shader("instancing/planet.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("instancing/planet.frag", vk::ShaderStageFlagBits::eFragment)}; // Vertex input bindings vk::VertexInputBindingDescription binding_description(0, sizeof(HPPVertex), vk::VertexInputRate::eVertex); @@ -247,6 +247,8 @@ vk::Pipeline HPPInstancing::create_planet_pipeline() shader_stages, input_state, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eBack, vk::FrontFace::eClockwise, {blend_attachment_state}, @@ -257,8 +259,8 @@ vk::Pipeline HPPInstancing::create_planet_pipeline() vk::Pipeline HPPInstancing::create_rocks_pipeline() { - std::array shader_stages{load_shader("instancing/instancing.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("instancing/instancing.frag", vk::ShaderStageFlagBits::eFragment)}; + std::vector shader_stages{load_shader("instancing/instancing.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("instancing/instancing.frag", vk::ShaderStageFlagBits::eFragment)}; // Vertex input bindings // The instancing pipeline uses a vertex input state with two bindings @@ -305,6 +307,8 @@ vk::Pipeline HPPInstancing::create_rocks_pipeline() shader_stages, input_state, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eBack, vk::FrontFace::eClockwise, {blend_attachment_state}, @@ -316,8 +320,8 @@ vk::Pipeline HPPInstancing::create_rocks_pipeline() vk::Pipeline HPPInstancing::create_starfield_pipeline() { // Starfield rendering pipeline - std::array shader_stages = {load_shader("instancing/starfield.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("instancing/starfield.frag", vk::ShaderStageFlagBits::eFragment)}; + std::vector shader_stages = {load_shader("instancing/starfield.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("instancing/starfield.frag", vk::ShaderStageFlagBits::eFragment)}; // Vertex input bindings vk::VertexInputBindingDescription binding_description(0, sizeof(HPPVertex), vk::VertexInputRate::eVertex); @@ -350,6 +354,8 @@ vk::Pipeline HPPInstancing::create_starfield_pipeline() shader_stages, {}, // Vertices are generated in the vertex shader vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, vk::FrontFace::eClockwise, {blend_attachment_state}, diff --git a/samples/api/hpp_separate_image_sampler/hpp_separate_image_sampler.cpp b/samples/api/hpp_separate_image_sampler/hpp_separate_image_sampler.cpp index 62b7a6420..0ff5654e5 100644 --- a/samples/api/hpp_separate_image_sampler/hpp_separate_image_sampler.cpp +++ b/samples/api/hpp_separate_image_sampler/hpp_separate_image_sampler.cpp @@ -53,47 +53,44 @@ HPPSeparateImageSampler::~HPPSeparateImageSampler() bool HPPSeparateImageSampler::prepare(const vkb::ApplicationOptions &options) { - if (!HPPApiVulkanSample::prepare(options)) + assert(!prepared); + + if (HPPApiVulkanSample::prepare(options)) { - return false; - } + load_assets(); + generate_quad(); + prepare_uniform_buffers(); - load_assets(); - generate_quad(); - prepare_uniform_buffers(); + // Create two samplers with different options, first one with linear filtering, the second one with nearest filtering + samplers = {{create_sampler(vk::Filter::eLinear), create_sampler(vk::Filter::eNearest)}}; - // Create two samplers with different options - // First sampler with linear filtering - samplers[0] = create_sampler(vk::Filter::eLinear); - // Second sampler with nearest filtering - samplers[1] = create_sampler(vk::Filter::eNearest); + descriptor_pool = create_descriptor_pool(); - descriptor_pool = create_descriptor_pool(); + // We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former + // Descriptors set for the uniform buffer and the image + base_descriptor_set_layout = create_base_descriptor_set_layout(); + base_descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, base_descriptor_set_layout); + update_base_descriptor_set(); - vk::Device device = get_device()->get_handle(); + // Sets for each of the sampler + sampler_descriptor_set_layout = create_sampler_descriptor_set_layout(); + for (size_t i = 0; i < sampler_descriptor_sets.size(); i++) + { + sampler_descriptor_sets[i] = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, sampler_descriptor_set_layout); + update_sampler_descriptor_set(i); + } - // We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former - // Descriptors set for the uniform buffer and the image - base_descriptor_set_layout = create_base_descriptor_set_layout(); - base_descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, base_descriptor_set_layout); - update_base_descriptor_set(); + // Pipeline layout + // Set layout for the base descriptors in set 0 and set layout for the sampler descriptors in set 1 + pipeline_layout = create_pipeline_layout({base_descriptor_set_layout, sampler_descriptor_set_layout}); + pipeline = create_graphics_pipeline(); - // Sets for each of the sampler - sampler_descriptor_set_layout = create_sampler_descriptor_set_layout(); - for (size_t i = 0; i < sampler_descriptor_sets.size(); i++) - { - sampler_descriptor_sets[i] = vkb::common::allocate_descriptor_set(device, descriptor_pool, sampler_descriptor_set_layout); - update_sampler_descriptor_set(i); - } + build_command_buffers(); - // Pipeline layout - // Set layout for the base descriptors in set 0 and set layout for the sampler descriptors in set 1 - pipeline_layout = create_pipeline_layout({base_descriptor_set_layout, sampler_descriptor_set_layout}); - pipeline = create_graphics_pipeline(); + prepared = true; + } - build_command_buffers(); - prepared = true; - return true; + return prepared; } // Enable physical device features required for this example @@ -163,11 +160,10 @@ void HPPSeparateImageSampler::on_update_ui_overlay(vkb::HPPDrawer &drawer) void HPPSeparateImageSampler::render(float delta_time) { - if (!prepared) + if (prepared) { - return; + draw(); } - draw(); } void HPPSeparateImageSampler::view_changed() @@ -201,8 +197,9 @@ vk::DescriptorPool HPPSeparateImageSampler::create_descriptor_pool() vk::Pipeline HPPSeparateImageSampler::create_graphics_pipeline() { // Load shaders - std::array shader_stages = {{load_shader("separate_image_sampler/separate_image_sampler.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("separate_image_sampler/separate_image_sampler.frag", vk::ShaderStageFlagBits::eFragment)}}; + std::vector shader_stages = { + load_shader("separate_image_sampler/separate_image_sampler.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("separate_image_sampler/separate_image_sampler.frag", vk::ShaderStageFlagBits::eFragment)}; // Vertex bindings and attributes vk::VertexInputBindingDescription input_binding(0, sizeof(VertexStructure), vk::VertexInputRate::eVertex); @@ -228,6 +225,8 @@ vk::Pipeline HPPSeparateImageSampler::create_graphics_pipeline() shader_stages, input_state, vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, vk::FrontFace::eCounterClockwise, {blend_attachment_state}, diff --git a/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.cpp b/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.cpp index 5483cb204..991d030b1 100644 --- a/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.cpp +++ b/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.cpp @@ -21,6 +21,7 @@ #include "hpp_terrain_tessellation.h" +#include #include HPPTerrainTessellation::HPPTerrainTessellation() @@ -36,66 +37,41 @@ HPPTerrainTessellation::~HPPTerrainTessellation() // Clean up used Vulkan resources // Note : Inherited destructor cleans up resources stored in base class - device.destroyPipeline(pipelines.skysphere); - device.destroyPipeline(pipelines.terrain); - if (pipelines.wireframe) - { - device.destroyPipeline(pipelines.wireframe); - } - - device.destroyPipelineLayout(pipeline_layouts.skysphere); - device.destroyPipelineLayout(pipeline_layouts.terrain); - - device.destroyDescriptorSetLayout(descriptor_set_layouts.skysphere); - device.destroyDescriptorSetLayout(descriptor_set_layouts.terrain); - - device.destroySampler(textures.heightmap.sampler); - device.destroySampler(textures.skysphere.sampler); - device.destroySampler(textures.terrain_array.sampler); - - if (query_pool) - { - device.destroyQueryPool(query_pool); - device.destroyBuffer(query_result.buffer); - device.freeMemory(query_result.memory); - } + sky_sphere.destroy(device); + terrain.destroy(device); + wireframe.destroy(device); + statistics.destroy(device); } } bool HPPTerrainTessellation::prepare(const vkb::ApplicationOptions &options) { - if (!HPPApiVulkanSample::prepare(options)) - { - return false; - } + assert(!prepared); - // Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped - camera.type = vkb::CameraType::FirstPerson; - camera.set_perspective(60.0f, static_cast(extent.width) / static_cast(extent.height), 512.0f, 0.1f); - camera.set_rotation(glm::vec3(-12.0f, 159.0f, 0.0f)); - camera.set_translation(glm::vec3(18.0f, 22.5f, 57.5f)); - camera.translation_speed = 7.5f; - - load_assets(); - generate_terrain(); - if (get_device()->get_gpu().get_features().pipelineStatisticsQuery) + if (HPPApiVulkanSample::prepare(options)) { - setup_query_result_buffer(); + prepare_camera(); + load_assets(); + generate_terrain(); + prepare_uniform_buffers(); + descriptor_pool = create_descriptor_pool(); + prepare_sky_sphere(); + prepare_terrain(); + prepare_wireframe(); + prepare_statistics(); + build_command_buffers(); + + prepared = true; } - prepare_uniform_buffers(); - setup_descriptor_set_layouts(); - prepare_pipelines(); - setup_descriptor_pool(); - setup_descriptor_sets(); - build_command_buffers(); - prepared = true; - return true; + + return prepared; } void HPPTerrainTessellation::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) { // Tessellation shader support is required for this example - if (!gpu.get_features().tessellationShader) + auto &available_features = gpu.get_features(); + if (!available_features.tessellationShader) { throw vkb::VulkanException(VK_ERROR_FEATURE_NOT_PRESENT, "Selected GPU does not support tessellation shaders!"); } @@ -105,13 +81,16 @@ void HPPTerrainTessellation::request_gpu_features(vkb::core::HPPPhysicalDevice & requested_features.tessellationShader = true; // Fill mode non solid is required for wireframe display - requested_features.fillModeNonSolid = gpu.get_features().fillModeNonSolid; + requested_features.fillModeNonSolid = available_features.fillModeNonSolid; + wireframe.supported = available_features.fillModeNonSolid; // Pipeline statistics - requested_features.pipelineStatisticsQuery = gpu.get_features().pipelineStatisticsQuery; + requested_features.pipelineStatisticsQuery = available_features.pipelineStatisticsQuery; + statistics.query_supported = available_features.pipelineStatisticsQuery; // Enable anisotropic filtering if supported - requested_features.samplerAnisotropy = gpu.get_features().samplerAnisotropy; + requested_features.samplerAnisotropy = available_features.samplerAnisotropy; + terrain.sampler_anisotropy_supported = available_features.samplerAnisotropy; } void HPPTerrainTessellation::build_command_buffers() @@ -123,55 +102,55 @@ void HPPTerrainTessellation::build_command_buffers() for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i) { - draw_cmd_buffers[i].begin(command_buffer_begin_info); + auto command_buffer = draw_cmd_buffers[i]; + command_buffer.begin(command_buffer_begin_info); - if (get_device()->get_gpu().get_features().pipelineStatisticsQuery) + if (statistics.query_supported) { - draw_cmd_buffers[i].resetQueryPool(query_pool, 0, 2); + command_buffer.resetQueryPool(statistics.query_pool, 0, 2); } render_pass_begin_info.framebuffer = framebuffers[i]; - draw_cmd_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); + command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); vk::Viewport viewport(0.0f, 0.0f, static_cast(extent.width), static_cast(extent.height), 0.0f, 1.0f); - draw_cmd_buffers[i].setViewport(0, viewport); + command_buffer.setViewport(0, viewport); vk::Rect2D scissor({0, 0}, extent); - draw_cmd_buffers[i].setScissor(0, scissor); - - draw_cmd_buffers[i].setLineWidth(1.0f); + command_buffer.setScissor(0, scissor); vk::DeviceSize offset = 0; // Skysphere - draw_cmd_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipelines.skysphere); - draw_cmd_buffers[i].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layouts.skysphere, 0, descriptor_sets.skysphere, {}); - draw_model(skysphere, draw_cmd_buffers[i]); + command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, sky_sphere.pipeline); + command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, sky_sphere.pipeline_layout, 0, sky_sphere.descriptor_set, {}); + draw_model(sky_sphere.geometry, command_buffer); // Terrain - if (get_device()->get_gpu().get_features().pipelineStatisticsQuery) + if (statistics.query_supported) { // Begin pipeline statistics query - draw_cmd_buffers[i].beginQuery(query_pool, 0, {}); + command_buffer.beginQuery(statistics.query_pool, 0, {}); } // Render - draw_cmd_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, wireframe ? pipelines.wireframe : pipelines.terrain); - draw_cmd_buffers[i].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layouts.terrain, 0, descriptor_sets.terrain, {}); - draw_cmd_buffers[i].bindVertexBuffers(0, terrain.vertices->get_handle(), offset); - draw_cmd_buffers[i].bindIndexBuffer(terrain.indices->get_handle(), 0, vk::IndexType::eUint32); - draw_cmd_buffers[i].drawIndexed(terrain.index_count, 1, 0, 0, 0); - if (get_device()->get_gpu().get_features().pipelineStatisticsQuery) + command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, wireframe.enabled ? wireframe.pipeline : terrain.pipeline); + command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, terrain.pipeline_layout, 0, terrain.descriptor_set, {}); + command_buffer.bindVertexBuffers(0, terrain.vertices->get_handle(), offset); + command_buffer.bindIndexBuffer(terrain.indices->get_handle(), 0, vk::IndexType::eUint32); + command_buffer.drawIndexed(terrain.index_count, 1, 0, 0, 0); + + if (statistics.query_supported) { // End pipeline statistics query - draw_cmd_buffers[i].endQuery(query_pool, 0); + command_buffer.endQuery(statistics.query_pool, 0); } - draw_ui(draw_cmd_buffers[i]); + draw_ui(command_buffer); - draw_cmd_buffers[i].endRenderPass(); + command_buffer.endRenderPass(); - draw_cmd_buffers[i].end(); + command_buffer.end(); } } @@ -179,39 +158,38 @@ void HPPTerrainTessellation::on_update_ui_overlay(vkb::HPPDrawer &drawer) { if (drawer.header("Settings")) { - if (drawer.checkbox("Tessellation", &tessellation)) + if (drawer.checkbox("Tessellation", &terrain.tessellation_enabled)) { update_uniform_buffers(); } - if (drawer.input_float("Factor", &ubo_tess.tessellation_factor, 0.05f, 2)) + if (drawer.input_float("Factor", &terrain.tessellation.tessellation_factor, 0.05f, 2)) { update_uniform_buffers(); } - if (get_device()->get_gpu().get_features().fillModeNonSolid) + if (wireframe.supported) { - if (drawer.checkbox("Wireframe", &wireframe)) + if (drawer.checkbox("Wireframe", &wireframe.enabled)) { build_command_buffers(); } } } - if (get_device()->get_gpu().get_features().pipelineStatisticsQuery) + if (statistics.query_supported) { if (drawer.header("Pipeline statistics")) { - drawer.text("VS invocations: %d", pipeline_stats[0]); - drawer.text("TE invocations: %d", pipeline_stats[1]); + drawer.text("VS invocations: %d", statistics.results[0]); + drawer.text("TE invocations: %d", statistics.results[1]); } } } void HPPTerrainTessellation::render(float delta_time) { - if (!prepared) + if (prepared) { - return; + draw(); } - draw(); } void HPPTerrainTessellation::view_changed() @@ -219,6 +197,123 @@ void HPPTerrainTessellation::view_changed() update_uniform_buffers(); } +vk::DescriptorPool HPPTerrainTessellation::create_descriptor_pool() +{ + std::array pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 3}, {vk::DescriptorType::eCombinedImageSampler, 3}}}; + return get_device()->get_handle().createDescriptorPool({{}, 2, pool_sizes}); +} + +vk::DescriptorSetLayout HPPTerrainTessellation::create_sky_sphere_descriptor_set_layout() +{ + std::array layout_bindings = { + {{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex}, + {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}}; + + vk::DescriptorSetLayoutCreateInfo skysphere_descriptor_layout({}, layout_bindings); + return get_device()->get_handle().createDescriptorSetLayout(skysphere_descriptor_layout); +} + +vk::Pipeline HPPTerrainTessellation::create_sky_sphere_pipeline() +{ + std::vector shader_stages = { + load_shader("terrain_tessellation/skysphere.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("terrain_tessellation/skysphere.frag", vk::ShaderStageFlagBits::eFragment)}; + + // Vertex bindings an attributes + // Binding description + std::array vertex_input_bindings = {{{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex}}}; + + // Attribute descriptions + std::array vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Position + {1, 0, vk::Format::eR32G32B32Sfloat, sizeof(float) * 3}, // Normal + {2, 0, vk::Format::eR32G32Sfloat, sizeof(float) * 6}}}; // UV + + vk::PipelineVertexInputStateCreateInfo vertex_input_state({}, vertex_input_bindings, vertex_input_attributes); + + vk::PipelineColorBlendAttachmentState blend_attachment_state; + blend_attachment_state.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + + // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept + vk::PipelineDepthStencilStateCreateInfo depth_stencil_state; + depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater; + depth_stencil_state.depthTestEnable = true; + depth_stencil_state.depthWriteEnable = false; + depth_stencil_state.back.compareOp = vk::CompareOp::eGreater; + depth_stencil_state.front = depth_stencil_state.back; + + // For the sky_sphere use triangle list topology + return vkb::common::create_graphics_pipeline(get_device()->get_handle(), + pipeline_cache, + shader_stages, + vertex_input_state, + vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, + vk::CullModeFlagBits::eBack, + vk::FrontFace::eCounterClockwise, + {blend_attachment_state}, + depth_stencil_state, + sky_sphere.pipeline_layout, + render_pass); +} + +vk::DescriptorSetLayout HPPTerrainTessellation::create_terrain_descriptor_set_layout() +{ + // Terrain + std::array layout_bindings = { + {{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eTessellationControl | vk::ShaderStageFlagBits::eTessellationEvaluation}, + {1, + vk::DescriptorType::eCombinedImageSampler, + 1, + vk::ShaderStageFlagBits::eTessellationControl | vk::ShaderStageFlagBits::eTessellationEvaluation | vk::ShaderStageFlagBits::eFragment}, + {2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}}; + + vk::DescriptorSetLayoutCreateInfo terrain_descriptor_layout({}, layout_bindings); + return get_device()->get_handle().createDescriptorSetLayout(terrain_descriptor_layout); +} + +vk::Pipeline HPPTerrainTessellation::create_terrain_pipeline(vk::PolygonMode polygon_mode) +{ + // Vertex bindings an attributes + // Binding description + std::array vertex_input_bindings = { + {{0, sizeof(HPPTerrainTessellation::Vertex), vk::VertexInputRate::eVertex}}}; + + // Attribute descriptions + std::array vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Position + {1, 0, vk::Format::eR32G32B32Sfloat, sizeof(float) * 3}, // Normal + {2, 0, vk::Format::eR32G32Sfloat, sizeof(float) * 6}}}; // UV + + vk::PipelineVertexInputStateCreateInfo vertex_input_state({}, vertex_input_bindings, vertex_input_attributes); + + vk::PipelineColorBlendAttachmentState blend_attachment_state; + blend_attachment_state.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + + // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept + vk::PipelineDepthStencilStateCreateInfo depth_stencil_state; + depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater; + depth_stencil_state.depthTestEnable = true; + depth_stencil_state.depthWriteEnable = true; + depth_stencil_state.back.compareOp = vk::CompareOp::eGreater; + depth_stencil_state.front = depth_stencil_state.back; + + return vkb::common::create_graphics_pipeline(get_device()->get_handle(), + pipeline_cache, + terrain.shader_stages, + vertex_input_state, + vk::PrimitiveTopology::ePatchList, + 4, // we render the terrain as a grid of quad patches + polygon_mode, + vk::CullModeFlagBits::eBack, + vk::FrontFace::eCounterClockwise, + {blend_attachment_state}, + depth_stencil_state, + terrain.pipeline_layout, + render_pass); +} + void HPPTerrainTessellation::draw() { HPPApiVulkanSample::prepare_frame(); @@ -229,11 +324,11 @@ void HPPTerrainTessellation::draw() // Submit to queue queue.submit(submit_info); - if (get_device()->get_gpu().get_features().pipelineStatisticsQuery) + if (statistics.query_supported) { // Read query results for displaying in next frame - pipeline_stats = - get_device()->get_handle().getQueryPoolResult>(query_pool, 0, 1, sizeof(pipeline_stats), vk::QueryResultFlagBits::e64).value; + statistics.results = + get_device()->get_handle().getQueryPoolResult>(statistics.query_pool, 0, 1, sizeof(statistics.results), vk::QueryResultFlagBits::e64).value; } HPPApiVulkanSample::submit_frame(); @@ -248,23 +343,20 @@ void HPPTerrainTessellation::generate_terrain() std::vector vertices; vertices.resize(vertex_count); - const float wx = 2.0f; - const float wy = 2.0f; - for (auto x = 0; x < patch_size; x++) { for (auto y = 0; y < patch_size; y++) { uint32_t index = (x + y * patch_size); - vertices[index].pos[0] = x * wx + wx / 2.0f - static_cast(patch_size) * wx / 2.0f; + vertices[index].pos[0] = 2.0f * x + 1.0f - patch_size; vertices[index].pos[1] = 0.0f; - vertices[index].pos[2] = y * wy + wy / 2.0f - static_cast(patch_size) * wy / 2.0f; + vertices[index].pos[2] = 2.0f * y * 1.0f - patch_size; vertices[index].uv = glm::vec2(static_cast(x) / patch_size, static_cast(y) / patch_size) * uv_scale; } } // Calculate normals from height map using a sobel filter - vkb::HeightMap heightmap("textures/terrain_heightmap_r16.ktx", patch_size); + vkb::HeightMap height_map("textures/terrain_heightmap_r16.ktx", patch_size); for (auto x = 0; x < patch_size; x++) { for (auto y = 0; y < patch_size; y++) @@ -275,7 +367,7 @@ void HPPTerrainTessellation::generate_terrain() { for (auto hy = -1; hy <= 1; hy++) { - heights[hx + 1][hy + 1] = heightmap.get_height(x + hx, y + hy); + heights[hx + 1][hy + 1] = height_map.get_height(x + hx, y + hy); } } @@ -334,318 +426,169 @@ void HPPTerrainTessellation::generate_terrain() terrain.indices = std::make_unique( *get_device(), index_buffer_size, vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, VMA_MEMORY_USAGE_GPU_ONLY); - // Copy from staging buffers - vk::CommandBuffer copy_command = device->create_command_buffer(vk::CommandBufferLevel::ePrimary, true); + vk::Device vkDevice = get_device()->get_handle(); + // Copy from staging buffers + vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(vkDevice, device->get_command_pool().get_handle()); + copy_command.begin(vk::CommandBufferBeginInfo()); copy_command.copyBuffer(vertex_staging.buffer, terrain.vertices->get_handle(), {{0, 0, vertex_buffer_size}}); - copy_command.copyBuffer(index_staging.buffer, terrain.indices->get_handle(), {{0, 0, index_buffer_size}}); + get_device()->flush_command_buffer(copy_command, queue, true); - device->flush_command_buffer(copy_command, queue, true); - - get_device()->get_handle().destroyBuffer(vertex_staging.buffer); - get_device()->get_handle().freeMemory(vertex_staging.memory); - get_device()->get_handle().destroyBuffer(index_staging.buffer); - get_device()->get_handle().freeMemory(index_staging.memory); + vkDevice.destroyBuffer(vertex_staging.buffer); + vkDevice.freeMemory(vertex_staging.memory); + vkDevice.destroyBuffer(index_staging.buffer); + vkDevice.freeMemory(index_staging.memory); } void HPPTerrainTessellation::load_assets() { - skysphere = load_model("scenes/geosphere.gltf"); - - textures.skysphere = load_texture("textures/skysphere_rgba.ktx", vkb::sg::Image::Color); - // Terrain textures are stored in a texture array with layers corresponding to terrain height - textures.terrain_array = load_texture_array("textures/terrain_texturearray_rgba.ktx", vkb::sg::Image::Color); - - // Height data is stored in a one-channel texture - textures.heightmap = load_texture("textures/terrain_heightmap_r16.ktx", vkb::sg::Image::Other); - - // Setup a mirroring sampler for the height map - vk::SamplerCreateInfo sampler_create_info; - sampler_create_info.magFilter = vk::Filter::eLinear; - sampler_create_info.minFilter = vk::Filter::eLinear; - sampler_create_info.mipmapMode = vk::SamplerMipmapMode::eLinear; - sampler_create_info.addressModeU = vk::SamplerAddressMode::eMirroredRepeat; - sampler_create_info.addressModeV = sampler_create_info.addressModeU; - sampler_create_info.addressModeW = sampler_create_info.addressModeU; - sampler_create_info.maxAnisotropy = 1.0f; - sampler_create_info.compareOp = vk::CompareOp::eNever; - sampler_create_info.minLod = 0.0f; - sampler_create_info.maxLod = static_cast(textures.heightmap.image->get_mipmaps().size()); - sampler_create_info.borderColor = vk::BorderColor::eFloatOpaqueWhite; - get_device()->get_handle().destroySampler(textures.heightmap.sampler); - textures.heightmap.sampler = get_device()->get_handle().createSampler(sampler_create_info); - - // Setup a repeating sampler for the terrain texture layers - sampler_create_info.addressModeU = vk::SamplerAddressMode::eRepeat; - sampler_create_info.addressModeV = sampler_create_info.addressModeU; - sampler_create_info.addressModeW = sampler_create_info.addressModeU; - sampler_create_info.maxLod = static_cast(textures.terrain_array.image->get_mipmaps().size()); - if (get_device()->get_gpu().get_features().samplerAnisotropy) - { - sampler_create_info.maxAnisotropy = 4.0f; - sampler_create_info.anisotropyEnable = true; - } - get_device()->get_handle().destroySampler(textures.terrain_array.sampler); - textures.terrain_array.sampler = get_device()->get_handle().createSampler(sampler_create_info); -} + sky_sphere.geometry = load_model("scenes/geosphere.gltf"); + sky_sphere.texture = load_texture("textures/skysphere_rgba.ktx", vkb::sg::Image::Color); -void HPPTerrainTessellation::prepare_pipelines() -{ - std::array terrain_shader_stages = {{load_shader("terrain_tessellation/terrain.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("terrain_tessellation/terrain.frag", vk::ShaderStageFlagBits::eFragment), - load_shader("terrain_tessellation/terrain.tesc", vk::ShaderStageFlagBits::eTessellationControl), - load_shader("terrain_tessellation/terrain.tese", vk::ShaderStageFlagBits::eTessellationEvaluation)}}; - - // Vertex bindings an attributes - // Binding description - std::array vertex_input_bindings = {{{0, sizeof(HPPTerrainTessellation::Vertex), vk::VertexInputRate::eVertex}}}; - - // Attribute descriptions - std::array vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Position - {1, 0, vk::Format::eR32G32B32Sfloat, sizeof(float) * 3}, // Normal - {2, 0, vk::Format::eR32G32Sfloat, sizeof(float) * 6}}}; // UV - - vk::PipelineVertexInputStateCreateInfo vertex_input_state({}, vertex_input_bindings, vertex_input_attributes); - - vk::PipelineInputAssemblyStateCreateInfo input_assembly_state({}, vk::PrimitiveTopology::ePatchList, false); - - // We render the terrain as a grid of quad patches - vk::PipelineTessellationStateCreateInfo tessellation_state({}, 4); + // Terrain textures are stored in a texture array with layers corresponding to terrain height; create a repeating sampler + terrain.terrain_array = load_texture_array("textures/terrain_texturearray_rgba.ktx", vkb::sg::Image::Color, vk::SamplerAddressMode::eRepeat); - vk::PipelineViewportStateCreateInfo viewport_state({}, 1, nullptr, 1, nullptr); - - vk::PipelineRasterizationStateCreateInfo rasterization_state; - rasterization_state.polygonMode = vk::PolygonMode::eFill; - rasterization_state.cullMode = vk::CullModeFlagBits::eBack; - rasterization_state.frontFace = vk::FrontFace::eCounterClockwise; - rasterization_state.lineWidth = 1.0f; - - vk::PipelineMultisampleStateCreateInfo multisample_state({}, vk::SampleCountFlagBits::e1); + // Height data is stored in a one-channel texture; create a mirroring sampler + terrain.height_map = load_texture("textures/terrain_heightmap_r16.ktx", vkb::sg::Image::Other, vk::SamplerAddressMode::eMirroredRepeat); +} - // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept - vk::PipelineDepthStencilStateCreateInfo depth_stencil_state; - depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater; - depth_stencil_state.depthTestEnable = true; - depth_stencil_state.depthWriteEnable = true; - depth_stencil_state.back.compareOp = vk::CompareOp::eGreater; - depth_stencil_state.front = depth_stencil_state.back; +void HPPTerrainTessellation::prepare_camera() +{ + // Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped + camera.type = vkb::CameraType::FirstPerson; + camera.set_perspective(60.0f, static_cast(extent.width) / static_cast(extent.height), 512.0f, 0.1f); + camera.set_rotation(glm::vec3(-12.0f, 159.0f, 0.0f)); + camera.set_translation(glm::vec3(18.0f, 22.5f, 57.5f)); + camera.translation_speed = 7.5f; +} - vk::PipelineColorBlendAttachmentState blend_attachment_state; - blend_attachment_state.colorWriteMask = - vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; +void HPPTerrainTessellation::prepare_sky_sphere() +{ + sky_sphere.descriptor_set_layout = create_sky_sphere_descriptor_set_layout(); + sky_sphere.pipeline_layout = get_device()->get_handle().createPipelineLayout({{}, sky_sphere.descriptor_set_layout}); + sky_sphere.pipeline = create_sky_sphere_pipeline(); + sky_sphere.descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, {sky_sphere.descriptor_set_layout}); + update_sky_sphere_descriptor_set(); +} - vk::PipelineColorBlendStateCreateInfo color_blend_state({}, false, {}, blend_attachment_state); - - std::array dynamic_state_enables = {vk::DynamicState::eViewport, vk::DynamicState::eScissor, vk::DynamicState::eLineWidth}; - - vk::PipelineDynamicStateCreateInfo dynamic_state({}, dynamic_state_enables); - - vk::GraphicsPipelineCreateInfo pipeline_create_info({}, - terrain_shader_stages, - &vertex_input_state, - &input_assembly_state, - &tessellation_state, - &viewport_state, - &rasterization_state, - &multisample_state, - &depth_stencil_state, - &color_blend_state, - &dynamic_state, - pipeline_layouts.terrain, - render_pass, - {}, - {}, - -1); - - vk::Result result; - std::tie(result, pipelines.terrain) = get_device()->get_handle().createGraphicsPipeline(pipeline_cache, pipeline_create_info); - assert(result == vk::Result::eSuccess); - - // Terrain wireframe pipeline - if (get_device()->get_gpu().get_features().fillModeNonSolid) +void HPPTerrainTessellation::prepare_statistics() +{ + if (statistics.query_supported) { - rasterization_state.polygonMode = vk::PolygonMode::eLine; - std::tie(result, pipelines.wireframe) = get_device()->get_handle().createGraphicsPipeline(pipeline_cache, pipeline_create_info); - assert(result == vk::Result::eSuccess); - }; + // Create query pool + vk::QueryPoolCreateInfo query_pool_info({}, + vk::QueryType::ePipelineStatistics, + 2, + vk::QueryPipelineStatisticFlagBits::eVertexShaderInvocations | + vk::QueryPipelineStatisticFlagBits::eTessellationEvaluationShaderInvocations); + statistics.query_pool = get_device()->get_handle().createQueryPool(query_pool_info); + } +} - // Skysphere pipeline - - // Stride from glTF model vertex layout - vertex_input_bindings[0].stride = sizeof(HPPVertex); - - rasterization_state.polygonMode = vk::PolygonMode::eFill; - // Revert to triangle list topology - input_assembly_state.topology = vk::PrimitiveTopology::eTriangleList; - // Reset tessellation state - pipeline_create_info.pTessellationState = nullptr; - // Don't write to depth buffer - depth_stencil_state.depthWriteEnable = false; - pipeline_create_info.layout = pipeline_layouts.skysphere; - std::array skysphere_shader_stages = {{load_shader("terrain_tessellation/skysphere.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("terrain_tessellation/skysphere.frag", vk::ShaderStageFlagBits::eFragment)}}; - pipeline_create_info.setStages(skysphere_shader_stages); - - std::tie(result, pipelines.skysphere) = get_device()->get_handle().createGraphicsPipeline(pipeline_cache, pipeline_create_info); - assert(result == vk::Result::eSuccess); +void HPPTerrainTessellation::prepare_terrain() +{ + terrain.shader_stages = {load_shader("terrain_tessellation/terrain.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("terrain_tessellation/terrain.frag", vk::ShaderStageFlagBits::eFragment), + load_shader("terrain_tessellation/terrain.tesc", vk::ShaderStageFlagBits::eTessellationControl), + load_shader("terrain_tessellation/terrain.tese", vk::ShaderStageFlagBits::eTessellationEvaluation)}; + + terrain.descriptor_set_layout = create_terrain_descriptor_set_layout(); + terrain.pipeline_layout = get_device()->get_handle().createPipelineLayout({{}, terrain.descriptor_set_layout}); + terrain.pipeline = create_terrain_pipeline(vk::PolygonMode::eFill); + terrain.descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, {terrain.descriptor_set_layout}); + update_terrain_descriptor_set(); } // Prepare and initialize uniform buffer containing shader uniforms void HPPTerrainTessellation::prepare_uniform_buffers() { // Shared tessellation shader stages uniform buffer - uniform_buffers.terrain_tessellation = - std::make_unique(*get_device(), sizeof(ubo_tess), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); + terrain.tessellation_buffer = + std::make_unique(*get_device(), sizeof(terrain.tessellation), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); // Skysphere vertex shader uniform buffer - uniform_buffers.skysphere_vertex = - std::make_unique(*get_device(), sizeof(ubo_vs), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); + sky_sphere.transform_buffer = + std::make_unique(*get_device(), sizeof(sky_sphere.transform), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); update_uniform_buffers(); } -void HPPTerrainTessellation::setup_descriptor_pool() +void HPPTerrainTessellation::prepare_wireframe() { - std::array pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 3}, {vk::DescriptorType::eCombinedImageSampler, 3}}}; - - vk::DescriptorPoolCreateInfo descriptor_pool_create_info({}, 2, pool_sizes); - - descriptor_pool = get_device()->get_handle().createDescriptorPool(descriptor_pool_create_info); + if (wireframe.supported) + { + // wireframe mode uses nearly the same settings as the terrain mode... just vk::PolygonMode::eLine, instead of vk::PolygonMode::eFill + wireframe.pipeline = create_terrain_pipeline(vk::PolygonMode::eLine); + }; } -void HPPTerrainTessellation::setup_descriptor_set_layouts() +void HPPTerrainTessellation::update_uniform_buffers() { - // Terrain - std::array terrain_layout_bindings = { - {{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eTessellationControl | vk::ShaderStageFlagBits::eTessellationEvaluation}, - {1, - vk::DescriptorType::eCombinedImageSampler, - 1, - vk::ShaderStageFlagBits::eTessellationControl | vk::ShaderStageFlagBits::eTessellationEvaluation | vk::ShaderStageFlagBits::eFragment}, - {2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}}; + // Tessellation + terrain.tessellation.projection = camera.matrices.perspective; + terrain.tessellation.modelview = camera.matrices.view * glm::mat4(1.0f); + terrain.tessellation.light_pos.y = -0.5f - terrain.tessellation.displacement_factor; // todo: Not used yet + terrain.tessellation.viewport_dim = glm::vec2(static_cast(extent.width), static_cast(extent.height)); - vk::DescriptorSetLayoutCreateInfo terrain_descriptor_layout({}, terrain_layout_bindings); - descriptor_set_layouts.terrain = get_device()->get_handle().createDescriptorSetLayout(terrain_descriptor_layout); -#if defined(ANDROID) - vk::PipelineLayoutCreateInfo terrain_pipeline_layout_create_info({}, 1, &descriptor_set_layouts.terrain); -#else - vk::PipelineLayoutCreateInfo terrain_pipeline_layout_create_info({}, descriptor_set_layouts.terrain); -#endif - pipeline_layouts.terrain = get_device()->get_handle().createPipelineLayout(terrain_pipeline_layout_create_info); - - // Skysphere - std::array skysphere_layout_bindings = { - {{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex}, - {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}}; + frustum.update(terrain.tessellation.projection * terrain.tessellation.modelview); + memcpy(terrain.tessellation.frustum_planes, frustum.get_planes().data(), sizeof(glm::vec4) * 6); - vk::DescriptorSetLayoutCreateInfo skysphere_descriptor_layout({}, skysphere_layout_bindings); - descriptor_set_layouts.skysphere = get_device()->get_handle().createDescriptorSetLayout(skysphere_descriptor_layout); -#if defined(ANDROID) - vk::PipelineLayoutCreateInfo skysphere_pipeline_layout_create_info({}, 1, &descriptor_set_layouts.skysphere); -#else - vk::PipelineLayoutCreateInfo skysphere_pipeline_layout_create_info({}, descriptor_set_layouts.skysphere); -#endif - pipeline_layouts.skysphere = get_device()->get_handle().createPipelineLayout(skysphere_pipeline_layout_create_info); + float saved_factor = terrain.tessellation.tessellation_factor; + if (!terrain.tessellation_enabled) + { + // Setting this to zero sets all tessellation factors to 1.0 in the shader + terrain.tessellation.tessellation_factor = 0.0f; + } + + terrain.tessellation_buffer->convert_and_update(terrain.tessellation); + + if (!terrain.tessellation_enabled) + { + terrain.tessellation.tessellation_factor = saved_factor; + } + + // Skysphere vertex shader + sky_sphere.transform = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view)); + sky_sphere.transform_buffer->convert_and_update(sky_sphere.transform); } -void HPPTerrainTessellation::setup_descriptor_sets() +void HPPTerrainTessellation::update_sky_sphere_descriptor_set() { - // Terrain -#if defined(ANDROID) - vk::DescriptorSetAllocateInfo alloc_info(descriptor_pool, 1, &descriptor_set_layouts.terrain); -#else - vk::DescriptorSetAllocateInfo alloc_info(descriptor_pool, descriptor_set_layouts.terrain); -#endif - descriptor_sets.terrain = get_device()->get_handle().allocateDescriptorSets(alloc_info).front(); - - vk::DescriptorBufferInfo terrain_buffer_descriptor(uniform_buffers.terrain_tessellation->get_handle(), 0, VK_WHOLE_SIZE); - vk::DescriptorImageInfo heightmap_image_descriptor( - textures.heightmap.sampler, - textures.heightmap.image->get_vk_image_view().get_handle(), - descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler, textures.heightmap.image->get_vk_image_view().get_format())); - vk::DescriptorImageInfo terrainmap_image_descriptor( - textures.terrain_array.sampler, - textures.terrain_array.image->get_vk_image_view().get_handle(), - descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler, textures.terrain_array.image->get_vk_image_view().get_format())); - std::array terrain_write_descriptor_sets = { - {{descriptor_sets.terrain, 0, {}, vk::DescriptorType::eUniformBuffer, {}, terrain_buffer_descriptor}, - {descriptor_sets.terrain, 1, {}, vk::DescriptorType::eCombinedImageSampler, heightmap_image_descriptor}, - {descriptor_sets.terrain, 2, {}, vk::DescriptorType::eCombinedImageSampler, terrainmap_image_descriptor}}}; - get_device()->get_handle().updateDescriptorSets(terrain_write_descriptor_sets, {}); + vk::DescriptorBufferInfo skysphere_buffer_descriptor(sky_sphere.transform_buffer->get_handle(), 0, VK_WHOLE_SIZE); - // Skysphere - alloc_info.setSetLayouts(descriptor_set_layouts.skysphere); - descriptor_sets.skysphere = get_device()->get_handle().allocateDescriptorSets(alloc_info).front(); + vk::DescriptorImageInfo skysphere_image_descriptor( + sky_sphere.texture.sampler, + sky_sphere.texture.image->get_vk_image_view().get_handle(), + descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler, sky_sphere.texture.image->get_vk_image_view().get_format())); - vk::DescriptorBufferInfo skysphere_buffer_descriptor(uniform_buffers.skysphere_vertex->get_handle(), 0, VK_WHOLE_SIZE); - vk::DescriptorImageInfo skysphere_image_descriptor( - textures.skysphere.sampler, - textures.skysphere.image->get_vk_image_view().get_handle(), - descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler, textures.skysphere.image->get_vk_image_view().get_format())); std::array skysphere_write_descriptor_sets = { - {{descriptor_sets.skysphere, 0, {}, vk::DescriptorType::eUniformBuffer, {}, skysphere_buffer_descriptor}, - {descriptor_sets.skysphere, 1, {}, vk::DescriptorType::eCombinedImageSampler, skysphere_image_descriptor}}}; - get_device()->get_handle().updateDescriptorSets(skysphere_write_descriptor_sets, {}); -} + {{sky_sphere.descriptor_set, 0, {}, vk::DescriptorType::eUniformBuffer, {}, skysphere_buffer_descriptor}, + {sky_sphere.descriptor_set, 1, {}, vk::DescriptorType::eCombinedImageSampler, skysphere_image_descriptor}}}; -// Setup pool and buffer for storing pipeline statistics results -void HPPTerrainTessellation::setup_query_result_buffer() -{ - const uint32_t buffer_size = 2 * sizeof(uint64_t); - - vk::BufferCreateInfo buffer_create_info({}, buffer_size, vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eTransferDst); - - // Results are saved in a host visible buffer for easy access by the application - query_result.buffer = get_device()->get_handle().createBuffer(buffer_create_info); - vk::MemoryRequirements memory_requirements = get_device()->get_handle().getBufferMemoryRequirements(query_result.buffer); - vk::MemoryAllocateInfo memory_allocation( - memory_requirements.size, - get_device()->get_gpu().get_memory_type(memory_requirements.memoryTypeBits, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)); - query_result.memory = get_device()->get_handle().allocateMemory(memory_allocation); - get_device()->get_handle().bindBufferMemory(query_result.buffer, query_result.memory, 0); - - // Create query pool - if (get_device()->get_gpu().get_features().pipelineStatisticsQuery) - { - vk::QueryPoolCreateInfo query_pool_info({}, - vk::QueryType::ePipelineStatistics, - 2, - vk::QueryPipelineStatisticFlagBits::eVertexShaderInvocations | - vk::QueryPipelineStatisticFlagBits::eTessellationEvaluationShaderInvocations); - query_pool = get_device()->get_handle().createQueryPool(query_pool_info); - } + get_device()->get_handle().updateDescriptorSets(skysphere_write_descriptor_sets, {}); } -void HPPTerrainTessellation::update_uniform_buffers() +void HPPTerrainTessellation::update_terrain_descriptor_set() { - // Tessellation - ubo_tess.projection = camera.matrices.perspective; - ubo_tess.modelview = camera.matrices.view * glm::mat4(1.0f); - ubo_tess.light_pos.y = -0.5f - ubo_tess.displacement_factor; // todo: Not used yet - ubo_tess.viewport_dim = glm::vec2(static_cast(extent.width), static_cast(extent.height)); - - frustum.update(ubo_tess.projection * ubo_tess.modelview); - memcpy(ubo_tess.frustum_planes, frustum.get_planes().data(), sizeof(glm::vec4) * 6); + vk::DescriptorBufferInfo terrain_buffer_descriptor(terrain.tessellation_buffer->get_handle(), 0, VK_WHOLE_SIZE); - float saved_factor = ubo_tess.tessellation_factor; - if (!tessellation) - { - // Setting this to zero sets all tessellation factors to 1.0 in the shader - ubo_tess.tessellation_factor = 0.0f; - } + vk::DescriptorImageInfo heightmap_image_descriptor( + terrain.height_map.sampler, + terrain.height_map.image->get_vk_image_view().get_handle(), + descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler, terrain.height_map.image->get_vk_image_view().get_format())); - uniform_buffers.terrain_tessellation->convert_and_update(ubo_tess); + vk::DescriptorImageInfo terrainmap_image_descriptor( + terrain.terrain_array.sampler, + terrain.terrain_array.image->get_vk_image_view().get_handle(), + descriptor_type_to_image_layout(vk::DescriptorType::eCombinedImageSampler, terrain.terrain_array.image->get_vk_image_view().get_format())); - if (!tessellation) - { - ubo_tess.tessellation_factor = saved_factor; - } + std::array terrain_write_descriptor_sets = { + {{terrain.descriptor_set, 0, {}, vk::DescriptorType::eUniformBuffer, {}, terrain_buffer_descriptor}, + {terrain.descriptor_set, 1, {}, vk::DescriptorType::eCombinedImageSampler, heightmap_image_descriptor}, + {terrain.descriptor_set, 2, {}, vk::DescriptorType::eCombinedImageSampler, terrainmap_image_descriptor}}}; - // Skysphere vertex shader - ubo_vs.mvp = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view)); - uniform_buffers.skysphere_vertex->convert_and_update(ubo_vs.mvp); + get_device()->get_handle().updateDescriptorSets(terrain_write_descriptor_sets, {}); } std::unique_ptr create_hpp_terrain_tessellation() diff --git a/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.h b/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.h index 3eede40d5..88dd1d2ed 100644 --- a/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.h +++ b/samples/api/hpp_terrain_tessellation/hpp_terrain_tessellation.h @@ -32,60 +32,49 @@ class HPPTerrainTessellation : public HPPApiVulkanSample ~HPPTerrainTessellation(); private: - struct DescriptorSetLayouts + struct SkySphere { - vk::DescriptorSetLayout terrain; - vk::DescriptorSetLayout skysphere; + vk::DescriptorSet descriptor_set; + vk::DescriptorSetLayout descriptor_set_layout; + vk::PipelineLayout pipeline_layout; + vk::Pipeline pipeline; + + std::unique_ptr geometry; + + HPPTexture texture; + + glm::mat4 transform; + std::unique_ptr transform_buffer; + + void destroy(vk::Device device) + { + device.destroyPipeline(pipeline); + device.destroyPipelineLayout(pipeline_layout); + device.destroyDescriptorSetLayout(descriptor_set_layout); + // the descriptor_set is implicitly freed by destroying its DescriptorPool + device.destroySampler(texture.sampler); + } }; - struct DescriptorSets + struct Statistics { - vk::DescriptorSet terrain; - vk::DescriptorSet skysphere; - }; - - struct PipelineLayouts - { - vk::PipelineLayout terrain; - vk::PipelineLayout skysphere; - }; - - struct Pipelines - { - vk::Pipeline terrain; - vk::Pipeline wireframe = nullptr; - vk::Pipeline skysphere; - }; - - // Pipeline statistics - struct QueryResults - { - vk::Buffer buffer; - vk::DeviceMemory memory; - }; + bool query_supported = false; - // Skysphere vertex shader stage - struct SkySphereUBO - { - glm::mat4 mvp; - }; + vk::QueryPool query_pool; - struct Textures - { - HPPTexture heightmap; - HPPTexture skysphere; - HPPTexture terrain_array; - }; + std::array results = {{0}}; - struct Terrain - { - std::unique_ptr vertices; - std::unique_ptr indices; - uint32_t index_count; + void destroy(vk::Device device) + { + if (query_supported) + { + device.destroyQueryPool(query_pool); + } + } }; // Shared values for tessellation control and evaluation stages - struct TessellationUBO + struct Tessellation { glm::mat4 projection; glm::mat4 modelview; @@ -98,10 +87,37 @@ class HPPTerrainTessellation : public HPPApiVulkanSample float tessellated_edge_size = 20.0f; }; - struct UniformBuffers + struct Terrain { - std::unique_ptr terrain_tessellation; - std::unique_ptr skysphere_vertex; + vk::DescriptorSet descriptor_set; + vk::DescriptorSetLayout descriptor_set_layout; + vk::PipelineLayout pipeline_layout; + vk::Pipeline pipeline; + + std::unique_ptr vertices; + std::unique_ptr indices; + uint32_t index_count; + + HPPTexture height_map; + HPPTexture terrain_array; + + bool sampler_anisotropy_supported = false; + + std::vector shader_stages; + + Tessellation tessellation; + std::unique_ptr tessellation_buffer; + bool tessellation_enabled = true; + + void destroy(vk::Device device) + { + device.destroyPipeline(pipeline); + device.destroyPipelineLayout(pipeline_layout); + device.destroyDescriptorSetLayout(descriptor_set_layout); + // the descriptor_set is implicitly freed by destroying its DescriptorPool + device.destroySampler(height_map.sampler); + device.destroySampler(terrain_array.sampler); + } }; struct Vertex @@ -111,6 +127,19 @@ class HPPTerrainTessellation : public HPPApiVulkanSample glm::vec2 uv; }; + struct Wireframe + { + bool supported = false; + bool enabled = false; + + vk::Pipeline pipeline; + + void destroy(vk::Device device) + { + device.destroyPipeline(pipeline); + } + }; + private: // from vkb::Application bool prepare(const vkb::ApplicationOptions &options) override; @@ -124,34 +153,30 @@ class HPPTerrainTessellation : public HPPApiVulkanSample void render(float delta_time) override; void view_changed() override; - void draw(); - void generate_terrain(); - void load_assets(); - void prepare_pipelines(); - void prepare_uniform_buffers(); - void setup_descriptor_pool(); - void setup_descriptor_set_layouts(); - void setup_descriptor_sets(); - void setup_query_result_buffer(); - void update_uniform_buffers(); + vk::DescriptorPool create_descriptor_pool(); + vk::DescriptorSetLayout create_sky_sphere_descriptor_set_layout(); + vk::Pipeline create_sky_sphere_pipeline(); + vk::DescriptorSetLayout create_terrain_descriptor_set_layout(); + vk::Pipeline create_terrain_pipeline(vk::PolygonMode polygon_mode); + void draw(); + void generate_terrain(); + void load_assets(); + void prepare_camera(); + void prepare_sky_sphere(); + void prepare_statistics(); + void prepare_terrain(); + void prepare_uniform_buffers(); + void prepare_wireframe(); + void update_uniform_buffers(); + void update_sky_sphere_descriptor_set(); + void update_terrain_descriptor_set(); private: - DescriptorSetLayouts descriptor_set_layouts; - DescriptorSets descriptor_sets; - vkb::Frustum frustum; // View frustum passed to tessellation control shader for culling - PipelineLayouts pipeline_layouts; - std::array pipeline_stats = {{0}}; - Pipelines pipelines; - vk::QueryPool query_pool = nullptr; - QueryResults query_result; - std::unique_ptr skysphere; - bool tessellation = true; - Textures textures; - Terrain terrain; - TessellationUBO ubo_tess; - SkySphereUBO ubo_vs; - UniformBuffers uniform_buffers; - bool wireframe = false; + vkb::Frustum frustum; // View frustum passed to tessellation control shader for culling + SkySphere sky_sphere; + Statistics statistics; + Terrain terrain; + Wireframe wireframe; }; std::unique_ptr create_hpp_terrain_tessellation(); diff --git a/samples/api/hpp_texture_loading/hpp_texture_loading.cpp b/samples/api/hpp_texture_loading/hpp_texture_loading.cpp index 5123d4e3f..af07a64cb 100644 --- a/samples/api/hpp_texture_loading/hpp_texture_loading.cpp +++ b/samples/api/hpp_texture_loading/hpp_texture_loading.cpp @@ -21,11 +21,14 @@ #include +#include + HPPTextureLoading::HPPTextureLoading() { + title = "HPP Texture loading"; + zoom = -2.5f; rotation = {0.0f, 15.0f, 0.0f}; - title = "HPP Texture loading"; } HPPTextureLoading::~HPPTextureLoading() @@ -36,18 +39,38 @@ HPPTextureLoading::~HPPTextureLoading() // Clean up used Vulkan resources // Note : Inherited destructor cleans up resources stored in base class - device.destroyPipeline(pipeline); - device.destroyPipelineLayout(pipeline_layout); device.destroyDescriptorSetLayout(descriptor_set_layout); + texture.destroy(device); } - destroy_texture(texture); - vertex_buffer.reset(); index_buffer.reset(); - uniform_buffer_vs.reset(); + vertex_shader_data_buffer.reset(); +} + +bool HPPTextureLoading::prepare(const vkb::ApplicationOptions &options) +{ + assert(!prepared); + + if (HPPApiVulkanSample::prepare(options)) + { + load_texture(); + generate_quad(); + prepare_uniform_buffers(); + descriptor_set_layout = create_descriptor_set_layout(); + pipeline_layout = get_device()->get_handle().createPipelineLayout({{}, descriptor_set_layout}); + pipeline = create_pipeline(); + descriptor_pool = create_descriptor_pool(); + descriptor_set = vkb::common::allocate_descriptor_set(get_device()->get_handle(), descriptor_pool, {descriptor_set_layout}); + update_descriptor_set(); + build_command_buffers(); + + prepared = true; + } + + return prepared; } // Enable physical device features required for this example @@ -60,6 +83,178 @@ void HPPTextureLoading::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) } } +void HPPTextureLoading::build_command_buffers() +{ + vk::CommandBufferBeginInfo command_buffer_begin_info; + + vk::ClearValue clear_values[2]; + clear_values[0].color = default_clear_color; + clear_values[1].depthStencil = vk::ClearDepthStencilValue(0.0f, 0); + + vk::RenderPassBeginInfo 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 = extent; + 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 command_buffer = draw_cmd_buffers[i]; + + // Set target frame buffer + render_pass_begin_info.framebuffer = framebuffers[i]; + + command_buffer.begin(command_buffer_begin_info); + + command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); + + vk::Viewport viewport(0.0f, 0.0f, static_cast(extent.width), static_cast(extent.height), 0.0f, 1.0f); + command_buffer.setViewport(0, viewport); + + vk::Rect2D scissor({0, 0}, extent); + command_buffer.setScissor(0, scissor); + + command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set, {}); + command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + + vk::DeviceSize offset = 0; + command_buffer.bindVertexBuffers(0, vertex_buffer->get_handle(), offset); + command_buffer.bindIndexBuffer(index_buffer->get_handle(), 0, vk::IndexType::eUint32); + + command_buffer.drawIndexed(index_count, 1, 0, 0, 0); + + draw_ui(command_buffer); + + command_buffer.endRenderPass(); + + command_buffer.end(); + } +} + +void HPPTextureLoading::on_update_ui_overlay(vkb::HPPDrawer &drawer) +{ + if (drawer.header("Settings")) + { + if (drawer.slider_float("LOD bias", &vertex_shader_data.lod_bias, 0.0f, static_cast(texture.mip_levels))) + { + update_uniform_buffers(); + } + } +} + +void HPPTextureLoading::render(float delta_time) +{ + if (prepared) + { + draw(); + } +} + +void HPPTextureLoading::view_changed() +{ + update_uniform_buffers(); +} + +vk::DescriptorPool HPPTextureLoading::create_descriptor_pool() +{ + // Example uses one ubo and one image sampler + std::array pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 1}, {vk::DescriptorType::eCombinedImageSampler, 1}}}; + + return get_device()->get_handle().createDescriptorPool({{}, 2, pool_sizes}); +} + +vk::DescriptorSetLayout HPPTextureLoading::create_descriptor_set_layout() +{ + std::array set_layout_bindings = { + {{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex}, + {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}}; + + return get_device()->get_handle().createDescriptorSetLayout({{}, set_layout_bindings}); +} + +vk::Pipeline HPPTextureLoading::create_pipeline() +{ + // Load shaders + std::vector shader_stages = {{load_shader("texture_loading/texture.vert", vk::ShaderStageFlagBits::eVertex), + load_shader("texture_loading/texture.frag", vk::ShaderStageFlagBits::eFragment)}}; + + // Vertex bindings and attributes + vk::VertexInputBindingDescription vertex_input_binding(0, sizeof(Vertex), vk::VertexInputRate::eVertex); + std::array vertex_input_attributes = { + {{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)}, // Location 0 : Position + {1, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, uv)}, // Location 1: Texture Coordinates + {2, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal)}}}; // Location 2 : Normal + vk::PipelineVertexInputStateCreateInfo vertex_input_state({}, vertex_input_binding, vertex_input_attributes); + + vk::PipelineColorBlendAttachmentState blend_attachment_state; + blend_attachment_state.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + + // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept + vk::PipelineDepthStencilStateCreateInfo depth_stencil_state; + depth_stencil_state.depthTestEnable = true; + depth_stencil_state.depthWriteEnable = true; + depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater; + depth_stencil_state.back.compareOp = vk::CompareOp::eGreater; + + return vkb::common::create_graphics_pipeline(get_device()->get_handle(), + pipeline_cache, + shader_stages, + vertex_input_state, + vk::PrimitiveTopology::eTriangleList, + 0, + vk::PolygonMode::eFill, + vk::CullModeFlagBits::eNone, + vk::FrontFace::eCounterClockwise, + {blend_attachment_state}, + depth_stencil_state, + pipeline_layout, + render_pass); +} + +void HPPTextureLoading::draw() +{ + HPPApiVulkanSample::prepare_frame(); + + // Command buffer to be submitted to the queue + submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]); + + // Submit to queue + queue.submit(submit_info); + + HPPApiVulkanSample::submit_frame(); +} + +void HPPTextureLoading::generate_quad() +{ + // Setup vertices for a single uv-mapped quad made from two triangles + std::vector vertices = {{{1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}}; + + // Setup indices + std::vector indices = {0, 1, 2, 2, 3, 0}; + index_count = static_cast(indices.size()); + + auto vertex_buffer_size = vkb::to_u32(vertices.size() * sizeof(Vertex)); + auto index_buffer_size = vkb::to_u32(indices.size() * sizeof(uint32_t)); + + // Create buffers + // For the sake of simplicity we won't stage the vertex data to the gpu memory + // Vertex buffer + vertex_buffer = std::make_unique( + *get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); + vertex_buffer->update(vertices.data(), vertex_buffer_size); + + // Index buffer + index_buffer = std::make_unique( + *get_device(), index_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); + index_buffer->update(indices.data(), index_buffer_size); +} + /* Upload texture image data to the GPU @@ -108,6 +303,8 @@ void HPPTextureLoading::load_texture() use_staging = !(format_properties.linearTilingFeatures & vk::FormatFeatureFlagBits::eSampledImage); } + vk::Device device = get_device()->get_handle(); + if (use_staging) { // Copy data to an optimal tiled image @@ -117,22 +314,23 @@ void HPPTextureLoading::load_texture() // This buffer will be the data source for copying texture data to the optimal tiled image on the device // This buffer is used as a transfer source for the buffer copy vk::BufferCreateInfo buffer_create_info({}, ktx_texture->dataSize, vk::BufferUsageFlagBits::eTransferSrc, vk::SharingMode::eExclusive, {}); - vk::Buffer staging_buffer = get_device()->get_handle().createBuffer(buffer_create_info); + vk::Buffer staging_buffer = device.createBuffer(buffer_create_info); // Get memory requirements for the staging buffer (alignment, memory type bits) - vk::MemoryRequirements memory_requirements = get_device()->get_handle().getBufferMemoryRequirements(staging_buffer); - vk::MemoryAllocateInfo memory_allocate_info( - memory_requirements.size, - // Get memory type index for a host visible buffer - get_device()->get_gpu().get_memory_type(memory_requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)); - vk::DeviceMemory staging_memory = get_device()->get_handle().allocateMemory(memory_allocate_info); - get_device()->get_handle().bindBufferMemory(staging_buffer, staging_memory, 0); + vk::MemoryRequirements memory_requirements = device.getBufferMemoryRequirements(staging_buffer); - // Copy texture data into host local staging buffer + // Get memory type index for a host visible buffer + uint32_t memory_type = get_device()->get_gpu().get_memory_type(memory_requirements.memoryTypeBits, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); + + vk::MemoryAllocateInfo memory_allocate_info(memory_requirements.size, memory_type); + vk::DeviceMemory staging_memory = device.allocateMemory(memory_allocate_info); + device.bindBufferMemory(staging_buffer, staging_memory, 0); - uint8_t *data = reinterpret_cast(get_device()->get_handle().mapMemory(staging_memory, 0, memory_requirements.size)); + // Copy texture data into host local staging buffer + uint8_t *data = reinterpret_cast(device.mapMemory(staging_memory, 0, memory_requirements.size)); memcpy(data, ktx_texture->pData, ktx_texture->dataSize); - get_device()->get_handle().unmapMemory(staging_memory); + device.unmapMemory(staging_memory); // Setup buffer copy regions for each mip level std::vector buffer_copy_regions(texture.mip_levels); @@ -153,39 +351,35 @@ void HPPTextureLoading::load_texture() // Create optimal tiled target image on the device vk::ImageCreateInfo image_create_info; - image_create_info.imageType = vk::ImageType::e2D; - image_create_info.format = format; - image_create_info.mipLevels = texture.mip_levels; - image_create_info.arrayLayers = 1; - image_create_info.samples = vk::SampleCountFlagBits::e1; - image_create_info.tiling = vk::ImageTiling::eOptimal; - image_create_info.sharingMode = vk::SharingMode::eExclusive; - // Set initial layout of the image to undefined - image_create_info.initialLayout = vk::ImageLayout::eUndefined; + image_create_info.imageType = vk::ImageType::e2D; + image_create_info.format = format; + image_create_info.mipLevels = texture.mip_levels; + image_create_info.arrayLayers = 1; + image_create_info.samples = vk::SampleCountFlagBits::e1; + image_create_info.tiling = vk::ImageTiling::eOptimal; + image_create_info.sharingMode = vk::SharingMode::eExclusive; + image_create_info.initialLayout = vk::ImageLayout::eUndefined; // Set initial layout of the image to undefined image_create_info.extent = vk::Extent3D(texture.extent, 1); image_create_info.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled; - texture.image = get_device()->get_handle().createImage(image_create_info); + texture.image = device.createImage(image_create_info); - memory_requirements = get_device()->get_handle().getImageMemoryRequirements(texture.image); - memory_allocate_info = {memory_requirements.size, - get_device()->get_gpu().get_memory_type(memory_requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal)}; - texture.device_memory = get_device()->get_handle().allocateMemory(memory_allocate_info); - get_device()->get_handle().bindImageMemory(texture.image, texture.device_memory, 0); + memory_requirements = device.getImageMemoryRequirements(texture.image); + memory_type = get_device()->get_gpu().get_memory_type(memory_requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal); + memory_allocate_info = {memory_requirements.size, memory_type}; + texture.device_memory = device.allocateMemory(memory_allocate_info); + device.bindImageMemory(texture.image, texture.device_memory, 0); - vk::CommandBuffer copy_command = get_device()->create_command_buffer(vk::CommandBufferLevel::ePrimary, true); + vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(device, get_device()->get_command_pool().get_handle()); + copy_command.begin(vk::CommandBufferBeginInfo()); // Image memory barriers for the texture image // The sub resource range describes the regions of the image that will be transitioned using the memory barriers below vk::ImageSubresourceRange subresource_range; - // Image only contains color data - subresource_range.aspectMask = vk::ImageAspectFlagBits::eColor; - // Start at first mip level - subresource_range.baseMipLevel = 0; - // We will transition on all mip levels - subresource_range.levelCount = texture.mip_levels; - // The 2D texture only has one layer - subresource_range.layerCount = 1; + subresource_range.aspectMask = vk::ImageAspectFlagBits::eColor; // Image contains only color data + subresource_range.baseMipLevel = 0; // Start at first mip level + subresource_range.levelCount = texture.mip_levels; // We will transition on all mip levels + subresource_range.layerCount = 1; // The 2D texture only has one layer // Transition the texture image layout to transfer target, so we can safely copy our buffer data to it. vk::ImageMemoryBarrier image_memory_barrier; @@ -223,8 +417,8 @@ void HPPTextureLoading::load_texture() get_device()->flush_command_buffer(copy_command, queue, true); // Clean up staging resources - get_device()->get_handle().destroyBuffer(staging_buffer); - get_device()->get_handle().freeMemory(staging_memory); + device.destroyBuffer(staging_buffer); + device.freeMemory(staging_memory); } else { @@ -242,23 +436,25 @@ void HPPTextureLoading::load_texture() image_create_info.sharingMode = vk::SharingMode::eExclusive; image_create_info.initialLayout = vk::ImageLayout::ePreinitialized; image_create_info.extent = vk::Extent3D(texture.extent, 1); - vk::Image mappable_image = get_device()->get_handle().createImage(image_create_info); + vk::Image mappable_image = device.createImage(image_create_info); // Get memory requirements for this image like size and alignment - vk::MemoryRequirements memory_requirements = get_device()->get_handle().getImageMemoryRequirements(mappable_image); + vk::MemoryRequirements memory_requirements = device.getImageMemoryRequirements(mappable_image); + + // Get memory type that can be mapped to host memory + uint32_t memory_type = get_device()->get_gpu().get_memory_type(memory_requirements.memoryTypeBits, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); + // Set memory allocation size to required memory size - vk::MemoryAllocateInfo memory_allocate_info( - memory_requirements.size, - // Get memory type that can be mapped to host memory - get_device()->get_gpu().get_memory_type(memory_requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)); - vk::DeviceMemory mappable_memory = get_device()->get_handle().allocateMemory(memory_allocate_info); - get_device()->get_handle().bindImageMemory(mappable_image, mappable_memory, 0); + vk::MemoryAllocateInfo memory_allocate_info(memory_requirements.size, memory_type); + vk::DeviceMemory mappable_memory = device.allocateMemory(memory_allocate_info); + device.bindImageMemory(mappable_image, mappable_memory, 0); // Map image memory - void *data = get_device()->get_handle().mapMemory(mappable_memory, 0, memory_requirements.size); + void *data = device.mapMemory(mappable_memory, 0, memory_requirements.size); // Copy image data of the first mip level into memory memcpy(data, ktx_texture->pData, ktxTexture_GetImageSize(ktx_texture, 0)); - get_device()->get_handle().unmapMemory(mappable_memory); + device.unmapMemory(mappable_memory); // Linear tiled images don't need to be staged and can be directly used as textures texture.image = mappable_image; @@ -266,7 +462,8 @@ void HPPTextureLoading::load_texture() texture.image_layout = vk::ImageLayout::eShaderReadOnlyOptimal; // Setup image memory barrier transfer image to shader read layout - vk::CommandBuffer copy_command = get_device()->create_command_buffer(vk::CommandBufferLevel::ePrimary, true); + vk::CommandBuffer copy_command = vkb::common::allocate_command_buffer(device, get_device()->get_command_pool().get_handle()); + copy_command.begin(vk::CommandBufferBeginInfo()); // The sub resource range describes the regions of the image we will be transition vk::ImageSubresourceRange subresource_range(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1); @@ -295,18 +492,18 @@ void HPPTextureLoading::load_texture() // This separates all the sampling information from the texture data. This means you could have multiple sampler objects for the same texture with different settings // Note: Similar to the samplers available with OpenGL 3.3 vk::SamplerCreateInfo sampler; - sampler.magFilter = vk::Filter::eLinear; - sampler.minFilter = vk::Filter::eLinear; - sampler.mipmapMode = vk::SamplerMipmapMode::eLinear; - sampler.addressModeU = vk::SamplerAddressMode::eRepeat; - sampler.addressModeV = vk::SamplerAddressMode::eRepeat; - sampler.addressModeW = vk::SamplerAddressMode::eRepeat; - sampler.mipLodBias = 0.0f; - sampler.compareOp = vk::CompareOp::eNever; - sampler.minLod = 0.0f; - // Set max level-of-detail to mip level count of the texture - sampler.maxLod = (use_staging) ? static_cast(texture.mip_levels) : 0.0f; + sampler.magFilter = vk::Filter::eLinear; + sampler.minFilter = vk::Filter::eLinear; + sampler.mipmapMode = vk::SamplerMipmapMode::eLinear; + sampler.addressModeU = vk::SamplerAddressMode::eRepeat; + sampler.addressModeV = vk::SamplerAddressMode::eRepeat; + sampler.addressModeW = vk::SamplerAddressMode::eRepeat; + sampler.mipLodBias = 0.0f; + sampler.compareOp = vk::CompareOp::eNever; + sampler.minLod = 0.0f; + sampler.maxLod = (use_staging) ? static_cast(texture.mip_levels) : 0.0f; // Set max level-of-detail to mip level count of the texture sampler.maxAnisotropy = 1.0f; + // Enable anisotropic filtering // This feature is optional, so we must check if it's supported on the device if (get_device()->get_gpu().get_features().samplerAnisotropy) @@ -322,170 +519,47 @@ void HPPTextureLoading::load_texture() sampler.anisotropyEnable = false; } sampler.borderColor = vk::BorderColor::eFloatOpaqueWhite; - texture.sampler = get_device()->get_handle().createSampler(sampler); + texture.sampler = device.createSampler(sampler); // Create image view // Textures are not directly accessed by the shaders and // are abstracted by image views containing additional // information and sub resource ranges - vk::ImageViewCreateInfo view; - view.viewType = vk::ImageViewType::e2D; - view.format = format; - view.components = {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA}; - // The subresource range describes the set of mip levels (and array layers) that can be accessed through this image view + vk::ImageViewCreateInfo image_view_create_info; + image_view_create_info.viewType = vk::ImageViewType::e2D; + image_view_create_info.format = format; + image_view_create_info.components = {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA}; + // The subresource range describes the set of mip levels (and array layers) that can be accessed through this image image_view_create_info // It's possible to create multiple image views for a single image referring to different (and/or overlapping) ranges of the image - view.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - view.subresourceRange.baseMipLevel = 0; - view.subresourceRange.baseArrayLayer = 0; - view.subresourceRange.layerCount = 1; + image_view_create_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + image_view_create_info.subresourceRange.baseMipLevel = 0; + image_view_create_info.subresourceRange.baseArrayLayer = 0; + image_view_create_info.subresourceRange.layerCount = 1; // Linear tiling usually won't support mip maps // Only set mip map count if optimal tiling is used - view.subresourceRange.levelCount = (use_staging) ? texture.mip_levels : 1; - // The view will be based on the texture's image - view.image = texture.image; - texture.view = get_device()->get_handle().createImageView(view); + image_view_create_info.subresourceRange.levelCount = (use_staging) ? texture.mip_levels : 1; + // The image_view_create_info will be based on the texture's image + image_view_create_info.image = texture.image; + texture.image_view = device.createImageView(image_view_create_info); } -// Free all Vulkan resources used by a texture object -void HPPTextureLoading::destroy_texture(Texture texture) -{ - get_device()->get_handle().destroyImageView(texture.view); - get_device()->get_handle().destroyImage(texture.image); - get_device()->get_handle().destroySampler(texture.sampler); - get_device()->get_handle().freeMemory(texture.device_memory); -} - -void HPPTextureLoading::build_command_buffers() -{ - vk::CommandBufferBeginInfo command_buffer_begin_info; - - vk::ClearValue clear_values[2]; - clear_values[0].color = default_clear_color; - clear_values[1].depthStencil = vk::ClearDepthStencilValue(0.0f, 0); - - vk::RenderPassBeginInfo 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 = extent; - render_pass_begin_info.clearValueCount = 2; - render_pass_begin_info.pClearValues = clear_values; - - for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i) - { - // Set target frame buffer - render_pass_begin_info.framebuffer = framebuffers[i]; - - draw_cmd_buffers[i].begin(command_buffer_begin_info); - - draw_cmd_buffers[i].beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline); - - vk::Viewport viewport(0.0f, 0.0f, static_cast(extent.width), static_cast(extent.height), 0.0f, 1.0f); - draw_cmd_buffers[i].setViewport(0, viewport); - - vk::Rect2D scissor({0, 0}, extent); - draw_cmd_buffers[i].setScissor(0, scissor); - - draw_cmd_buffers[i].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set, {}); - draw_cmd_buffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); - - vk::DeviceSize offset = 0; - draw_cmd_buffers[i].bindVertexBuffers(0, vertex_buffer->get_handle(), offset); - draw_cmd_buffers[i].bindIndexBuffer(index_buffer->get_handle(), 0, vk::IndexType::eUint32); - - draw_cmd_buffers[i].drawIndexed(index_count, 1, 0, 0, 0); - - draw_ui(draw_cmd_buffers[i]); - - draw_cmd_buffers[i].endRenderPass(); - - draw_cmd_buffers[i].end(); - } -} - -void HPPTextureLoading::draw() -{ - HPPApiVulkanSample::prepare_frame(); - - // Command buffer to be submitted to the queue - submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]); - - // Submit to queue - queue.submit(submit_info); - - HPPApiVulkanSample::submit_frame(); -} - -void HPPTextureLoading::generate_quad() -{ - // Setup vertices for a single uv-mapped quad made from two triangles - std::vector vertices = - { - {{1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, - {{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, - {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, - {{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}}; - - // Setup indices - std::vector indices = {0, 1, 2, 2, 3, 0}; - index_count = static_cast(indices.size()); - - auto vertex_buffer_size = vkb::to_u32(vertices.size() * sizeof(HPPTextureLoadingVertexStructure)); - auto index_buffer_size = vkb::to_u32(indices.size() * sizeof(uint32_t)); - - // Create buffers - // For the sake of simplicity we won't stage the vertex data to the gpu memory - // Vertex buffer - vertex_buffer = std::make_unique( - *get_device(), vertex_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); - vertex_buffer->update(vertices.data(), vertex_buffer_size); - - // Index buffer - index_buffer = std::make_unique( - *get_device(), index_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); - index_buffer->update(indices.data(), index_buffer_size); -} - -void HPPTextureLoading::setup_descriptor_pool() -{ - // Example uses one ubo and one image sampler - std::array pool_sizes = {{{vk::DescriptorType::eUniformBuffer, 1}, {vk::DescriptorType::eCombinedImageSampler, 1}}}; - - vk::DescriptorPoolCreateInfo descriptor_pool_create_info({}, 2, pool_sizes); - - descriptor_pool = get_device()->get_handle().createDescriptorPool(descriptor_pool_create_info); -} - -void HPPTextureLoading::setup_descriptor_set_layout() +// Prepare and initialize uniform buffer containing shader uniforms +void HPPTextureLoading::prepare_uniform_buffers() { - std::array set_layout_bindings = { - {{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex}, - {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}}; - - vk::DescriptorSetLayoutCreateInfo descriptor_layout({}, set_layout_bindings); - - descriptor_set_layout = get_device()->get_handle().createDescriptorSetLayout(descriptor_layout); - -#if defined(ANDROID) - vk::PipelineLayoutCreateInfo pipeline_layout_create_info({}, 1, &descriptor_set_layout); -#else - vk::PipelineLayoutCreateInfo pipeline_layout_create_info({}, descriptor_set_layout); -#endif + // Vertex shader uniform buffer block + vertex_shader_data_buffer = + std::make_unique(*get_device(), sizeof(vertex_shader_data), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); - pipeline_layout = get_device()->get_handle().createPipelineLayout(pipeline_layout_create_info); + update_uniform_buffers(); } -void HPPTextureLoading::setup_descriptor_set() +void HPPTextureLoading::update_descriptor_set() { - vk::DescriptorSetAllocateInfo alloc_info(descriptor_pool, 1, &descriptor_set_layout); - - descriptor_set = get_device()->get_handle().allocateDescriptorSets(alloc_info).front(); - - vk::DescriptorBufferInfo buffer_descriptor(uniform_buffer_vs->get_handle(), 0, VK_WHOLE_SIZE); + vk::DescriptorBufferInfo buffer_descriptor(vertex_shader_data_buffer->get_handle(), 0, VK_WHOLE_SIZE); // Setup a descriptor image info for the current texture to be used as a combined image sampler vk::DescriptorImageInfo image_descriptor; - image_descriptor.imageView = texture.view; // The image's view (images are never directly accessed by the shader, but rather through views defining subresources) + image_descriptor.imageView = texture.image_view; // The image's view (images are never directly accessed by the shader, but rather through views defining subresources) image_descriptor.sampler = texture.sampler; // The sampler (Telling the pipeline how to sample the texture, including repeat, border, etc.) image_descriptor.imageLayout = texture.image_layout; // The current layout of the image (Note: Should always fit the actual use, e.g. shader read) @@ -499,136 +573,20 @@ void HPPTextureLoading::setup_descriptor_set() get_device()->get_handle().updateDescriptorSets(write_descriptor_sets, {}); } -void HPPTextureLoading::prepare_pipeline() -{ - vk::PipelineInputAssemblyStateCreateInfo input_assembly_state({}, vk::PrimitiveTopology::eTriangleList); - - vk::PipelineRasterizationStateCreateInfo rasterization_state; - rasterization_state.polygonMode = vk::PolygonMode::eFill; - rasterization_state.cullMode = vk::CullModeFlagBits::eNone; - rasterization_state.frontFace = vk::FrontFace::eCounterClockwise; - rasterization_state.lineWidth = 1.0f; - - vk::PipelineColorBlendAttachmentState blend_attachment_state; - blend_attachment_state.colorWriteMask = - vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - - vk::PipelineColorBlendStateCreateInfo color_blend_state({}, {}, {}, blend_attachment_state); - - // Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept - vk::PipelineDepthStencilStateCreateInfo depth_stencil_state; - depth_stencil_state.depthTestEnable = true; - depth_stencil_state.depthWriteEnable = true; - depth_stencil_state.depthCompareOp = vk::CompareOp::eGreater; - depth_stencil_state.back.compareOp = vk::CompareOp::eGreater; - - vk::PipelineViewportStateCreateInfo viewport_state({}, 1, nullptr, 1, nullptr); - - vk::PipelineMultisampleStateCreateInfo multisample_state({}, vk::SampleCountFlagBits::e1); - - std::array dynamic_state_enables = {{vk::DynamicState::eViewport, vk::DynamicState::eScissor}}; - vk::PipelineDynamicStateCreateInfo dynamic_state({}, dynamic_state_enables); - - // Load shaders - std::array shader_stages = {{load_shader("texture_loading/texture.vert", vk::ShaderStageFlagBits::eVertex), - load_shader("texture_loading/texture.frag", vk::ShaderStageFlagBits::eFragment)}}; - - // Vertex bindings and attributes - vk::VertexInputBindingDescription vertex_input_binding(0, sizeof(HPPTextureLoadingVertexStructure), vk::VertexInputRate::eVertex); - std::array vertex_input_attributes = { - {{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPTextureLoadingVertexStructure, pos)}, // Location 0 : Position - {1, 0, vk::Format::eR32G32Sfloat, offsetof(HPPTextureLoadingVertexStructure, uv)}, // Location 1: Texture Coordinates - {2, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPTextureLoadingVertexStructure, normal)}}}; // Location 2 : Normal - vk::PipelineVertexInputStateCreateInfo vertex_input_state({}, vertex_input_binding, vertex_input_attributes); - - vk::GraphicsPipelineCreateInfo pipeline_create_info({}, - shader_stages, - &vertex_input_state, - &input_assembly_state, - {}, - &viewport_state, - &rasterization_state, - &multisample_state, - &depth_stencil_state, - &color_blend_state, - &dynamic_state, - pipeline_layout, - render_pass, - {}, - {}, - -1); - - vk::Result result; - std::tie(result, pipeline) = get_device()->get_handle().createGraphicsPipeline(pipeline_cache, pipeline_create_info); - assert(result == vk::Result::eSuccess); -} - -// Prepare and initialize uniform buffer containing shader uniforms -void HPPTextureLoading::prepare_uniform_buffers() -{ - // Vertex shader uniform buffer block - uniform_buffer_vs = std::make_unique(*get_device(), sizeof(ubo_vs), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU); - - update_uniform_buffers(); -} - void HPPTextureLoading::update_uniform_buffers() { // Vertex shader - ubo_vs.projection = glm::perspective(glm::radians(60.0f), static_cast(extent.width) / static_cast(extent.height), 0.001f, 256.0f); - glm::mat4 view_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom)); - - ubo_vs.model = view_matrix * glm::translate(glm::mat4(1.0f), camera_pos); - ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); - ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); - ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); - - ubo_vs.view_pos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f); - - uniform_buffer_vs->convert_and_update(ubo_vs); -} - -bool HPPTextureLoading::prepare(const vkb::ApplicationOptions &options) -{ - if (!HPPApiVulkanSample::prepare(options)) - { - return false; - } - load_texture(); - generate_quad(); - prepare_uniform_buffers(); - setup_descriptor_set_layout(); - prepare_pipeline(); - setup_descriptor_pool(); - setup_descriptor_set(); - build_command_buffers(); - prepared = true; - return true; -} + vertex_shader_data.projection = glm::perspective(glm::radians(60.0f), static_cast(extent.width) / static_cast(extent.height), 0.001f, 256.0f); + glm::mat4 view_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom)); -void HPPTextureLoading::render(float delta_time) -{ - if (!prepared) - { - return; - } - draw(); -} + vertex_shader_data.model = view_matrix * glm::translate(glm::mat4(1.0f), camera_pos); + vertex_shader_data.model = glm::rotate(vertex_shader_data.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); + vertex_shader_data.model = glm::rotate(vertex_shader_data.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); + vertex_shader_data.model = glm::rotate(vertex_shader_data.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); -void HPPTextureLoading::view_changed() -{ - update_uniform_buffers(); -} + vertex_shader_data.view_pos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f); -void HPPTextureLoading::on_update_ui_overlay(vkb::HPPDrawer &drawer) -{ - if (drawer.header("Settings")) - { - if (drawer.slider_float("LOD bias", &ubo_vs.lod_bias, 0.0f, static_cast(texture.mip_levels))) - { - update_uniform_buffers(); - } - } + vertex_shader_data_buffer->convert_and_update(vertex_shader_data); } std::unique_ptr create_hpp_texture_loading() diff --git a/samples/api/hpp_texture_loading/hpp_texture_loading.h b/samples/api/hpp_texture_loading/hpp_texture_loading.h index 6d3d58f8b..af2410cc6 100644 --- a/samples/api/hpp_texture_loading/hpp_texture_loading.h +++ b/samples/api/hpp_texture_loading/hpp_texture_loading.h @@ -25,67 +25,84 @@ #include -// Vertex layout for this example -struct HPPTextureLoadingVertexStructure -{ - float pos[3]; - float uv[2]; - float normal[3]; -}; - class HPPTextureLoading : public HPPApiVulkanSample { public: + HPPTextureLoading(); + ~HPPTextureLoading(); + + private: // Contains all Vulkan objects that are required to store and use a texture // Note that this repository contains a texture class (vulkan_texture.h) that encapsulates texture loading functionality in a class that is used in subsequent demos struct Texture { - vk::Sampler sampler; + vk::DeviceMemory device_memory; vk::Image image; vk::ImageLayout image_layout; - vk::DeviceMemory device_memory; - vk::ImageView view; + vk::ImageView image_view; + vk::Sampler sampler; vk::Extent2D extent; uint32_t mip_levels; - } texture; - std::unique_ptr vertex_buffer; - std::unique_ptr index_buffer; - uint32_t index_count; + void destroy(vk::Device device) + { + device.destroyImageView(image_view); + device.destroyImage(image); + device.destroySampler(sampler); + device.freeMemory(device_memory); + } + }; - std::unique_ptr uniform_buffer_vs; + // Vertex layout for this example + struct Vertex + { + float pos[3]; + float uv[2]; + float normal[3]; + }; - struct + struct VertexShaderData { glm::mat4 projection; glm::mat4 model; glm::vec4 view_pos; float lod_bias = 0.0f; - } ubo_vs; + }; - vk::Pipeline pipeline; - vk::PipelineLayout pipeline_layout; - vk::DescriptorSet descriptor_set; - vk::DescriptorSetLayout descriptor_set_layout; + private: + // from vkb::Application + bool prepare(const vkb::ApplicationOptions &options) override; - HPPTextureLoading(); - ~HPPTextureLoading(); - virtual void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override; - void load_texture(); - void destroy_texture(Texture texture); - void build_command_buffers() override; - void draw(); - void generate_quad(); - void setup_descriptor_pool(); - void setup_descriptor_set_layout(); - void setup_descriptor_set(); - void prepare_pipeline(); - void prepare_uniform_buffers(); - void update_uniform_buffers(); - bool prepare(const vkb::ApplicationOptions &options) override; - virtual void render(float delta_time) override; - virtual void view_changed() override; - virtual void on_update_ui_overlay(vkb::HPPDrawer &drawer) override; + // from HPPVulkanSample + void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override; + + // from HPPApiVulkanSample + void build_command_buffers() override; + void on_update_ui_overlay(vkb::HPPDrawer &drawer) override; + void render(float delta_time) override; + void view_changed() override; + + vk::DescriptorPool create_descriptor_pool(); + vk::DescriptorSetLayout create_descriptor_set_layout(); + vk::Pipeline create_pipeline(); + void draw(); + void generate_quad(); + void load_texture(); + void prepare_uniform_buffers(); + void update_descriptor_set(); + void update_uniform_buffers(); + + private: + vk::DescriptorSet descriptor_set; + vk::DescriptorSetLayout descriptor_set_layout; + std::unique_ptr index_buffer; + uint32_t index_count; + vk::Pipeline pipeline; + vk::PipelineLayout pipeline_layout; + Texture texture; + std::unique_ptr vertex_buffer; + VertexShaderData vertex_shader_data; + std::unique_ptr vertex_shader_data_buffer; }; std::unique_ptr create_hpp_texture_loading(); diff --git a/samples/extensions/dynamic_line_rasterization/CMakeLists.txt b/samples/extensions/dynamic_line_rasterization/CMakeLists.txt new file mode 100644 index 000000000..4693789bd --- /dev/null +++ b/samples/extensions/dynamic_line_rasterization/CMakeLists.txt @@ -0,0 +1,33 @@ +# 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 "Khronos" + NAME "dynamic_line_rasterization" + DESCRIPTION "Sample description" + SHADER_FILES_GLSL + "dynamic_line_rasterization/base.vert" + "dynamic_line_rasterization/base.frag" + "dynamic_line_rasterization/grid.vert" + "dynamic_line_rasterization/grid.frag" + ) diff --git a/samples/extensions/dynamic_line_rasterization/README.adoc b/samples/extensions/dynamic_line_rasterization/README.adoc new file mode 100644 index 000000000..47226e685 --- /dev/null +++ b/samples/extensions/dynamic_line_rasterization/README.adoc @@ -0,0 +1,63 @@ +//// +- 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 line rasterization + +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 diff --git a/samples/extensions/dynamic_line_rasterization/dynamic_line_rasterization.cpp b/samples/extensions/dynamic_line_rasterization/dynamic_line_rasterization.cpp new file mode 100644 index 000000000..c0e628fc5 --- /dev/null +++ b/samples/extensions/dynamic_line_rasterization/dynamic_line_rasterization.cpp @@ -0,0 +1,515 @@ +/* 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. + */ + +#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 (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(width) / static_cast(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 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 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 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(cube_indices.size()); + edges_index_count = static_cast(edges_indices.size()); + uint32_t vertex_buffer_size = vertices.size() * sizeof(glm::vec3); + uint32_t cube_index_buffer_size = cube_indices.size() * sizeof(uint32_t); + uint32_t edges_index_buffer_size = edges_indices.size() * sizeof(uint32_t); + + vertex_buffer = std::make_unique(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(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(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 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(pool_sizes.size()), + pool_sizes.data(), + static_cast(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 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(dynamic_state_enables.size()), + 0); + + const std::vector vertex_input_bindings = { + vkb::initializers::vertex_input_binding_description(0, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX), + }; + const std::vector 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(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("dynamic_line_rasterization/base.vert", VK_SHADER_STAGE_VERTEX_BIT); + shader_stages[1] = load_shader("dynamic_line_rasterization/base.frag", 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", VK_SHADER_STAGE_VERTEX_BIT); + shader_stages[1] = load_shader("dynamic_line_rasterization/grid.frag", 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(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; + + camera_ubo->convert_and_update(cam); + + build_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 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(write_descriptor_sets.size()), write_descriptor_sets.data(), 0u, nullptr); +} + +void DynamicLineRasterization::create_descriptor_set_layout() +{ + std::vector 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 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(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(width), static_cast(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(gui_settings.selected_rasterization_mode)); + vkCmdSetLineWidth(cmd_buff, gui_settings.line_width); + vkCmdSetLineStippleEnableEXT(cmd_buff, static_cast(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) +{ + { + auto &features = gpu.request_extension_features( + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT); + features.smoothLines = VK_TRUE; + features.stippledSmoothLines = VK_TRUE; + features.bresenhamLines = VK_TRUE; + features.stippledBresenhamLines = VK_TRUE; + features.rectangularLines = VK_TRUE; + features.stippledRectangularLines = VK_TRUE; + } + { + auto &features = + gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT); + features.extendedDynamicState = VK_TRUE; + } + { + auto &features = + gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT); + features.extendedDynamicState3PolygonMode = VK_TRUE; + features.extendedDynamicState3LineRasterizationMode = VK_TRUE; + features.extendedDynamicState3LineStippleEnable = VK_TRUE; + } + { + 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) + build_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); + + build_command_buffers(); + } + ImGui::PopID(); + if (i % 8 != 7) + ImGui::SameLine(); + } + } +} + +uint16_t DynamicLineRasterization::array_to_uint16(const std::array &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 create_dynamic_line_rasterization() +{ + return std::make_unique(); +} diff --git a/samples/extensions/dynamic_line_rasterization/dynamic_line_rasterization.h b/samples/extensions/dynamic_line_rasterization/dynamic_line_rasterization.h new file mode 100644 index 000000000..ce2af2d71 --- /dev/null +++ b/samples/extensions/dynamic_line_rasterization/dynamic_line_rasterization.h @@ -0,0 +1,91 @@ +/* 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" + +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; + }; + + struct + { + VkPipeline grid; + VkPipeline object; + } pipelines; + + VkDescriptorSet descriptor_set; + VkDescriptorSetLayout descriptor_set_layout; + VkPipelineLayout pipeline_layout; + VkDescriptorPool descriptor_pool; + + std::unique_ptr camera_ubo; + std::unique_ptr vertex_buffer; + std::unique_ptr 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 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 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 &array); +}; + +std::unique_ptr create_dynamic_line_rasterization(); diff --git a/samples/extensions/dynamic_line_rasterization/screenshot.png b/samples/extensions/dynamic_line_rasterization/screenshot.png new file mode 100644 index 000000000..a99a4ef9e Binary files /dev/null and b/samples/extensions/dynamic_line_rasterization/screenshot.png differ diff --git a/samples/extensions/ray_tracing_basic/README.adoc b/samples/extensions/ray_tracing_basic/README.adoc new file mode 100644 index 000000000..f4de092e8 --- /dev/null +++ b/samples/extensions/ray_tracing_basic/README.adoc @@ -0,0 +1,26 @@ +//// +- Copyright (c) 2020-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. +- +//// + +=== Basic hardware accelerated ray tracing + +*Extensions*: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_ray_tracing_pipeline[`VK_KHR_ray_tracing_pipeline`], https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_acceleration_structure[`VK_KHR_acceleration_structure`] + +Render a basic scene using the official cross-vendor ray tracing extension. +Shows how to setup all data structures required for ray tracing, including the bottom and top level acceleration structures for the geometry, the shader binding table and the ray tracing pipelines with shader groups for ray generation, ray hits, and ray misses. +After dispatching the rays, the final result is copied to the swapchain image. diff --git a/samples/extensions/ray_tracing_basic/README.md b/samples/extensions/ray_tracing_basic/README.md deleted file mode 100644 index b4e49c06c..000000000 --- a/samples/extensions/ray_tracing_basic/README.md +++ /dev/null @@ -1,23 +0,0 @@ - -### Basic hardware accelerated ray tracing - -**Extensions**: [```VK_KHR_ray_tracing_pipeline```](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_ray_tracing_pipeline), [```VK_KHR_acceleration_structure```](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_acceleration_structure) - -Render a basic scene using the official cross-vendor ray tracing extension. Shows how to setup all data structures required for ray tracing, including the bottom and top level acceleration structures for the geometry, the shader binding table and the ray tracing pipelines with shader groups for ray generation, ray hits, and ray misses. After dispatching the rays, the final result is copied to the swapchain image. diff --git a/samples/extensions/ray_tracing_extended/CMakeLists.txt b/samples/extensions/ray_tracing_extended/CMakeLists.txt index 03449c945..9fe065384 100644 --- a/samples/extensions/ray_tracing_extended/CMakeLists.txt +++ b/samples/extensions/ray_tracing_extended/CMakeLists.txt @@ -24,4 +24,8 @@ add_sample_with_tags( CATEGORY ${CATEGORY_NAME} AUTHOR "Holochip Corporation" NAME "Ray tracing extended" - DESCRIPTION "Extended example of Ray Tracing highlighting the Bottom and Top Level Acceleration Structure rebuild and AO.") + DESCRIPTION "Extended example of Ray Tracing highlighting the Bottom and Top Level Acceleration Structure rebuild and AO." + SHADER_FILES_GLSL + "khr_ray_tracing_extended/raygen.rgen" + "khr_ray_tracing_extended/miss.rmiss" + "khr_ray_tracing_extended/closesthit.rchit") diff --git a/samples/extensions/ray_tracing_extended/README.adoc b/samples/extensions/ray_tracing_extended/README.adoc new file mode 100644 index 000000000..1c6de4852 --- /dev/null +++ b/samples/extensions/ray_tracing_extended/README.adoc @@ -0,0 +1,133 @@ +//// +- Copyright (c) 2019-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. +- +//// += Ray-tracing: Extended features and dynamic objects + +This code sample demonstrates how to incorporate animations into a ray-traced scene, and shows how to incorporate different types of changing objects within the acceleration structures. + +== Acceleration structures + +The ray tracing acceleration structures are separated into two types: bottom-level acceleration structures (BLAS) and top-level acceleration structures (TLAS). +The BLAS contains information about each object's geometry within its own coordinate system and is built using the vertex and index data stored in a GPU buffer. +In contrast, the TLAS contains information about each instance of the geometry and its transformation (i.e. +scaling, rotation, translation, etc.). + +Each object must be represented in the BLAS, but can have any number of instances, each with its own transformation. +This allows objects to be replicated without creating an acceleration structure for each instance. + +== Objects: Static, moving, and changing + +There are three categories of objects to consider when building acceleration structures: static, moving, and changing geometry. +Static geometry includes scene data. +In this code sample, the Sponza scene has a single, non-moving instance. +In contrast, dynamic objects can have a changing transformation, changing geometry, or both. +An example of transformation-only dynamic objects in this code sample are given by the flame particle effect, which is achieved by adjusting only the location and rotation of a square billboard -- the internal geometry (and thus the billboard's BLAS) does not change. +In contrast, the refraction effect is achieved by changing both the internal geometry each frame, and the rotation (so that it faces the viewer). + +Vulkan offers methods of optimizing the acceleration structures for each type of geometry. +The `VkAccelerationStructureBuildGeometryInfoKHR` struct has flags that can either toggle "fast trace", which optimizes run-time performance at the expense of build time, or "fast build", which optimizes build time. +When constructing large, static objects such as the Sponza scene, for instance, the "fast trace" bit (`VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR`) is selected because the build will occur once and the model contains many points. +When constructing dynamic objects such as the refraction model, which will need a BLAS update every frame, the "fast build" bit (`VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR`) is selected. + +Further optimization methods can be used. +For instance, the refraction model is updated every frame by the CPU and thus uses host-visible memory. +However, because host-visible memory can incur a performance penalty, the Sponza and billboard models use a staging buffer to copy to device-exclusive memory. +An alternative method would be to use a "compute shader" to generate the refraction model each frame, but that is outside the scope of this tutorial. + +== Reference Object Data from a Closest-Hit Shader + +Though the ray-tracing pipeline uses an acceleration structure to traverse the scene's geometry, the acceleration structures themselves do not store user-defined information about the geometry and instead give the developer the flexibility to define their own custom geometry information. +This information can be encoded at the per-instance level, per-object level, or per-primitive level. + +_Per-instance level:_ The top-level acceleration structure allows instance information to encode a custom ID ( per-instance level). + +_Per-object level:_ In this code sample, this custom ID then references a struct at the per-object level containing the object ID , the index of the vertices in the vertex buffer, and the index of the (triangle) indices in the index buffer: + +---- +struct SceneInstanceData +{ + uint32_t vertex_index; + uint32_t indices_index; + uint32_t image_index; + uint32_t object_type; +}; +---- + +_Per-primitive level_ In this sample, each vertex is encoded with a per-vertex normal and texture coordinate, though other applications may wish to provide other information at the per-vertex level. +To allow the bottom-level acceleration structure to reference geometry data with a custom-defined layout, the `VkAccelerationStructureGeometryKHR` provides the ability to set geometry offsets and strides (i.e. +`vertexStride`). +In the code below, the struct `acceleration_structure_geometry` of type `VkAccelerationStructureGeometryKHR` references the data layout provided by NewVertex, which encodes the normal and texture coordinate: + +---- +acceleration_structure_geometry.geometry.triangles.vertexData = vertex_data_device_address; +acceleration_structure_geometry.geometry.triangles.maxVertex = model_buffer.num_vertices; +acceleration_structure_geometry.geometry.triangles.vertexStride = sizeof(NewVertex); +acceleration_structure_geometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32; +acceleration_structure_geometry.geometry.triangles.indexData = index_data_device_address; +acceleration_structure_geometry.geometry.triangles.transformData = transform_matrix_device_address; +---- + +This technique allows the closest-hit shader to access pre-calculated vertex information. + +== Texture Binding and Shaders + +In a traditional raster pipeline, it is possible to render each object separately and bind its appropriate texture images during that pass. +However, in a ray-tracing pipeline, each ray during a render pass could intersect with many objects within the scene, and thus all textures must be available to the shader. +In this code sample, an array of textures (`Sampler2D[]`) is bound, and each object is associated with a given texture index. +The texture ID information is stored in the object data. + +== Ambient Occlusion and Ray-Traced Shadows + +This code sample explores two different ways to calculate lighting: ray-traced shadows and ambient occlusion, both of which are updated each frame and are triggered when a primary ray intersects a scene object (i.e. +an element of the Sponza scene). + +Ray-traced shadows are calculated by performing a test: a ray is shot from the object point in the direction of the light. +If the returned distance is less than the distance to the light source, then the object point is in a shadow. +In pseudocode: + +---- +direction = object_pt - light_pt +dist = trace_ray(object_pt, direction) +if (dist < distance(object_pt, light_pt)): + color.rgb *= 0.2 +---- + +The ambient occlusion effect is used to simulate the light diminishing effect of clustered geometry. +It's simulated by tracing rays distributed about a hemisphere centered at the intersection point with the object's normal. +The light-diminishing effect is estimated using the distance to the nearest ray intersection. +In some implementations, a hard threshold is used. +In pseudocode: + +---- +for theta,phi in angles: + hard_threshold = 10.f + direction = hemisphere_pt(object_pt, normal, theta, phi) + dist = trace_ray(object_pt, direction) + if (dist < hard_threshold): + color.rgb *= 0.2 +---- + +The code sample in this tutorial instead linearly interpolates up to the hard_threshold: + +---- +color.rgb *= min(dist, hard_threshold) / min_threshold +---- + +There are further optimizations that can be used. +One common technique is to reduce the number of generated ambient occlusion rays at each point, often shooting just a single ray. +The resulting image can then be de-noised using a separate de-noising pass, though this technique is outside the scope of this tutorial. diff --git a/samples/extensions/ray_tracing_extended/README.md b/samples/extensions/ray_tracing_extended/README.md deleted file mode 100644 index db8545e1f..000000000 --- a/samples/extensions/ray_tracing_extended/README.md +++ /dev/null @@ -1,147 +0,0 @@ - - -# Ray-tracing: Extended features and dynamic objects - -This code sample demonstrates how to incorporate animations into a ray-traced scene, and shows how to incorporate -different types of changing objects within the acceleration structures. - -## Acceleration structures - -The ray tracing acceleration structures are separated into two types: bottom-level acceleration structures (BLAS) and -top-level acceleration structures (TLAS). The BLAS contains information about each object's geometry within its own -coordinate system and is built using the vertex and index data stored in a GPU buffer. In contrast, the TLAS contains -information about each instance of the geometry and its transformation (i.e. scaling, rotation, translation, etc.). - -Each object must be represented in the BLAS, but can have any number of instances, each with its own transformation. -This allows objects to be replicated without creating an acceleration structure for each instance. - -## Objects: Static, moving, and changing - -There are three categories of objects to consider when building acceleration structures: static, moving, and changing -geometry. Static geometry includes scene data. In this code sample, the Sponza scene has a single, non-moving instance. -In contrast, dynamic objects can have a changing transformation, changing geometry, or both. An example of -transformation-only dynamic objects in this code sample are given by the flame particle effect, which is achieved by -adjusting only the location and rotation of a square billboard -- the internal geometry (and thus the billboard's BLAS) -does not change. In contrast, the refraction effect is achieved by changing both the internal geometry each frame, and -the rotation (so that it faces the viewer). - -Vulkan offers methods of optimizing the acceleration structures for each type of geometry. -The `VkAccelerationStructureBuildGeometryInfoKHR` struct has flags that can either toggle "fast trace", which optimizes -run-time performance at the expense of build time, or "fast build", which optimizes build time. When constructing large, -static objects such as the Sponza scene, for instance, the "fast trace" -bit (`VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR`) is selected because the build will occur once and the -model contains many points. When constructing dynamic objects such as the refraction model, which will need a BLAS -update every frame, the "fast build" bit (`VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR`) is selected. - -Further optimization methods can be used. For instance, the refraction model is updated every frame by the CPU and thus -uses host-visible memory. However, because host-visible memory can incur a performance penalty, the Sponza and billboard -models use a staging buffer to copy to device-exclusive memory. An alternative method would be to use a "compute shader" -to generate the refraction model each frame, but that is outside the scope of this tutorial. - -## Reference Object Data from a Closest-Hit Shader - -Though the ray-tracing pipeline uses an acceleration structure to traverse the scene's geometry, the acceleration -structures themselves do not store user-defined information about the geometry and instead give the developer the -flexibility to define their own custom geometry information. This information can be encoded at the per-instance level, -per-object level, or per-primitive level. - -*Per-instance level:* The top-level acceleration structure allows instance information to encode a custom ID ( -per-instance level). - -*Per-object level:* In this code sample, this custom ID then references a struct at the per-object level containing the -object ID , the index of the vertices in the vertex buffer, and the index of the (triangle) indices in the index buffer: - -``` -struct SceneInstanceData -{ - uint32_t vertex_index; - uint32_t indices_index; - uint32_t image_index; - uint32_t object_type; -}; -``` - -*Per-primitive level* In this sample, each vertex is encoded with a per-vertex normal and texture coordinate, though -other applications may wish to provide other information at the per-vertex level. To allow the bottom-level acceleration -structure to reference geometry data with a custom-defined layout, the `VkAccelerationStructureGeometryKHR` provides the -ability to set geometry offsets and strides (i.e. `vertexStride`). In the code below, the -struct `acceleration_structure_geometry` of type `VkAccelerationStructureGeometryKHR` references the data layout -provided by NewVertex, which encodes the normal and texture coordinate: - -``` -acceleration_structure_geometry.geometry.triangles.vertexData = vertex_data_device_address; -acceleration_structure_geometry.geometry.triangles.maxVertex = model_buffer.num_vertices; -acceleration_structure_geometry.geometry.triangles.vertexStride = sizeof(NewVertex); -acceleration_structure_geometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32; -acceleration_structure_geometry.geometry.triangles.indexData = index_data_device_address; -acceleration_structure_geometry.geometry.triangles.transformData = transform_matrix_device_address; -``` - -This technique allows the closest-hit shader to access pre-calculated vertex information. - -## Texture Binding and Shaders - -In a traditional raster pipeline, it is possible to render each object separately and bind its appropriate texture -images during that pass. However, in a ray-tracing pipeline, each ray during a render pass could intersect with many -objects within the scene, and thus all textures must be available to the shader. In this code sample, an array of -textures (`Sampler2D[]`) is bound, and each object is associated with a given texture index. The texture ID information -is stored in the object data. - -## Ambient Occlusion and Ray-Traced Shadows - -This code sample explores two different ways to calculate lighting: ray-traced shadows and ambient occlusion, both of -which are updated each frame and are triggered when a primary ray intersects a scene object (i.e. an element of the -Sponza scene). - -Ray-traced shadows are calculated by performing a test: a ray is shot from the object point in the direction of the -light. If the returned distance is less than the distance to the light source, then the object point is in a shadow. In -pseudocode: - -``` -direction = object_pt - light_pt -dist = trace_ray(object_pt, direction) -if (dist < distance(object_pt, light_pt)): - color.rgb *= 0.2 -``` - -The ambient occlusion effect is used to simulate the light diminishing effect of clustered geometry. It's simulated by -tracing rays distributed about a hemisphere centered at the intersection point with the object's normal. The -light-diminishing effect is estimated using the distance to the nearest ray intersection. In some implementations, a -hard threshold is used. In pseudocode: - -``` -for theta,phi in angles: - hard_threshold = 10.f - direction = hemisphere_pt(object_pt, normal, theta, phi) - dist = trace_ray(object_pt, direction) - if (dist < hard_threshold): - color.rgb *= 0.2 -``` - -The code sample in this tutorial instead linearly interpolates up to the hard_threshold: - -``` -color.rgb *= min(dist, hard_threshold) / min_threshold -``` - -There are further optimizations that can be used. One common technique is to reduce the number of generated ambient -occlusion rays at each point, often shooting just a single ray. The resulting image can then be de-noised using a -separate de-noising pass, though this technique is outside the scope of this tutorial. - diff --git a/samples/extensions/shader_object/README.md b/samples/extensions/shader_object/README.adoc similarity index 64% rename from samples/extensions/shader_object/README.md rename to samples/extensions/shader_object/README.adoc index f734566b2..68586a190 100644 --- a/samples/extensions/shader_object/README.md +++ b/samples/extensions/shader_object/README.adoc @@ -1,24 +1,23 @@ - +//// += Shader Object -# Shader Object +image::./images/shader_object_screenshot.png[Sample] -![Sample](./images/shader_object_screenshot.png) - -## Overview +== Overview This sample demonstrates how to use the `VK_EXT_shader_object` extension, which provides a way to specify shaders and state without using `VkPipeline` objects. @@ -33,50 +32,62 @@ In its default configuration, this sample automatically iterates over permutatio * The output color format * The output depth format -The models in the middle demonstrate the use of linked vertex and fragment shaders, which can only ever change in tandem. The surrounding models use unlinked shaders, where each model uses its own separate vertex, fragment, and potentially geometry shaders. +The models in the middle demonstrate the use of linked vertex and fragment shaders, which can only ever change in tandem. +The surrounding models use unlinked shaders, where each model uses its own separate vertex, fragment, and potentially geometry shaders. Post processing, which is enabled by default, applies a specified visual effect to the results of model rendering. An optional wireframe mode may be enabled if supported by the device. -This sample can be configured at runtime through a debug GUI. This UI allows you to change various aspects of the scene that would be impractical to control dynamically using pipelines. UI drawing also demonstrates interoperability between pipelines within conventional render passes (which are used to render the UI) and shader objects with dynamic rendering (which are used to render the scene). +This sample can be configured at runtime through a debug GUI. +This UI allows you to change various aspects of the scene that would be impractical to control dynamically using pipelines. +UI drawing also demonstrates interoperability between pipelines within conventional render passes (which are used to render the UI) and shader objects with dynamic rendering (which are used to render the scene). -A plot of the last 2000 CPU frame times is displayed at the bottom of the screen. This plot shows the CPU impact of swapping shaders and state at runtime. +A plot of the last 2000 CPU frame times is displayed at the bottom of the screen. +This plot shows the CPU impact of swapping shaders and state at runtime. -Shader objects may only be used within `VK_KHR_dynamic_rendering` render passes. The [dynamic rendering sample](../dynamic_rendering) explains how to use that extension in more detail. The main parts relevant to drawing with shader objects are `vkCmdBeginRenderingKHR` and `vkCmdEndRenderingKHR`. +Shader objects may only be used within `VK_KHR_dynamic_rendering` render passes. +The link:../dynamic_rendering[dynamic rendering sample] explains how to use that extension in more detail. +The main parts relevant to drawing with shader objects are `vkCmdBeginRenderingKHR` and `vkCmdEndRenderingKHR`. -## Enabling the Extension +== Enabling the Extension -In order to use shader objects, both the `VK_EXT_shader_object` extension and the `shaderObject` feature need to be enabled on the `VkDevice`. This is accomplished by including `VK_EXT_SHADER_OBJECT_EXTENSION_NAME` in `VkDeviceCreateInfo`'s `ppEnabledExtensionNames` array, and also including a `VkPhysicalDeviceShaderObjectFeaturesEXT` structure with the `shaderObject` feature set to `VK_TRUE` in its `pNext` chain. +In order to use shader objects, both the `VK_EXT_shader_object` extension and the `shaderObject` feature need to be enabled on the `VkDevice`. +This is accomplished by including `VK_EXT_SHADER_OBJECT_EXTENSION_NAME` in ``VkDeviceCreateInfo``'s `ppEnabledExtensionNames` array, and also including a `VkPhysicalDeviceShaderObjectFeaturesEXT` structure with the `shaderObject` feature set to `VK_TRUE` in its `pNext` chain. In our case, these are handled through a common abstraction shared with the other samples. -```CPP +[,CPP] +---- add_device_extension(VK_EXT_SHADER_OBJECT_EXTENSION_NAME); -``` +---- -```CPP +[,CPP] +---- // Enable Shader Object auto &requestedShaderObject = gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT); requestedShaderObject.shaderObject = VK_TRUE; -``` +---- -## Shader Object Creation +== Shader Object Creation `VkShaderEXT` objects are created using the `vkCreateShadersEXT` function. -```CPP +[,CPP] +---- VkResult vkCreateShadersEXT( VkDevice device, uint32_t createInfoCount, const VkShaderCreateInfoEXT* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkShaderEXT* pShaders); -``` +---- -In this sample, shaders are abstracted into a `Shader` class. This class holds the created `VkShaderEXT` object, information needed for building and binding the shader. +In this sample, shaders are abstracted into a `Shader` class. +This class holds the created `VkShaderEXT` object, information needed for building and binding the shader. -```CPP +[,CPP] +---- class Shader { VkShaderStageFlagBits stage; @@ -88,21 +99,26 @@ class Shader // ... } -``` +---- The scene contains multiple models that each use multiple `Shader` objects. -## Linked Shaders +== Linked Shaders -If we know a combination of shaders will always be used together we can link the shaders together. Linked shaders allow the driver to perform cross stage optimizations that can potentially improve GPU performance when the shaders are executed. +If we know a combination of shaders will always be used together we can link the shaders together. +Linked shaders allow the driver to perform cross stage optimizations that can potentially improve GPU performance when the shaders are executed. One of the ways this sample uses linked shaders is for the skybox. -The skybox uses only a vertex and fragment shader. The vertex shader's stage is set to `VK_SHADER_STAGE_VERTEX_BIT` with its next stage set to only `VK_SHADER_STAGE_FRAGMENT_BIT`. The fragment shader's stage is set to `VK_SHADER_STAGE_FRAGMENT_BIT`, with no next stage. +The skybox uses only a vertex and fragment shader. +The vertex shader's stage is set to `VK_SHADER_STAGE_VERTEX_BIT` with its next stage set to only `VK_SHADER_STAGE_FRAGMENT_BIT`. +The fragment shader's stage is set to `VK_SHADER_STAGE_FRAGMENT_BIT`, with no next stage. -To draw with shader objects we need to create one `VkShaderEXT` object per shader stage. This means we need an instance of the `Shader` class for each shader stage. +To draw with shader objects we need to create one `VkShaderEXT` object per shader stage. +This means we need an instance of the `Shader` class for each shader stage. -```CPP +[,CPP] +---- // Create shaders with current and next stage bits and set the shaders GLSL shader data, descriptor sets, and push constants skybox_vert_shader = new Shader(VK_SHADER_STAGE_VERTEX_BIT, @@ -117,11 +133,13 @@ skybox_frag_shader = new Shader(VK_SHADER_STAGE_FRAGMENT_BIT, frag_shader_data, &descriptor_set_layouts[ShaderTypeBasic], &push_constant_ranges[ShaderTypeBasic]); -``` +---- -The `Shader` class require us to provide the entire contents of the `VkShaderCreateInfoEXT` structure including information to compile the GLSL. The constructor will compile the GLSL and fill out the `VkShaderCreateInfoEXT` structure for this `Shader`. +The `Shader` class require us to provide the entire contents of the `VkShaderCreateInfoEXT` structure including information to compile the GLSL. +The constructor will compile the GLSL and fill out the `VkShaderCreateInfoEXT` structure for this `Shader`. -```CPP +[,CPP] +---- ShaderObject::Shader::Shader(VkShaderStageFlagBits stage_, VkShaderStageFlags next_stage_, std::string shader_name_, @@ -155,18 +173,24 @@ ShaderObject::Shader::Shader(VkShaderStageFlagBits stage_, vk_shader_create_info.pPushConstantRanges = pPushConstantRange; vk_shader_create_info.pSpecializationInfo = nullptr; } -``` +---- -We want the skybox shaders to be linked, so we need to add the `VK_SHADER_CREATE_LINK_STAGE_BIT_EXT` flag to each shader's `VkShaderCreateInfoEXT`. The sample determines whether to add the flag at shader creation time depending on if `build_shader` or `build_linked_shaders` is called. `build_linked_shaders` will be called with pointers to the vertex and fragment shaders. The sample only supports linked vertex and fragment shaders and does not handle linking other shader stages such as geometry. +We want the skybox shaders to be linked, so we need to add the `VK_SHADER_CREATE_LINK_STAGE_BIT_EXT` flag to each shader's `VkShaderCreateInfoEXT`. +The sample determines whether to add the flag at shader creation time depending on if `build_shader` or `build_linked_shaders` is called. +`build_linked_shaders` will be called with pointers to the vertex and fragment shaders. +The sample only supports linked vertex and fragment shaders and does not handle linking other shader stages such as geometry. -```CPP +[,CPP] +---- // Set the fragment shader as linked to build them linked and build the shader build_linked_shaders(device, skybox_vert_shader, skybox_frag_shader); -``` +---- -To build the shaders we call `vkCreateShadersEXT` with all of the `VkShaderCreateInfoEXT`s for each of the linked shaders. The `build_linked_shaders` function adds the `VK_SHADER_CREATE_LINK_STAGE_BIT_EXT` flag to each `VkShaderCreateInfoEXT` struct, then calls `vkCreateShadersEXT` on both shaders. +To build the shaders we call `vkCreateShadersEXT` with all of the ``VkShaderCreateInfoEXT``s for each of the linked shaders. +The `build_linked_shaders` function adds the `VK_SHADER_CREATE_LINK_STAGE_BIT_EXT` flag to each `VkShaderCreateInfoEXT` struct, then calls `vkCreateShadersEXT` on both shaders. -```CPP +[,CPP] +---- void ShaderObject::build_linked_shaders(VkDevice device, ShaderObject::Shader *vert, ShaderObject::Shader *frag) { VkShaderCreateInfoEXT shader_create_infos[2]; @@ -201,23 +225,29 @@ void ShaderObject::build_linked_shaders(VkDevice device, ShaderObject::Shader *v vert->set_shader(shaderEXTs[0]); frag->set_shader(shaderEXTs[1]); } -``` +---- The skybox shaders can now be bound and used to draw. -## Unlinked Shaders +== Unlinked Shaders -Linking shaders is optional. For some use cases it may be advantageous to create shaders that are not linked. This allows arbitrary combinations of shaders to be used together at command buffer recording time, though you should keep in mind that the driver may be less able to optimize the shaders' GPU performance. +Linking shaders is optional. +For some use cases it may be advantageous to create shaders that are not linked. +This allows arbitrary combinations of shaders to be used together at command buffer recording time, though you should keep in mind that the driver may be less able to optimize the shaders' GPU performance. In order to use a given combination of unlinked shaders together, their input and output interfaces need to be compatible and their arrays of descriptor set layouts and push constants must be identical. -Some models in the scene use unlinked shaders. For the sake of convenience, this sample refers to these models as "material models". +Some models in the scene use unlinked shaders. +For the sake of convenience, this sample refers to these models as "material models". -Each of the material models binds different vertex, geometry, and fragment shaders. Each of the vertex shaders can be used with each of the geometry and fragment shaders. +Each of the material models binds different vertex, geometry, and fragment shaders. +Each of the vertex shaders can be used with each of the geometry and fragment shaders. -This sample lets you disable the geometry stage through the debug UI, so all of the vertex shaders support both geometry or fragment as a next stage. In order to allow either next stage, the vertex shaders are created with next stage flags of `VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT` and set the GLSL, descriptors, and push constants. +This sample lets you disable the geometry stage through the debug UI, so all of the vertex shaders support both geometry or fragment as a next stage. +In order to allow either next stage, the vertex shaders are created with next stage flags of `VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT` and set the GLSL, descriptors, and push constants. -```CPP +[,CPP] +---- // Create shader with current and next stage bits set the GLSL shader data, descriptor sets, and push constants material_vert_shaders.emplace_back( new Shader(VK_SHADER_STAGE_VERTEX_BIT, @@ -226,18 +256,21 @@ material_vert_shaders.emplace_back( shader_data, &descriptor_set_layouts[ShaderTypeMaterial], &push_constant_ranges[ShaderTypeMaterial])); -``` +---- -Then build the shader alone. This works similarly to the earlier skybox example, except that the `VkShaderEXT`s are created separately. +Then build the shader alone. +This works similarly to the earlier skybox example, except that the ``VkShaderEXT``s are created separately. -```CPP +[,CPP] +---- // Build shader build_shader(device, material_vert_shaders.back()); -``` +---- -The `build_shader` function is very similar to `build_linked_shaders` but only creates one `VkShaderEXT` object. +The `build_shader` function is very similar to `build_linked_shaders` but only creates one `VkShaderEXT` object. -```CPP +[,CPP] +---- void ShaderObject::build_shader(VkDevice device, ShaderObject::Shader *shader) { VkShaderEXT shaderEXT; @@ -252,29 +285,35 @@ void ShaderObject::build_shader(VkDevice device, ShaderObject::Shader *shader) shader->set_shader(shaderEXT); } -``` -## Binding Shaders +---- + +== Binding Shaders -`VkShaderEXT` objects need to be bound to the command buffer for use in subsequent `vkCmdDraw*` calls. One or more shader objects can be bound using `vkCmdBindShadersEXT`. +`VkShaderEXT` objects need to be bound to the command buffer for use in subsequent `vkCmdDraw*` calls. +One or more shader objects can be bound using `vkCmdBindShadersEXT`. -Before drawing the skybox we need to bind the relevant `VkShaderEXT` objects. This sample calls the `bind_shader` function, which simply binds the `Shader`'s `VkShaderEXT`. +Before drawing the skybox we need to bind the relevant `VkShaderEXT` objects. +This sample calls the `bind_shader` function, which simply binds the ``Shader``'s `VkShaderEXT`. -```CPP +[,CPP] +---- // Bind shaders for the skybox bind_shader(draw_cmd_buffer, skybox_vert_shader); bind_shader(draw_cmd_buffer, skybox_frag_shader); -``` +---- -```CPP +[,CPP] +---- void ShaderObject::bind_shader(VkCommandBuffer cmd_buffer, ShaderObject::Shader *shader) { vkCmdBindShadersEXT(cmd_buffer, 1, shader->get_stage(), shader->get_shader()); } -``` +---- The unlinked shaders are bound with multiple calls to `bind_shader`, one per each shader to bind. -```CPP +[,CPP] +---- void ShaderObject::bind_material_shader(VkCommandBuffer cmd_buffer, int shader_index) { CurrentShader &shader = current_material_shaders[shader_index]; @@ -284,71 +323,82 @@ void ShaderObject::bind_material_shader(VkCommandBuffer cmd_buffer, int shader_i bind_shader(cmd_buffer, material_geo_shaders[shader.geo]); bind_shader(cmd_buffer, material_frag_shaders[shader.frag]); } -``` +---- It would be equally valid for the `Shader` class to be designed to bind all of the `VkShaderEXT` objects in a single `vkCmdBindShadersEXT` call. -## Unbinding Shaders +== Unbinding Shaders + Bound shaders can be unbound by calling `vkCmdBindShadersEXT` with `pShaders` set to `nullptr` and `pStages` set to an array of stages to unbind. This sample uses a geometry shader for only some draws, so we need to unbind the geometry shader before draws that don't need them. -```CPP +[,CPP] +---- // Unbind geometry shader by binding nullptr to the geometry stage VkShaderStageFlagBits geo_stage = VK_SHADER_STAGE_GEOMETRY_BIT; vkCmdBindShadersEXT(draw_cmd_buffer, 1, &geo_stage, nullptr); -``` +---- -Before drawing with shader objects, applications are required to bind either `nullptr` or a valid `VkShaderEXT` object for every shader stage enabled on the device. A valid geometry stage shader will not be bound before the first draw so we need to bind `nullptr` to it. The same code as above can be used. +Before drawing with shader objects, applications are required to bind either `nullptr` or a valid `VkShaderEXT` object for every shader stage enabled on the device. +A valid geometry stage shader will not be bound before the first draw so we need to bind `nullptr` to it. +The same code as above can be used. This sample does not enable the `tessellationShader` device feature, so it's not necessary to bind anything to either tessellation stage. -## State Setting and Drawing +== State Setting and Drawing -The [Setting State](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#shaders-objects-state) subsection of the [Shader Objects](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#shaders-objects) section of the Vulkan specification lists the graphics state that needs to be set on a command buffer before `vkCmdDraw*` can be called with graphics shader objects bound. +The https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#shaders-objects-state[Setting State] subsection of the https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#shaders-objects[Shader Objects] section of the Vulkan specification lists the graphics state that needs to be set on a command buffer before `vkCmdDraw*` can be called with graphics shader objects bound. This sample demonstrates one possible way to take advantage of these rules to minimize calls into the Vulkan driver. -All of the required and common state for this sample, such as vertex input binding descriptions, is set in `set_initial_state` before any draw calls. State specific to particular draws is set on the command buffer as needed. +All of the required and common state for this sample, such as vertex input binding descriptions, is set in `set_initial_state` before any draw calls. +State specific to particular draws is set on the command buffer as needed. For the skybox, we disable culling and writing to depth. -```CPP +[,CPP] +---- // Disable depth write and use cull mode none to draw skybox vkCmdSetCullModeEXT(draw_cmd_buffer, VK_CULL_MODE_NONE); vkCmdSetDepthWriteEnableEXT(draw_cmd_buffer, VK_FALSE); -``` +---- Then we bind the descriptor sets and push constants for the draw. -```CPP +[,CPP] +---- // Bind descriptors and push constants for the skybox draw glm::mat4 model_matrix = glm::mat4(1.0f); vkCmdBindDescriptorSets(draw_cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout[ShaderTypeBasic], 0, 1, &descriptor_sets[ShaderTypeBasic], 0, nullptr); vkCmdPushConstants(draw_cmd_buffer, pipeline_layout[ShaderTypeBasic], VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(BasicPushConstant), &model_matrix); -``` +---- Finally, we bind the shaders and draw the model. -```CPP +[,CPP] +---- // Bind shaders for the skybox bind_shader(draw_cmd_buffer, skybox_vert_shader); bind_shader(draw_cmd_buffer, skybox_frag_shader); // Draw the skybox model draw_model(skybox, draw_cmd_buffer); -``` +---- The models that will be drawn next require different state than the skybox. -```CPP +[,CPP] +---- vkCmdSetCullModeEXT(draw_cmd_buffer, VK_CULL_MODE_BACK_BIT); vkCmdSetDepthWriteEnableEXT(draw_cmd_buffer, VK_TRUE); -``` +---- -Unlinked shaders are bound the same way as linked shaders. First we set state, bind descriptor sets, push constants, and finally bind shaders and draw the model. +Unlinked shaders are bound the same way as linked shaders. +First we set state, bind descriptor sets, push constants, and finally bind shaders and draw the model. -```CPP +[,CPP] +---- // Update and push constants for cube 1 material_push_constant.model = glm::translate(glm::vec3(1.2f, -1.f, 0)) * glm::rotate((float) elapsed_time, glm::vec3(0, 1, 0)) * glm::scale(glm::vec3(0.05f)); vkCmdPushConstants(draw_cmd_buffer, pipeline_layout[ShaderTypeMaterial], @@ -360,43 +410,54 @@ bind_material_shader(draw_cmd_buffer, 2); // Draw cube 1 draw_model(cube, draw_cmd_buffer); -``` +---- -After rendering the skybox, terrain, and all other models, an optional post processing effect is applied to the intermediate image. The final result is drawn to the screen. +After rendering the skybox, terrain, and all other models, an optional post processing effect is applied to the intermediate image. +The final result is drawn to the screen. -## Options +== Options -The debug UI allows various aspects of rendering to be controlled dynamically. It can change shaders per model, both color and depth output formats, and the post processing effect. This level of dynamism would be impractical to achieve using pipelines because the tens of thousands of permutations of state would potentially each require their own pipeline. Shader objects, on the other hand, support this kind of application architecture naturally. +The debug UI allows various aspects of rendering to be controlled dynamically. +It can change shaders per model, both color and depth output formats, and the post processing effect. +This level of dynamism would be impractical to achieve using pipelines because the tens of thousands of permutations of state would potentially each require their own pipeline. +Shader objects, on the other hand, support this kind of application architecture naturally. -## Emulation Layer +== Emulation Layer -The Vulkan SDK ships with an [emulation layer](https://github.com/KhronosGroup/Vulkan-ExtensionLayer/blob/main/docs/shader_object_layer.md) that allows `VK_EXT_shader_object` to be used on drivers that don't yet have native support for the extension. The layer is useful for applications that want to use shader objects without implementing their own pipeline based fallback path for older drivers without native support. The layer can be shipped with your application, and it will disable itself if a native implementation of `VK_EXT_shader_object` exists in the driver. +The Vulkan SDK ships with an https://github.com/KhronosGroup/Vulkan-ExtensionLayer/blob/main/docs/shader_object_layer.md[emulation layer] that allows `VK_EXT_shader_object` to be used on drivers that don't yet have native support for the extension. +The layer is useful for applications that want to use shader objects without implementing their own pipeline based fallback path for older drivers without native support. +The layer can be shipped with your application, and it will disable itself if a native implementation of `VK_EXT_shader_object` exists in the driver. The emulation layer can be enabled by adding `VK_LAYER_KHRONOS_shader_object` to `ppEnabledLayerNames` in `VkDeviceCreateInfo`. -The sample framework already has an existing abstraction normally used for enabling the validation layer. This sample repurposes this mechanism to instead load the emulation layer. +The sample framework already has an existing abstraction normally used for enabling the validation layer. +This sample repurposes this mechanism to instead load the emulation layer. -```CPP +[,CPP] +---- const std::vector ShaderObject::get_validation_layers() { return {"VK_LAYER_KHRONOS_shader_object"}; } -``` +---- -Because you can't rely on the Vulkan SDK to be installed on a user's system, this sample's method of loading the layer is not suitable for a real application. Instead, the layer needs to be shipped with the application. +Because you can't rely on the Vulkan SDK to be installed on a user's system, this sample's method of loading the layer is not suitable for a real application. +Instead, the layer needs to be shipped with the application. -There are various ways to ship the layer with an application. One method is to copy the layer's `VkLayer_khronos_shader_object.dll` and `VkLayer_khronos_shader_object.json` from the Vulkan SDK to the same directory as your application's executable and then append the path to these files to the `VK_LAYER_PATH` environment variable before your first call into the Vulkan API. +There are various ways to ship the layer with an application. +One method is to copy the layer's `VkLayer_khronos_shader_object.dll` and `VkLayer_khronos_shader_object.json` from the Vulkan SDK to the same directory as your application's executable and then append the path to these files to the `VK_LAYER_PATH` environment variable before your first call into the Vulkan API. -However the layer is packaged, the Vulkan Loader is always responsible for loading the layer. Because of this the layer's files will always need to be somewhere accessible to the loader. +However the layer is packaged, the Vulkan Loader is always responsible for loading the layer. +Because of this the layer's files will always need to be somewhere accessible to the loader. -## Additional Resources +== Additional Resources -* [You Can Use Vulkan Without Pipelines Today](https://www.khronos.org/blog/you-can-use-vulkan-without-pipelines-today) -* [Extension Proposal](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/proposals/VK_EXT_shader_object.adoc) -* [Specification](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#shaders-objects) -* [Emulation Layer](https://github.com/KhronosGroup/Vulkan-ExtensionLayer/blob/main/docs/shader_object_layer.md) +* https://www.khronos.org/blog/you-can-use-vulkan-without-pipelines-today[You Can Use Vulkan Without Pipelines Today] +* https://github.com/KhronosGroup/Vulkan-Docs/blob/main/proposals/VK_EXT_shader_object.adoc[Extension Proposal] +* https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#shaders-objects[Specification] +* https://github.com/KhronosGroup/Vulkan-ExtensionLayer/blob/main/docs/shader_object_layer.md[Emulation Layer] -## Conclusion +== Conclusion Shader objects can be an invaluable tool for simplifying shader and state management in highly dynamic application architectures which don't lend themselves to practical implementation using pipelines. diff --git a/shaders/dynamic_line_rasterization/base.frag b/shaders/dynamic_line_rasterization/base.frag new file mode 100644 index 000000000..c0f52d12e --- /dev/null +++ b/shaders/dynamic_line_rasterization/base.frag @@ -0,0 +1,27 @@ +#version 450 +/* 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. + */ + +layout (location = 0) out vec4 outFragColor; +layout( push_constant ) uniform Push_Constants{ + vec4 color; +} pushConstant; + + +void main() { + outFragColor = pushConstant.color; +} diff --git a/framework/core/hpp_vulkan_resource.cpp b/shaders/dynamic_line_rasterization/base.vert similarity index 50% rename from framework/core/hpp_vulkan_resource.cpp rename to shaders/dynamic_line_rasterization/base.vert index d8945d093..327223fea 100644 --- a/framework/core/hpp_vulkan_resource.cpp +++ b/shaders/dynamic_line_rasterization/base.vert @@ -1,4 +1,5 @@ -/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. +#version 450 +/* Copyright (c) 2023, Mobica Limited * * SPDX-License-Identifier: Apache-2.0 * @@ -15,27 +16,20 @@ * limitations under the License. */ -#include +layout (location = 0) in vec3 inPos; -#include - -namespace vkb -{ -namespace core -{ -namespace detail -{ -void set_debug_name(const HPPDevice *device, vk::ObjectType object_type, uint64_t handle, const char *debug_name) +layout (binding = 0) uniform Ubo { - if (!debug_name || *debug_name == '\0' || !device) - { - // Can't set name, or no point in setting an empty name - return; - } + mat4 projection; + mat4 view; + mat4 model; +} ubo; - device->get_debug_utils().set_debug_name(device->get_handle(), object_type, handle, debug_name); -} -} // namespace detail -} // namespace core -} // namespace vkb \ No newline at end of file +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() { + gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz - vec3(0.0f, 1.0f, 0.0f), 1.0f); +} diff --git a/shaders/dynamic_line_rasterization/grid.frag b/shaders/dynamic_line_rasterization/grid.frag new file mode 100644 index 000000000..3936be542 --- /dev/null +++ b/shaders/dynamic_line_rasterization/grid.frag @@ -0,0 +1,58 @@ +#version 450 +/* 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. + */ + +layout(location = 0) in vec3 nearPoint; +layout(location = 1) in vec3 farPoint; +layout(location = 2) in mat4 view; +layout(location = 6) in mat4 projection; +layout(location = 0) out vec4 outColor; + +vec4 grid(vec3 pos) { + vec2 coord = pos.xz; + vec2 derivative = fwidth(coord); + vec2 grid = abs(fract(coord - 0.5) - 0.5) / derivative; + float line = min(grid.x, grid.y); + float minimumz = min(derivative.y, 1); + float minimumx = min(derivative.x, 1); + vec4 color = vec4(0.5, 0.5, 0.5, 1.0 - min(line, 1.0)); + + if(abs(pos.x) < minimumx) + color.y = 1; + if(abs(pos.z) < minimumz) + color.x = 1; + return color; +} + +float fadeFactor(vec3 pos) { + float z = (projection * view * vec4(pos.xyz, 1.0)).z; + // Empirical values are used to determine when to cut off the grid before moire patterns become visible. + return z * 6 - 0.5; +} + +void main() { + float t = -nearPoint.y / (farPoint.y - nearPoint.y); + vec3 pos = nearPoint + t * (farPoint - nearPoint); + + // Display only the lower plane + if(t < 1) { + vec4 gridColor = grid(pos); + outColor = vec4(gridColor.xyz, gridColor.w * fadeFactor(pos)); + } + else + outColor = vec4(0.0, 0.0, 0.0, 0.0); +} diff --git a/shaders/dynamic_line_rasterization/grid.vert b/shaders/dynamic_line_rasterization/grid.vert new file mode 100644 index 000000000..e3dd87d00 --- /dev/null +++ b/shaders/dynamic_line_rasterization/grid.vert @@ -0,0 +1,55 @@ +#version 450 +/* 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. + */ + +layout(binding = 0) uniform Ubo { + mat4 projection; + mat4 view; + mat4 model; +} ubo; + +layout(location = 0) out vec3 nearPoint; +layout(location = 1) out vec3 farPoint; +layout(location = 2) out mat4 view; +layout(location = 6) out mat4 projection; + + +vec3 unprojectPoint(float x, float y, float z, mat4 viewProjectionInverse) { + vec4 clipSpacePos = vec4(x, y, z, 1.0); + vec4 eyeSpacePos = viewProjectionInverse * clipSpacePos; + return eyeSpacePos.xyz / eyeSpacePos.w; +} + +vec3 gridPlane[6] = vec3[]( + vec3(1, 1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), + vec3(-1, -1, 0), vec3(1, 1, 0), vec3(1, -1, 0) +); + +void main() { + vec3 pos = gridPlane[gl_VertexIndex].xyz; + mat4 viewProjectionInverse = inverse(ubo.projection * ubo.view); + + nearPoint = unprojectPoint(pos.x, pos.y, 0.0, viewProjectionInverse); + farPoint = unprojectPoint(pos.x, pos.y, 1.0, viewProjectionInverse); + + view = ubo.view; + projection = ubo.projection; + + gl_Position = vec4(pos, 1.0); + + +} diff --git a/third_party/vulkan b/third_party/vulkan index 9e61870ec..a0c76b4ef 160000 --- a/third_party/vulkan +++ b/third_party/vulkan @@ -1 +1 @@ -Subproject commit 9e61870ecbd32514113b467e0a0c46f60ed222c7 +Subproject commit a0c76b4ef76e219483755ff61dce6b67ff79f24b