/* Copyright (c) 2019-2025, Arm Limited and Contributors * Copyright (c) 2021-2025, 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. */ #pragma once #include "common/hpp_utils.h" #include "core/debug.h" #include "gui.h" #include "hpp_gltf_loader.h" #include "platform/application.h" #include "platform/window.h" #include "rendering/hpp_render_pipeline.h" #include "stats/hpp_stats.h" #if defined(PLATFORM__MACOS) # include #endif namespace vkb { /** * @mainpage Overview of the framework * * @section initialization Initialization * * @subsection platform_init Platform initialization * The lifecycle of a Vulkan sample starts by instantiating the correct Platform * (e.g. WindowsPlatform) and then calling initialize() on it, which sets up * the windowing system and logging. Then it calls the parent Platform::initialize(), * which takes ownership of the active application. It's the platforms responsibility * to then call VulkanSample::prepare() to prepare the vulkan sample when it is ready. * * @subsection sample_init Sample initialization * The preparation step is divided in two steps, one in VulkanSample and the other in the * specific sample, such as SurfaceRotation. * VulkanSample::prepare() contains functions that do not require customization, * including creating a Vulkan instance, the surface and getting physical devices. * The prepare() function for the specific sample completes the initialization, including: * - setting enabled Stats * - creating the Device * - creating the Swapchain * - creating the RenderContext (or child class) * - preparing the RenderContext * - loading the sg::Scene * - creating the RenderPipeline with ShaderModule (s) * - creating the sg::Camera * - creating the Gui * * @section frame_rendering Frame rendering * * @subsection update Update function * Rendering happens in the update() function. Each sample can override it, e.g. * to recreate the Swapchain in SwapchainImages when required by user input. * Typically a sample will then call VulkanSample::update(). * * @subsection rendering Rendering * A series of steps are performed, some of which can be customized (it will be * highlighted when that's the case): * * - calling sg::Script::update() for all sg::Script (s) * - beginning a frame in RenderContext (does the necessary waiting on fences and * acquires an core::Image) * - requesting a CommandBuffer * - updating Stats and Gui * - getting an active RenderTarget constructed by the factory function of the RenderFrame * - setting up barriers for color and depth, note that these are only for the default RenderTarget * - calling VulkanSample::draw_swapchain_renderpass (see below) * - setting up a barrier for the Swapchain transition to present * - submitting the CommandBuffer and end the Frame (present) * * @subsection draw_swapchain Draw swapchain renderpass * The function starts and ends a RenderPass which includes setting up viewport, scissors, * blend state (etc.) and calling draw_scene. * Note that RenderPipeline::draw is not virtual in RenderPipeline, but internally it calls * Subpass::draw for each Subpass, which is virtual and can be customized. * * @section framework_classes Main framework classes * * - RenderContext * - RenderFrame * - RenderTarget * - RenderPipeline * - ShaderModule * - ResourceCache * - BufferPool * - Core classes: Classes in vkb::core wrap Vulkan objects for indexing and hashing. */ class RenderPipeline; class Stats; namespace core { class HPPDevice; class HPPInstance; class HPPPhysicalDevice; } // namespace core namespace rendering { class HPPRenderContext; class HPPRenderTarget; } // namespace rendering namespace stats { class HPPStats; } template class VulkanSample : public vkb::Application { using Parent = vkb::Application; /// /// PUBLIC INTERFACE /// public: VulkanSample() = default; ~VulkanSample() override; using PhysicalDeviceType = typename std::conditional::type; using RenderContextType = typename std::conditional::type; using RenderPipelineType = typename std::conditional::type; using RenderTargetType = typename std::conditional::type; using SceneType = typename std::conditional::type; using StatsType = typename std::conditional::type; using Extent2DType = typename std::conditional::type; using LayerSettingType = typename std::conditional::type; using SurfaceFormatType = typename std::conditional::type; using SurfaceType = typename std::conditional::type; Configuration &get_configuration(); RenderContextType &get_render_context(); RenderContextType const &get_render_context() const; bool has_render_context() const; /// /// PROTECTED VIRTUAL INTERFACE /// protected: // from Application void input_event(const InputEvent &input_event) override; void finish() override; bool resize(uint32_t width, uint32_t height) override; /** * @brief Create the Vulkan device used by this sample * @note Can be overridden to implement custom device creation */ virtual std::unique_ptr> create_device(PhysicalDeviceType &gpu); /** * @brief Create the Vulkan instance used by this sample * @note Can be overridden to implement custom instance creation */ virtual std::unique_ptr> create_instance(); /** * @brief Override this to customise the creation of the render_context */ virtual void create_render_context(); /** * @brief Prepares the render target and draws to it, calling draw_renderpass * @param command_buffer The command buffer to record the commands to * @param render_target The render target that is being drawn to */ virtual void draw(vkb::core::CommandBuffer &command_buffer, RenderTargetType &render_target); /** * @brief Samples should override this function to draw their interface */ virtual void draw_gui(); /** * @brief Starts the render pass, executes the render pipeline, and then ends the render pass * @param command_buffer The command buffer to record the commands to * @param render_target The render target that is being drawn to */ virtual void draw_renderpass(vkb::core::CommandBuffer &command_buffer, RenderTargetType &render_target); /** * @brief Override this to customise the creation of the swapchain and render_context */ virtual void prepare_render_context(); /** * @brief Triggers the render pipeline, it can be overridden by samples to specialize their rendering logic * @param command_buffer The command buffer to record the commands to */ virtual void render(vkb::core::CommandBuffer &command_buffer); /** * @brief Request features from the gpu based on what is supported */ virtual void request_gpu_features(PhysicalDeviceType &gpu); /** * @brief Resets the stats view max values for high demanding configs * Should be overridden by the samples since they * know which configuration is resource demanding */ virtual void reset_stats_view(); /** * @brief Updates the debug window, samples can override this to insert their own data elements */ virtual void update_debug_window(); /// /// PROTECTED INTERFACE /// /** * @brief Add a sample-specific device extension * @param extension The extension name * @param optional (Optional) Whether the extension is optional */ void add_device_extension(const char *extension, bool optional = false); /** * @brief Add a sample-specific instance extension * @param extension The extension name * @param optional (Optional) Whether the extension is optional */ void add_instance_extension(const char *extension, bool optional = false); /** * @brief Add a sample-specific instance layer * @param layer The layer name * @param optional (Optional) Whether the extension is optional */ void add_instance_layer(const char *layer, bool optional = false); /** * @brief Add a sample-specific layer setting * @param layerSetting The layer setting */ void add_layer_setting(LayerSettingType const &layerSetting); void create_gui(const Window &window, StatsType const *stats = nullptr, const float font_size = 21.0f, bool explicit_update = false); /** * @brief A helper to create a render context */ void create_render_context(const std::vector &surface_priority_list); vkb::core::Device &get_device(); vkb::core::Device const &get_device() const; vkb::Gui &get_gui(); vkb::Gui const &get_gui() const; vkb::core::Instance &get_instance(); vkb::core::Instance const &get_instance() const; RenderPipelineType &get_render_pipeline(); RenderPipelineType const &get_render_pipeline() const; SceneType &get_scene(); StatsType &get_stats(); SurfaceType get_surface() const; std::vector &get_surface_priority_list(); std::vector const &get_surface_priority_list() const; bool has_device() const; bool has_instance() const; bool has_gui() const; bool has_render_pipeline() const; bool has_scene(); /** * @brief Loads the scene * * @param path The path of the glTF file */ void load_scene(const std::string &path); /** * @brief Additional sample initialization */ bool prepare(const ApplicationOptions &options) override; /** * @brief Set the Vulkan API version to request at instance creation time */ void set_api_version(uint32_t requested_api_version); /** * @brief Sets whether or not the first graphics queue should have higher priority than other queues. * Very specific feature which is used by async compute samples. * Needs to be called before prepare(). * @param enable If true, present queue will have prio 1.0 and other queues have prio 0.5. * Default state is false, where all queues have 0.5 priority. */ void set_high_priority_graphics_queue_enable(bool enable); void set_render_context(std::unique_ptr &&render_context); void set_render_pipeline(std::unique_ptr &&render_pipeline); /** * @brief Main loop sample events */ void update(float delta_time) override; /** * @brief Update GUI * @param delta_time */ void update_gui(float delta_time); /** * @brief Update scene * @param delta_time */ void update_scene(float delta_time); /** * @brief Update counter values * @param delta_time */ void update_stats(float delta_time); /** * @brief Set viewport and scissor state in command buffer for a given extent */ static void set_viewport_and_scissor(vkb::core::CommandBuffer const &command_buffer, Extent2DType const &extent); /// /// PRIVATE INTERFACE /// private: void create_render_context_impl(const std::vector &surface_priority_list); void draw_impl(vkb::core::CommandBufferCpp &command_buffer, vkb::rendering::HPPRenderTarget &render_target); void draw_renderpass_impl(vkb::core::CommandBufferCpp &command_buffer, vkb::rendering::HPPRenderTarget &render_target); void render_impl(vkb::core::CommandBufferCpp &command_buffer); static void set_viewport_and_scissor_impl(vkb::core::CommandBufferCpp const &command_buffer, vk::Extent2D const &extent); /** * @brief Get sample-specific device extensions. * * @return Map of device extensions and whether or not they are optional. Default is empty map. */ std::unordered_map const &get_device_extensions() const; /** * @brief Get sample-specific instance extensions. * * @return Map of instance extensions and whether or not they are optional. Default is empty map. */ std::unordered_map const &get_instance_extensions() const; /** * @brief Get sample-specific instance layers. * * @return Map of instance layers and whether or not they are optional. Default is empty map. */ std::unordered_map const &get_instance_layers() const; /** * @brief Get sample-specific layer settings. * * @return Vector of layer settings. Default is empty vector. */ std::vector const &get_layer_settings() const; /// /// PRIVATE MEMBERS /// private: /** * @brief The Vulkan instance */ std::unique_ptr instance; /** * @brief The Vulkan device */ std::unique_ptr device; /** * @brief Context used for rendering, it is responsible for managing the frames and their underlying images */ std::unique_ptr render_context; /** * @brief Pipeline used for rendering, it should be set up by the concrete sample */ std::unique_ptr render_pipeline; /** * @brief Holds all scene information */ std::unique_ptr scene; std::unique_ptr gui; std::unique_ptr stats; static constexpr float STATS_VIEW_RESET_TIME{10.0f}; // 10 seconds /** * @brief The Vulkan surface */ vk::SurfaceKHR surface; /** * @brief A list of surface formats in order of priority (vector[0] has high priority, vector[size-1] has low priority) */ std::vector surface_priority_list = { {vk::Format::eR8G8B8A8Srgb, vk::ColorSpaceKHR::eSrgbNonlinear}, {vk::Format::eB8G8R8A8Srgb, vk::ColorSpaceKHR::eSrgbNonlinear}}; /** * @brief The configuration of the sample */ Configuration configuration{}; /** @brief Set of device extensions to be enabled for this example and whether they are optional (must be set in the derived constructor) */ std::unordered_map device_extensions; /** @brief Set of instance extensions to be enabled for this example and whether they are optional (must be set in the derived constructor) */ std::unordered_map instance_extensions; /** @brief Set of instance layers to be enabled for this example and whether they are optional (must be set in the derived constructor) */ std::unordered_map instance_layers; /** @brief Vector of layer settings to be enabled for this example (must be set in the derived constructor) */ std::vector layer_settings; /** @brief The Vulkan API version to request for this sample at instance creation time */ uint32_t api_version = VK_API_VERSION_1_1; /** @brief Whether or not we want a high priority graphics queue. */ bool high_priority_graphics_queue{false}; std::unique_ptr debug_utils; }; using VulkanSampleC = VulkanSample; using VulkanSampleCpp = VulkanSample; // Member function definitions template inline VulkanSample::~VulkanSample() { if (device) { device->get_handle().waitIdle(); } scene.reset(); stats.reset(); gui.reset(); render_context.reset(); device.reset(); if (surface) { instance->get_handle().destroySurfaceKHR(surface); } instance.reset(); } template inline void VulkanSample::add_device_extension(const char *extension, bool optional) { device_extensions[extension] = optional; } template inline void VulkanSample::add_instance_extension(const char *extension, bool optional) { instance_extensions[extension] = optional; } template inline void VulkanSample::add_layer_setting(LayerSettingType const &layerSetting) { if constexpr (bindingType == BindingType::Cpp) { layer_settings.push_back(layerSetting); } else { layer_settings.push_back(reinterpret_cast(layerSetting)); } } template inline std::unique_ptr> VulkanSample::create_device(PhysicalDeviceType &gpu) { if constexpr (bindingType == BindingType::Cpp) { return std::make_unique(gpu, surface, std::move(debug_utils), get_device_extensions()); } else { return std::make_unique(gpu, static_cast(surface), std::unique_ptr(reinterpret_cast(debug_utils.release())), get_device_extensions()); } } template inline std::unique_ptr> VulkanSample::create_instance() { return std::make_unique>(get_name(), get_instance_extensions(), get_instance_layers(), get_layer_settings(), api_version); } template inline void VulkanSample::create_render_context() { create_render_context_impl(surface_priority_list); } template inline void VulkanSample::create_render_context(const std::vector &surface_priority_list) { if constexpr (bindingType == BindingType::Cpp) { create_render_context_impl(surface_priority_list); } else { create_render_context_impl(reinterpret_cast const &>(surface_priority_list)); } } template void VulkanSample::create_render_context_impl(const std::vector &surface_priority_list) { #ifdef VK_USE_PLATFORM_ANDROID_KHR vk::PresentModeKHR present_mode = (window->get_properties().vsync == Window::Vsync::OFF) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; std::vector present_mode_priority_list{vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eImmediate}; #else vk::PresentModeKHR present_mode = (window->get_properties().vsync == Window::Vsync::ON) ? vk::PresentModeKHR::eFifo : vk::PresentModeKHR::eMailbox; std::vector present_mode_priority_list{vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eImmediate, vk::PresentModeKHR::eFifo}; #endif render_context = std::make_unique(*device, surface, *window, present_mode, present_mode_priority_list, surface_priority_list); } template inline void VulkanSample::draw(vkb::core::CommandBuffer &command_buffer, RenderTargetType &render_target) { if constexpr (bindingType == BindingType::Cpp) { draw_impl(command_buffer, render_target); } else { draw_impl(reinterpret_cast(command_buffer), reinterpret_cast(render_target)); } } template inline void VulkanSample::draw_impl(vkb::core::CommandBufferCpp &command_buffer, vkb::rendering::HPPRenderTarget &render_target) { auto &views = render_target.get_views(); { // Image 0 is the swapchain vkb::common::HPPImageMemoryBarrier memory_barrier{}; memory_barrier.old_layout = vk::ImageLayout::eUndefined; memory_barrier.new_layout = vk::ImageLayout::eColorAttachmentOptimal; memory_barrier.src_access_mask = {}; memory_barrier.dst_access_mask = vk::AccessFlagBits::eColorAttachmentWrite; memory_barrier.src_stage_mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; memory_barrier.dst_stage_mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; command_buffer.image_memory_barrier(views[0], memory_barrier); render_target.set_layout(0, memory_barrier.new_layout); // Skip 1 as it is handled later as a depth-stencil attachment for (size_t i = 2; i < views.size(); ++i) { command_buffer.image_memory_barrier(views[i], memory_barrier); render_target.set_layout(static_cast(i), memory_barrier.new_layout); } } { vkb::common::HPPImageMemoryBarrier memory_barrier{}; memory_barrier.old_layout = vk::ImageLayout::eUndefined; memory_barrier.new_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; memory_barrier.src_access_mask = {}; memory_barrier.dst_access_mask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; memory_barrier.src_stage_mask = vk::PipelineStageFlagBits::eTopOfPipe; memory_barrier.dst_stage_mask = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests; command_buffer.image_memory_barrier(views[1], memory_barrier); render_target.set_layout(1, memory_barrier.new_layout); } // draw_renderpass is a virtual function, thus we have to call that, instead of directly calling draw_renderpass_impl! if constexpr (bindingType == BindingType::Cpp) { draw_renderpass(command_buffer, render_target); } else { draw_renderpass(reinterpret_cast(command_buffer), reinterpret_cast(render_target)); } { vkb::common::HPPImageMemoryBarrier memory_barrier{}; memory_barrier.old_layout = vk::ImageLayout::eColorAttachmentOptimal; memory_barrier.new_layout = vk::ImageLayout::ePresentSrcKHR; memory_barrier.src_access_mask = vk::AccessFlagBits::eColorAttachmentWrite; memory_barrier.src_stage_mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; memory_barrier.dst_stage_mask = vk::PipelineStageFlagBits::eBottomOfPipe; command_buffer.image_memory_barrier(views[0], memory_barrier); render_target.set_layout(0, memory_barrier.new_layout); } } template inline void VulkanSample::draw_gui() { } template inline void VulkanSample::draw_renderpass(vkb::core::CommandBuffer &command_buffer, RenderTargetType &render_target) { if constexpr (bindingType == BindingType::Cpp) { draw_renderpass_impl(command_buffer, render_target); } else { draw_renderpass_impl(reinterpret_cast(command_buffer), reinterpret_cast(render_target)); } } template inline void VulkanSample::draw_renderpass_impl(vkb::core::CommandBufferCpp &command_buffer, vkb::rendering::HPPRenderTarget &render_target) { set_viewport_and_scissor_impl(command_buffer, render_target.get_extent()); // render is a virtual function, thus we have to call that, instead of directly calling render_impl! if constexpr (bindingType == BindingType::Cpp) { render(command_buffer); } else { render(reinterpret_cast(command_buffer)); } if (gui) { gui->draw(command_buffer); } command_buffer.get_handle().endRenderPass(); } template inline void VulkanSample::finish() { Parent::finish(); if (device) { device->get_handle().waitIdle(); } } template inline Configuration &VulkanSample::get_configuration() { return configuration; } template inline vkb::core::Device const &VulkanSample::get_device() const { if constexpr (bindingType == BindingType::Cpp) { return *device; } else { return reinterpret_cast(*device); } } template inline vkb::core::Device &VulkanSample::get_device() { if constexpr (bindingType == BindingType::Cpp) { return *device; } else { return reinterpret_cast(*device); } } template inline std::unordered_map const &VulkanSample::get_device_extensions() const { return device_extensions; } template inline vkb::Gui &VulkanSample::get_gui() { if constexpr (bindingType == BindingType::Cpp) { return *gui; } else { return reinterpret_cast(*gui); } } template inline vkb::Gui const &VulkanSample::get_gui() const { if constexpr (bindingType == BindingType::Cpp) { return *gui; } else { return reinterpret_cast(*gui); } } template inline std::vector::SurfaceFormatType> &VulkanSample::get_surface_priority_list() { if constexpr (bindingType == BindingType::Cpp) { return surface_priority_list; } else { return reinterpret_cast &>(surface_priority_list); } } template inline std::vector::SurfaceFormatType> const &VulkanSample::get_surface_priority_list() const { if constexpr (bindingType == BindingType::Cpp) { return surface_priority_list; } else { return reinterpret_cast const &>(surface_priority_list); } } template inline vkb::core::Instance &VulkanSample::get_instance() { if constexpr (bindingType == BindingType::Cpp) { return *instance; } else { return reinterpret_cast(*instance); } } template inline vkb::core::Instance const &VulkanSample::get_instance() const { if constexpr (bindingType == BindingType::Cpp) { return *instance; } else { return reinterpret_cast(*instance); } } template inline std::unordered_map const &VulkanSample::get_instance_extensions() const { return instance_extensions; } template inline std::unordered_map const &VulkanSample::get_instance_layers() const { return instance_layers; } template inline void VulkanSample::add_instance_layer(const char *layer, bool optional) { instance_layers[layer] = optional; } template inline std::vector::LayerSettingType> const &VulkanSample::get_layer_settings() const { if constexpr (bindingType == BindingType::Cpp) { return layer_settings; } else { return reinterpret_cast const &>(layer_settings); } } template inline typename VulkanSample::RenderContextType const &VulkanSample::get_render_context() const { assert(render_context && "Render context is not valid"); if constexpr (bindingType == BindingType::Cpp) { return reinterpret_cast(*render_context); } else { return *render_context; } } template inline typename VulkanSample::RenderContextType &VulkanSample::get_render_context() { assert(render_context && "Render context is not valid"); if constexpr (bindingType == BindingType::Cpp) { return *render_context; } else { return reinterpret_cast(*render_context); } } template inline typename VulkanSample::RenderPipelineType const &VulkanSample::get_render_pipeline() const { assert(render_pipeline && "Render pipeline was not created"); if constexpr (bindingType == BindingType::Cpp) { return *render_pipeline; } else { return reinterpret_cast(*render_pipeline); } } template inline typename VulkanSample::RenderPipelineType &VulkanSample::get_render_pipeline() { assert(render_pipeline && "Render pipeline was not created"); if constexpr (bindingType == BindingType::Cpp) { return *render_pipeline; } else { return reinterpret_cast(*render_pipeline); } } template inline typename VulkanSample::SceneType &VulkanSample::get_scene() { assert(scene && "Scene not loaded"); if constexpr (bindingType == BindingType::Cpp) { return *scene; } else { return reinterpret_cast(*scene); } } template inline typename VulkanSample::StatsType &VulkanSample::get_stats() { if constexpr (bindingType == BindingType::Cpp) { return *stats; } else { return reinterpret_cast(*stats); } } template inline typename VulkanSample::SurfaceType VulkanSample::get_surface() const { if constexpr (bindingType == BindingType::Cpp) { return surface; } else { return static_cast(surface); } } template inline bool VulkanSample::has_device() const { return device != nullptr; } template inline bool VulkanSample::has_instance() const { return instance != nullptr; } template inline bool VulkanSample::has_gui() const { return gui != nullptr; } template inline bool VulkanSample::has_render_context() const { return render_context != nullptr; } template inline bool VulkanSample::has_render_pipeline() const { return render_pipeline != nullptr; } template bool VulkanSample::has_scene() { return scene != nullptr; } template inline void VulkanSample::input_event(const InputEvent &input_event) { Parent::input_event(input_event); bool gui_captures_event = false; if (gui) { gui_captures_event = gui->input_event(input_event); } if (!gui_captures_event) { if (scene && scene->has_component()) { auto scripts = scene->get_components(); for (auto script : scripts) { script->input_event(input_event); } } } if (input_event.get_source() == EventSource::Keyboard) { const auto &key_event = static_cast(input_event); if (key_event.get_action() == KeyAction::Down && (key_event.get_code() == KeyCode::PrintScreen || key_event.get_code() == KeyCode::F12)) { vkb::common::screenshot(*render_context, "screenshot-" + get_name()); } } } template inline void VulkanSample::load_scene(const std::string &path) { vkb::HPPGLTFLoader loader(*device); scene = loader.read_scene_from_file(path); if (!scene) { LOGE("Cannot load scene: {}", path.c_str()); throw std::runtime_error("Cannot load scene: " + path); } } template inline bool VulkanSample::prepare(const ApplicationOptions &options) { if (!Parent::prepare(options)) { return false; } LOGI("Initializing Vulkan sample"); // initialize C++-Bindings default dispatcher, first step #if defined(_HPP_VULKAN_LIBRARY) static vk::detail::DynamicLoader dl(_HPP_VULKAN_LIBRARY); #else static vk::detail::DynamicLoader dl; #endif VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress("vkGetInstanceProcAddr")); bool headless = window->get_window_mode() == Window::Mode::Headless; // for a while we're running on mixed C- and C++-bindings, needing volk for the C-bindings! VkResult result = volkInitialize(); if (result) { throw VulkanException(result, "Failed to initialize volk."); } // Creating the vulkan instance for (const char *extension_name : window->get_required_surface_extensions()) { add_instance_extension(extension_name); } #ifdef VKB_VULKAN_DEBUG { std::vector available_instance_extensions = vk::enumerateInstanceExtensionProperties(); auto debugExtensionIt = std::ranges::find_if(available_instance_extensions, [](vk::ExtensionProperties const &ep) { return strcmp(ep.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0; }); if (debugExtensionIt != available_instance_extensions.end()) { LOGI("Vulkan debug utils enabled ({})", VK_EXT_DEBUG_UTILS_EXTENSION_NAME); debug_utils = std::make_unique(); add_instance_extension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } } #endif if constexpr (bindingType == BindingType::Cpp) { instance = create_instance(); } else { instance.reset(reinterpret_cast(create_instance().release())); } // initialize C++-Bindings default dispatcher, second step VULKAN_HPP_DEFAULT_DISPATCHER.init(instance->get_handle()); // Getting a valid vulkan surface from the platform surface = static_cast(window->create_surface(reinterpret_cast(*instance))); if (!surface) { throw std::runtime_error("Failed to create window surface."); } auto &gpu = instance->get_suitable_gpu(surface, headless); gpu.set_high_priority_graphics_queue_enable(high_priority_graphics_queue); // Request to enable ASTC if (gpu.get_features().textureCompressionASTC_LDR) { gpu.get_mutable_requested_features().textureCompressionASTC_LDR = true; } // Request sample required GPU features if constexpr (bindingType == BindingType::Cpp) { request_gpu_features(gpu); } else { request_gpu_features(reinterpret_cast(gpu)); } // Creating vulkan device, specifying the swapchain extension always // If using VK_EXT_headless_surface, we still create and use a swap-chain { add_device_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); if (instance_extensions.find(VK_KHR_DISPLAY_EXTENSION_NAME) != instance_extensions.end()) { add_device_extension(VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME, /*optional=*/true); } } #ifdef VKB_ENABLE_PORTABILITY // VK_KHR_portability_subset must be enabled if present in the implementation (e.g on macOS/iOS with beta extensions enabled) add_device_extension(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, /*optional=*/true); #endif #ifdef VKB_VULKAN_DEBUG if (!debug_utils) { std::vector available_device_extensions = gpu.get_handle().enumerateDeviceExtensionProperties(); auto debugExtensionIt = std::ranges::find_if(available_device_extensions, [](vk::ExtensionProperties const &ep) { return strcmp(ep.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0; }); if (debugExtensionIt != available_device_extensions.end()) { LOGI("Vulkan debug utils enabled ({})", VK_EXT_DEBUG_MARKER_EXTENSION_NAME); debug_utils = std::make_unique(); add_device_extension(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); } } if (!debug_utils) { LOGW("Vulkan debug utils were requested, but no extension that provides them was found"); } #endif if (!debug_utils) { debug_utils = std::make_unique(); } if constexpr (bindingType == BindingType::Cpp) { device = create_device(gpu); } else { device.reset(reinterpret_cast(create_device(reinterpret_cast(gpu)).release())); } // initialize C++-Bindings default dispatcher, optional third step VULKAN_HPP_DEFAULT_DISPATCHER.init(device->get_handle()); create_render_context(); prepare_render_context(); stats = std::make_unique(*render_context); // Start the sample in the first GUI configuration configuration.reset(); return true; } template inline void VulkanSample::create_gui(const Window &window, StatsType const *stats, const float font_size, bool explicit_update) { if constexpr (bindingType == BindingType::Cpp) { gui = std::make_unique(get_render_context(), window, stats, font_size, explicit_update); } else { gui = std::make_unique(reinterpret_cast(get_render_context()), window, reinterpret_cast(stats), font_size, explicit_update); } } template inline void VulkanSample::prepare_render_context() { render_context->prepare(); } template inline void VulkanSample::render(vkb::core::CommandBuffer &command_buffer) { if constexpr (bindingType == BindingType::Cpp) { render_impl(command_buffer); } else { render_impl(reinterpret_cast(command_buffer)); } } template inline void VulkanSample::render_impl(vkb::core::CommandBufferCpp &command_buffer) { if (render_pipeline) { render_pipeline->draw(command_buffer, render_context->get_active_frame().get_render_target()); } } template inline void VulkanSample::request_gpu_features(PhysicalDeviceType &gpu) { // To be overridden by sample } template inline void VulkanSample::reset_stats_view() { } template inline bool VulkanSample::resize(uint32_t width, uint32_t height) { if (!Parent::resize(width, height)) { return false; } if (gui) { gui->resize(width, height); } if (scene && scene->has_component()) { auto scripts = scene->get_components(); for (auto script : scripts) { script->resize(width, height); } } if (stats) { stats->resize(width); } return true; } template inline void VulkanSample::set_api_version(uint32_t requested_api_version) { api_version = requested_api_version; } template inline void VulkanSample::set_high_priority_graphics_queue_enable(bool enable) { high_priority_graphics_queue = enable; } template inline void VulkanSample::set_render_context(std::unique_ptr &&rc) { if constexpr (bindingType == BindingType::Cpp) { render_context.reset(rc.release()); } else { render_context.reset(reinterpret_cast(rc.release())); } } template inline void VulkanSample::set_render_pipeline(std::unique_ptr &&rp) { if constexpr (bindingType == BindingType::Cpp) { render_pipeline.reset(rp.release()); } else { render_pipeline.reset(reinterpret_cast(rp.release())); } } template inline void VulkanSample::set_viewport_and_scissor(vkb::core::CommandBuffer const &command_buffer, Extent2DType const &extent) { if constexpr (bindingType == BindingType::Cpp) { set_viewport_and_scissor_impl(command_buffer, extent); } else { set_viewport_and_scissor_impl(reinterpret_cast(command_buffer), reinterpret_cast(extent)); } } template inline void VulkanSample::set_viewport_and_scissor_impl(vkb::core::CommandBufferCpp const &command_buffer, vk::Extent2D const &extent) { command_buffer.get_handle().setViewport(0, {{0.0f, 0.0f, static_cast(extent.width), static_cast(extent.height), 0.0f, 1.0f}}); command_buffer.get_handle().setScissor(0, vk::Rect2D{{}, extent}); } template inline void VulkanSample::update(float delta_time) { vkb::Application::update(delta_time); update_scene(delta_time); update_gui(delta_time); auto command_buffer = render_context->begin(); // Collect the performance data for the sample graphs update_stats(delta_time); command_buffer->begin(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); stats->begin_sampling(*command_buffer); if constexpr (bindingType == BindingType::Cpp) { draw(*command_buffer, render_context->get_active_frame().get_render_target()); } else { draw(reinterpret_cast(*command_buffer), reinterpret_cast(render_context->get_active_frame().get_render_target())); } stats->end_sampling(*command_buffer); command_buffer->end(); render_context->submit(*command_buffer); } template inline void VulkanSample::update_debug_window() { auto driver_version = device->get_gpu().get_driver_version(); std::string driver_version_str = fmt::format("major: {} minor: {} patch: {}", driver_version.major, driver_version.minor, driver_version.patch); get_debug_info().template insert("driver_version", driver_version_str); get_debug_info().template insert("resolution", to_string(static_cast(render_context->get_swapchain().get_extent()))); get_debug_info().template insert("surface_format", to_string(render_context->get_swapchain().get_format()) + " (" + to_string(vkb::common::get_bits_per_pixel(render_context->get_swapchain().get_format())) + "bpp)"); if (scene != nullptr) { get_debug_info().template insert("mesh_count", to_u32(scene->get_components().size())); get_debug_info().template insert("texture_count", to_u32(scene->get_components().size())); if (auto camera = scene->get_components()[0]) { if (auto camera_node = camera->get_node()) { const glm::vec3 &pos = camera_node->get_transform().get_translation(); get_debug_info().template insert("camera_pos", pos.x, pos.y, pos.z); } } } } template inline void VulkanSample::update_gui(float delta_time) { if (gui) { if (gui->is_debug_view_active()) { update_debug_window(); } gui->new_frame(); gui->show_top_window(get_name(), stats.get(), &get_debug_info()); // Samples can override this draw_gui(); gui->update(delta_time); } } template inline void VulkanSample::update_scene(float delta_time) { if (scene) { // Update scripts if (scene->has_component()) { auto scripts = scene->get_components(); for (auto script : scripts) { script->update(delta_time); } } // Update animations if (scene->has_component()) { auto animations = scene->get_components(); for (auto animation : animations) { animation->update(delta_time); } } } } template inline void VulkanSample::update_stats(float delta_time) { if (stats) { stats->update(delta_time); static float stats_view_count = 0.0f; stats_view_count += delta_time; // Reset every STATS_VIEW_RESET_TIME seconds if (stats_view_count > STATS_VIEW_RESET_TIME) { reset_stats_view(); stats_view_count = 0.0f; } } } } // namespace vkb