Files
2025-09-04 10:54:47 +08:00

1451 lines
46 KiB
C++

/* 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 <TargetConditionals.h>
#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 <vkb::BindingType bindingType>
class VulkanSample : public vkb::Application
{
using Parent = vkb::Application;
/// <summary>
/// PUBLIC INTERFACE
/// </summary>
public:
VulkanSample() = default;
~VulkanSample() override;
using PhysicalDeviceType = typename std::conditional<bindingType == BindingType::Cpp, vkb::core::HPPPhysicalDevice, vkb::PhysicalDevice>::type;
using RenderContextType = typename std::conditional<bindingType == BindingType::Cpp, vkb::rendering::HPPRenderContext, vkb::RenderContext>::type;
using RenderPipelineType = typename std::conditional<bindingType == BindingType::Cpp, vkb::rendering::HPPRenderPipeline, vkb::RenderPipeline>::type;
using RenderTargetType = typename std::conditional<bindingType == BindingType::Cpp, vkb::rendering::HPPRenderTarget, vkb::RenderTarget>::type;
using SceneType = typename std::conditional<bindingType == BindingType::Cpp, vkb::scene_graph::HPPScene, vkb::sg::Scene>::type;
using StatsType = typename std::conditional<bindingType == BindingType::Cpp, vkb::stats::HPPStats, vkb::Stats>::type;
using Extent2DType = typename std::conditional<bindingType == BindingType::Cpp, vk::Extent2D, VkExtent2D>::type;
using LayerSettingType = typename std::conditional<bindingType == BindingType::Cpp, vk::LayerSettingEXT, VkLayerSettingEXT>::type;
using SurfaceFormatType = typename std::conditional<bindingType == BindingType::Cpp, vk::SurfaceFormatKHR, VkSurfaceFormatKHR>::type;
using SurfaceType = typename std::conditional<bindingType == BindingType::Cpp, vk::SurfaceKHR, VkSurfaceKHR>::type;
Configuration &get_configuration();
RenderContextType &get_render_context();
RenderContextType const &get_render_context() const;
bool has_render_context() const;
/// <summary>
/// PROTECTED VIRTUAL INTERFACE
/// </summary>
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<vkb::core::Device<bindingType>> 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<vkb::core::Instance<bindingType>> 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<bindingType> &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<bindingType> &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<bindingType> &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();
/// <summary>
/// PROTECTED INTERFACE
/// </summary>
/**
* @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<SurfaceFormatType> &surface_priority_list);
vkb::core::Device<bindingType> &get_device();
vkb::core::Device<bindingType> const &get_device() const;
vkb::Gui<bindingType> &get_gui();
vkb::Gui<bindingType> const &get_gui() const;
vkb::core::Instance<bindingType> &get_instance();
vkb::core::Instance<bindingType> 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<SurfaceFormatType> &get_surface_priority_list();
std::vector<SurfaceFormatType> 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<RenderContextType> &&render_context);
void set_render_pipeline(std::unique_ptr<RenderPipelineType> &&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<bindingType> const &command_buffer, Extent2DType const &extent);
/// <summary>
/// PRIVATE INTERFACE
/// </summary>
private:
void create_render_context_impl(const std::vector<vk::SurfaceFormatKHR> &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 char *, bool> 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 char *, bool> 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 char *, bool> const &get_instance_layers() const;
/**
* @brief Get sample-specific layer settings.
*
* @return Vector of layer settings. Default is empty vector.
*/
std::vector<LayerSettingType> const &get_layer_settings() const;
/// <summary>
/// PRIVATE MEMBERS
/// </summary>
private:
/**
* @brief The Vulkan instance
*/
std::unique_ptr<vkb::core::InstanceCpp> instance;
/**
* @brief The Vulkan device
*/
std::unique_ptr<vkb::core::DeviceCpp> device;
/**
* @brief Context used for rendering, it is responsible for managing the frames and their underlying images
*/
std::unique_ptr<vkb::rendering::HPPRenderContext> render_context;
/**
* @brief Pipeline used for rendering, it should be set up by the concrete sample
*/
std::unique_ptr<vkb::rendering::HPPRenderPipeline> render_pipeline;
/**
* @brief Holds all scene information
*/
std::unique_ptr<vkb::scene_graph::HPPScene> scene;
std::unique_ptr<vkb::GuiCpp> gui;
std::unique_ptr<vkb::stats::HPPStats> 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<vk::SurfaceFormatKHR> 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<const char *, bool> 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<const char *, bool> 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<const char *, bool> instance_layers;
/** @brief Vector of layer settings to be enabled for this example (must be set in the derived constructor) */
std::vector<vk::LayerSettingEXT> 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<vkb::core::HPPDebugUtils> debug_utils;
};
using VulkanSampleC = VulkanSample<vkb::BindingType::C>;
using VulkanSampleCpp = VulkanSample<vkb::BindingType::Cpp>;
// Member function definitions
template <vkb::BindingType bindingType>
inline VulkanSample<bindingType>::~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 <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::add_device_extension(const char *extension, bool optional)
{
device_extensions[extension] = optional;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::add_instance_extension(const char *extension, bool optional)
{
instance_extensions[extension] = optional;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::add_layer_setting(LayerSettingType const &layerSetting)
{
if constexpr (bindingType == BindingType::Cpp)
{
layer_settings.push_back(layerSetting);
}
else
{
layer_settings.push_back(reinterpret_cast<vk::LayerSettingEXT const &>(layerSetting));
}
}
template <vkb::BindingType bindingType>
inline std::unique_ptr<typename vkb::core::Device<bindingType>> VulkanSample<bindingType>::create_device(PhysicalDeviceType &gpu)
{
if constexpr (bindingType == BindingType::Cpp)
{
return std::make_unique<vkb::core::DeviceCpp>(gpu, surface, std::move(debug_utils), get_device_extensions());
}
else
{
return std::make_unique<vkb::core::DeviceC>(gpu,
static_cast<VkSurfaceKHR>(surface),
std::unique_ptr<vkb::DebugUtils>(reinterpret_cast<vkb::DebugUtils *>(debug_utils.release())),
get_device_extensions());
}
}
template <vkb::BindingType bindingType>
inline std::unique_ptr<vkb::core::Instance<bindingType>> VulkanSample<bindingType>::create_instance()
{
return std::make_unique<vkb::core::Instance<bindingType>>(get_name(), get_instance_extensions(), get_instance_layers(), get_layer_settings(), api_version);
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::create_render_context()
{
create_render_context_impl(surface_priority_list);
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::create_render_context(const std::vector<SurfaceFormatType> &surface_priority_list)
{
if constexpr (bindingType == BindingType::Cpp)
{
create_render_context_impl(surface_priority_list);
}
else
{
create_render_context_impl(reinterpret_cast<std::vector<vk::SurfaceFormatKHR> const &>(surface_priority_list));
}
}
template <vkb::BindingType bindingType>
void VulkanSample<bindingType>::create_render_context_impl(const std::vector<vk::SurfaceFormatKHR> &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<vk::PresentModeKHR> 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<vk::PresentModeKHR> present_mode_priority_list{vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eImmediate, vk::PresentModeKHR::eFifo};
#endif
render_context =
std::make_unique<vkb::rendering::HPPRenderContext>(*device, surface, *window, present_mode, present_mode_priority_list, surface_priority_list);
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::draw(vkb::core::CommandBuffer<bindingType> &command_buffer, RenderTargetType &render_target)
{
if constexpr (bindingType == BindingType::Cpp)
{
draw_impl(command_buffer, render_target);
}
else
{
draw_impl(reinterpret_cast<vkb::core::CommandBufferCpp &>(command_buffer),
reinterpret_cast<vkb::rendering::HPPRenderTarget &>(render_target));
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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<uint32_t>(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<vkb::core::CommandBufferC &>(command_buffer), reinterpret_cast<vkb::RenderTarget &>(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 <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::draw_gui()
{
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::draw_renderpass(vkb::core::CommandBuffer<bindingType> &command_buffer, RenderTargetType &render_target)
{
if constexpr (bindingType == BindingType::Cpp)
{
draw_renderpass_impl(command_buffer, render_target);
}
else
{
draw_renderpass_impl(reinterpret_cast<vkb::core::CommandBufferCpp &>(command_buffer),
reinterpret_cast<vkb::rendering::HPPRenderTarget &>(render_target));
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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<vkb::core::CommandBufferC &>(command_buffer));
}
if (gui)
{
gui->draw(command_buffer);
}
command_buffer.get_handle().endRenderPass();
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::finish()
{
Parent::finish();
if (device)
{
device->get_handle().waitIdle();
}
}
template <vkb::BindingType bindingType>
inline Configuration &VulkanSample<bindingType>::get_configuration()
{
return configuration;
}
template <vkb::BindingType bindingType>
inline vkb::core::Device<bindingType> const &VulkanSample<bindingType>::get_device() const
{
if constexpr (bindingType == BindingType::Cpp)
{
return *device;
}
else
{
return reinterpret_cast<vkb::core::DeviceC const &>(*device);
}
}
template <vkb::BindingType bindingType>
inline vkb::core::Device<bindingType> &VulkanSample<bindingType>::get_device()
{
if constexpr (bindingType == BindingType::Cpp)
{
return *device;
}
else
{
return reinterpret_cast<vkb::core::DeviceC &>(*device);
}
}
template <vkb::BindingType bindingType>
inline std::unordered_map<const char *, bool> const &VulkanSample<bindingType>::get_device_extensions() const
{
return device_extensions;
}
template <vkb::BindingType bindingType>
inline vkb::Gui<bindingType> &VulkanSample<bindingType>::get_gui()
{
if constexpr (bindingType == BindingType::Cpp)
{
return *gui;
}
else
{
return reinterpret_cast<vkb::GuiC &>(*gui);
}
}
template <vkb::BindingType bindingType>
inline vkb::Gui<bindingType> const &VulkanSample<bindingType>::get_gui() const
{
if constexpr (bindingType == BindingType::Cpp)
{
return *gui;
}
else
{
return reinterpret_cast<vkb::GuiC const &>(*gui);
}
}
template <vkb::BindingType bindingType>
inline std::vector<typename VulkanSample<bindingType>::SurfaceFormatType> &VulkanSample<bindingType>::get_surface_priority_list()
{
if constexpr (bindingType == BindingType::Cpp)
{
return surface_priority_list;
}
else
{
return reinterpret_cast<std::vector<VkSurfaceFormatKHR> &>(surface_priority_list);
}
}
template <vkb::BindingType bindingType>
inline std::vector<typename VulkanSample<bindingType>::SurfaceFormatType> const &VulkanSample<bindingType>::get_surface_priority_list() const
{
if constexpr (bindingType == BindingType::Cpp)
{
return surface_priority_list;
}
else
{
return reinterpret_cast<std::vector<VkSurfaceFormatKHR> const &>(surface_priority_list);
}
}
template <vkb::BindingType bindingType>
inline vkb::core::Instance<bindingType> &VulkanSample<bindingType>::get_instance()
{
if constexpr (bindingType == BindingType::Cpp)
{
return *instance;
}
else
{
return reinterpret_cast<vkb::core::InstanceC &>(*instance);
}
}
template <vkb::BindingType bindingType>
inline vkb::core::Instance<bindingType> const &VulkanSample<bindingType>::get_instance() const
{
if constexpr (bindingType == BindingType::Cpp)
{
return *instance;
}
else
{
return reinterpret_cast<vkb::core::InstanceC const &>(*instance);
}
}
template <vkb::BindingType bindingType>
inline std::unordered_map<const char *, bool> const &VulkanSample<bindingType>::get_instance_extensions() const
{
return instance_extensions;
}
template <vkb::BindingType bindingType>
inline std::unordered_map<const char *, bool> const &VulkanSample<bindingType>::get_instance_layers() const
{
return instance_layers;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::add_instance_layer(const char *layer, bool optional)
{
instance_layers[layer] = optional;
}
template <vkb::BindingType bindingType>
inline std::vector<typename VulkanSample<bindingType>::LayerSettingType> const &VulkanSample<bindingType>::get_layer_settings() const
{
if constexpr (bindingType == BindingType::Cpp)
{
return layer_settings;
}
else
{
return reinterpret_cast<std::vector<VkLayerSettingEXT> const &>(layer_settings);
}
}
template <vkb::BindingType bindingType>
inline typename VulkanSample<bindingType>::RenderContextType const &VulkanSample<bindingType>::get_render_context() const
{
assert(render_context && "Render context is not valid");
if constexpr (bindingType == BindingType::Cpp)
{
return reinterpret_cast<vkb::rendering::HPPRenderContext const &>(*render_context);
}
else
{
return *render_context;
}
}
template <vkb::BindingType bindingType>
inline typename VulkanSample<bindingType>::RenderContextType &VulkanSample<bindingType>::get_render_context()
{
assert(render_context && "Render context is not valid");
if constexpr (bindingType == BindingType::Cpp)
{
return *render_context;
}
else
{
return reinterpret_cast<vkb::RenderContext &>(*render_context);
}
}
template <vkb::BindingType bindingType>
inline typename VulkanSample<bindingType>::RenderPipelineType const &VulkanSample<bindingType>::get_render_pipeline() const
{
assert(render_pipeline && "Render pipeline was not created");
if constexpr (bindingType == BindingType::Cpp)
{
return *render_pipeline;
}
else
{
return reinterpret_cast<vkb::RenderPipeline const &>(*render_pipeline);
}
}
template <vkb::BindingType bindingType>
inline typename VulkanSample<bindingType>::RenderPipelineType &VulkanSample<bindingType>::get_render_pipeline()
{
assert(render_pipeline && "Render pipeline was not created");
if constexpr (bindingType == BindingType::Cpp)
{
return *render_pipeline;
}
else
{
return reinterpret_cast<vkb::RenderPipeline &>(*render_pipeline);
}
}
template <vkb::BindingType bindingType>
inline typename VulkanSample<bindingType>::SceneType &VulkanSample<bindingType>::get_scene()
{
assert(scene && "Scene not loaded");
if constexpr (bindingType == BindingType::Cpp)
{
return *scene;
}
else
{
return reinterpret_cast<vkb::sg::Scene &>(*scene);
}
}
template <vkb::BindingType bindingType>
inline typename VulkanSample<bindingType>::StatsType &VulkanSample<bindingType>::get_stats()
{
if constexpr (bindingType == BindingType::Cpp)
{
return *stats;
}
else
{
return reinterpret_cast<vkb::Stats &>(*stats);
}
}
template <vkb::BindingType bindingType>
inline typename VulkanSample<bindingType>::SurfaceType VulkanSample<bindingType>::get_surface() const
{
if constexpr (bindingType == BindingType::Cpp)
{
return surface;
}
else
{
return static_cast<VkSurfaceKHR>(surface);
}
}
template <vkb::BindingType bindingType>
inline bool VulkanSample<bindingType>::has_device() const
{
return device != nullptr;
}
template <vkb::BindingType bindingType>
inline bool VulkanSample<bindingType>::has_instance() const
{
return instance != nullptr;
}
template <vkb::BindingType bindingType>
inline bool VulkanSample<bindingType>::has_gui() const
{
return gui != nullptr;
}
template <vkb::BindingType bindingType>
inline bool VulkanSample<bindingType>::has_render_context() const
{
return render_context != nullptr;
}
template <vkb::BindingType bindingType>
inline bool VulkanSample<bindingType>::has_render_pipeline() const
{
return render_pipeline != nullptr;
}
template <vkb::BindingType bindingType>
bool VulkanSample<bindingType>::has_scene()
{
return scene != nullptr;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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<sg::Script>())
{
auto scripts = scene->get_components<sg::Script>();
for (auto script : scripts)
{
script->input_event(input_event);
}
}
}
if (input_event.get_source() == EventSource::Keyboard)
{
const auto &key_event = static_cast<const KeyInputEvent &>(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 <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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 <vkb::BindingType bindingType>
inline bool VulkanSample<bindingType>::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<PFN_vkGetInstanceProcAddr>("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<vk::ExtensionProperties> 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<vkb::core::HPPDebugUtilsExtDebugUtils>();
add_instance_extension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
}
#endif
if constexpr (bindingType == BindingType::Cpp)
{
instance = create_instance();
}
else
{
instance.reset(reinterpret_cast<vkb::core::InstanceCpp *>(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<vk::SurfaceKHR>(window->create_surface(reinterpret_cast<vkb::core::InstanceC &>(*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<vkb::PhysicalDevice &>(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<vk::ExtensionProperties> 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<vkb::core::HPPDebugMarkerExtDebugUtils>();
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<vkb::core::HPPDummyDebugUtils>();
}
if constexpr (bindingType == BindingType::Cpp)
{
device = create_device(gpu);
}
else
{
device.reset(reinterpret_cast<vkb::core::DeviceCpp *>(create_device(reinterpret_cast<vkb::PhysicalDevice &>(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<vkb::stats::HPPStats>(*render_context);
// Start the sample in the first GUI configuration
configuration.reset();
return true;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::create_gui(const Window &window, StatsType const *stats, const float font_size, bool explicit_update)
{
if constexpr (bindingType == BindingType::Cpp)
{
gui = std::make_unique<vkb::GuiCpp>(get_render_context(), window, stats, font_size, explicit_update);
}
else
{
gui = std::make_unique<vkb::GuiCpp>(reinterpret_cast<vkb::rendering::HPPRenderContext &>(get_render_context()),
window,
reinterpret_cast<vkb::stats::HPPStats const *>(stats),
font_size,
explicit_update);
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::prepare_render_context()
{
render_context->prepare();
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::render(vkb::core::CommandBuffer<bindingType> &command_buffer)
{
if constexpr (bindingType == BindingType::Cpp)
{
render_impl(command_buffer);
}
else
{
render_impl(reinterpret_cast<vkb::core::CommandBufferCpp &>(command_buffer));
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::render_impl(vkb::core::CommandBufferCpp &command_buffer)
{
if (render_pipeline)
{
render_pipeline->draw(command_buffer, render_context->get_active_frame().get_render_target());
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::request_gpu_features(PhysicalDeviceType &gpu)
{
// To be overridden by sample
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::reset_stats_view()
{
}
template <vkb::BindingType bindingType>
inline bool VulkanSample<bindingType>::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<sg::Script>())
{
auto scripts = scene->get_components<sg::Script>();
for (auto script : scripts)
{
script->resize(width, height);
}
}
if (stats)
{
stats->resize(width);
}
return true;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::set_api_version(uint32_t requested_api_version)
{
api_version = requested_api_version;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::set_high_priority_graphics_queue_enable(bool enable)
{
high_priority_graphics_queue = enable;
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::set_render_context(std::unique_ptr<RenderContextType> &&rc)
{
if constexpr (bindingType == BindingType::Cpp)
{
render_context.reset(rc.release());
}
else
{
render_context.reset(reinterpret_cast<vkb::rendering::HPPRenderContext *>(rc.release()));
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::set_render_pipeline(std::unique_ptr<RenderPipelineType> &&rp)
{
if constexpr (bindingType == BindingType::Cpp)
{
render_pipeline.reset(rp.release());
}
else
{
render_pipeline.reset(reinterpret_cast<vkb::rendering::HPPRenderPipeline *>(rp.release()));
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::set_viewport_and_scissor(vkb::core::CommandBuffer<bindingType> 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<vkb::core::CommandBufferCpp const &>(command_buffer),
reinterpret_cast<vk::Extent2D const &>(extent));
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f}});
command_buffer.get_handle().setScissor(0, vk::Rect2D{{}, extent});
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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<vkb::core::CommandBufferC &>(*command_buffer),
reinterpret_cast<vkb::RenderTarget &>(render_context->get_active_frame().get_render_target()));
}
stats->end_sampling(*command_buffer);
command_buffer->end();
render_context->submit(*command_buffer);
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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<field::Static, std::string>("driver_version", driver_version_str);
get_debug_info().template insert<field::Static, std::string>("resolution",
to_string(static_cast<VkExtent2D const &>(render_context->get_swapchain().get_extent())));
get_debug_info().template insert<field::Static, std::string>("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<field::Static, uint32_t>("mesh_count", to_u32(scene->get_components<sg::SubMesh>().size()));
get_debug_info().template insert<field::Static, uint32_t>("texture_count", to_u32(scene->get_components<sg::Texture>().size()));
if (auto camera = scene->get_components<vkb::sg::Camera>()[0])
{
if (auto camera_node = camera->get_node())
{
const glm::vec3 &pos = camera_node->get_transform().get_translation();
get_debug_info().template insert<field::Vector, float>("camera_pos", pos.x, pos.y, pos.z);
}
}
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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 <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::update_scene(float delta_time)
{
if (scene)
{
// Update scripts
if (scene->has_component<sg::Script>())
{
auto scripts = scene->get_components<sg::Script>();
for (auto script : scripts)
{
script->update(delta_time);
}
}
// Update animations
if (scene->has_component<sg::Animation>())
{
auto animations = scene->get_components<sg::Animation>();
for (auto animation : animations)
{
animation->update(delta_time);
}
}
}
}
template <vkb::BindingType bindingType>
inline void VulkanSample<bindingType>::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