1436 lines
56 KiB
C++
1436 lines
56 KiB
C++
/* Copyright (c) 2018-2025, Arm Limited and Contributors
|
|
* Copyright (c) 2019-2025, Sascha Willems
|
|
* Copyright (c) 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/vk_common.h"
|
|
#include "core/command_buffer.h"
|
|
#include "core/hpp_debug.h"
|
|
#include "core/hpp_sampler.h"
|
|
#include "debug_info.h"
|
|
#include "drawer.h"
|
|
#include "filesystem/legacy.h"
|
|
#include "platform/input_events.h"
|
|
#include "platform/window.h"
|
|
#include "rendering/hpp_pipeline_state.h"
|
|
#include "stats/hpp_stats.h"
|
|
#include "stats/stats.h"
|
|
#include <glm/glm.hpp>
|
|
#include <imgui.h>
|
|
#include <imgui_internal.h>
|
|
#include <numeric>
|
|
|
|
#if defined(PLATFORM__MACOS)
|
|
# include <TargetConditionals.h>
|
|
#endif
|
|
|
|
namespace vkb
|
|
{
|
|
|
|
namespace
|
|
{
|
|
inline void reset_graph_max_value(StatGraphData &graph_data)
|
|
{
|
|
// If it does not have a fixed max
|
|
if (!graph_data.has_fixed_max)
|
|
{
|
|
// Reset it
|
|
graph_data.max_value = 0.0f;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
/**
|
|
* @brief Helper structure for fonts loaded from TTF
|
|
*/
|
|
struct Font
|
|
{
|
|
/**
|
|
* @brief Constructor
|
|
* @param name_ The name of the font file that exists within 'assets/fonts' (without extension)
|
|
* @param size_ The font size, scaled by DPI
|
|
*/
|
|
Font(const std::string &name_, float size_) :
|
|
name{name_}, data{vkb::fs::read_asset("fonts/" + name + ".ttf")}, size{size_}
|
|
{
|
|
// Keep ownership of the font data to avoid a double delete
|
|
ImFontConfig font_config{};
|
|
font_config.FontDataOwnedByAtlas = false;
|
|
|
|
if (size < 1.0f)
|
|
{
|
|
size = 20.0f;
|
|
}
|
|
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
handle = io.Fonts->AddFontFromMemoryTTF(data.data(), static_cast<int>(data.size()), size, &font_config);
|
|
}
|
|
|
|
ImFont *handle = nullptr;
|
|
std::string name;
|
|
std::vector<uint8_t> data;
|
|
float size = {};
|
|
};
|
|
|
|
/**
|
|
* @brief Vulkan helper class for Dear ImGui
|
|
*/
|
|
template <vkb::BindingType bindingType>
|
|
class Gui
|
|
{
|
|
public:
|
|
static inline bool visible = true; // Used to show/hide the GUI
|
|
|
|
using CommandBufferType = typename std::conditional<bindingType == BindingType::Cpp, vk::CommandBuffer, VkCommandBuffer>::type;
|
|
using DescriptorSetType = typename std::conditional<bindingType == BindingType::Cpp, vk::DescriptorSet, VkDescriptorSet>::type;
|
|
using PipelineType = typename std::conditional<bindingType == BindingType::Cpp, vk::Pipeline, VkPipeline>::type;
|
|
using PipelineCacheType = typename std::conditional<bindingType == BindingType::Cpp, vk::PipelineCache, VkPipelineCache>::type;
|
|
using PipelineLayoutType = typename std::conditional<bindingType == BindingType::Cpp, vk::PipelineLayout, VkPipelineLayout>::type;
|
|
using PipelineShaderStageCreateInfoType = typename std::conditional<bindingType == BindingType::Cpp, vk::PipelineShaderStageCreateInfo, VkPipelineShaderStageCreateInfo>::type;
|
|
using RenderContextType = typename std::conditional<bindingType == BindingType::Cpp, vkb::rendering::HPPRenderContext, vkb::RenderContext>::type;
|
|
using RenderPassType = typename std::conditional<bindingType == BindingType::Cpp, vk::RenderPass, VkRenderPass>::type;
|
|
using StatsType = typename std::conditional<bindingType == BindingType::Cpp, vkb::stats::HPPStats, vkb::Stats>::type;
|
|
|
|
public:
|
|
/**
|
|
* @brief Helper struct for rendering debug statistics in the GUI
|
|
*/
|
|
struct DebugView
|
|
{
|
|
bool active = false;
|
|
float scale = 1.7f;
|
|
uint32_t max_fields = 8;
|
|
float label_column_width = 0;
|
|
};
|
|
|
|
/**
|
|
* @brief Helper class for drawing statistics
|
|
*/
|
|
class StatsView
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Constructs a StatsView
|
|
* @param stats Const pointer to the Stats data object; may be null
|
|
*/
|
|
StatsView(const StatsType *stats);
|
|
|
|
float get_graph_height() const;
|
|
|
|
StatGraphData const &get_stat_graph_data(StatIndex const &index) const;
|
|
|
|
float get_top_padding() const;
|
|
|
|
/**
|
|
* @brief Resets the max value for a specific stat
|
|
*/
|
|
void reset_max_value(const StatIndex index);
|
|
|
|
/**
|
|
* @brief Resets the max values for the stats which do not have a fixed max
|
|
*/
|
|
void reset_max_values();
|
|
|
|
private:
|
|
std::map<StatIndex, StatGraphData> graph_map;
|
|
float graph_height = 50.0f;
|
|
float top_padding = 1.1f;
|
|
};
|
|
|
|
public:
|
|
/**
|
|
* @brief Initializes the Gui
|
|
* @param render_context A vulkan render context
|
|
* @param window A Window object from which to draw DPI and content scaling information
|
|
* @param stats A statistics object (null if no statistics are used)
|
|
* @param font_size The font size
|
|
* @param explicit_update If true, update buffers every frame
|
|
*/
|
|
Gui(RenderContextType &render_context, Window const &window, StatsType const *stats = nullptr, float font_size = 21.0f, bool explicit_update = false);
|
|
|
|
/**
|
|
* @brief Destroys the Gui
|
|
*/
|
|
~Gui();
|
|
|
|
public:
|
|
/**
|
|
* @brief Draws the Gui
|
|
* @param command_buffer Command buffer to register draw-commands
|
|
*/
|
|
void draw(CommandBufferType command_buffer);
|
|
|
|
/**
|
|
* @brief Draws the Gui using an external pipeline
|
|
* @param command_buffer Command buffer to register draw-commands
|
|
* @param pipeline Pipeline to bind to perform draw-commands
|
|
* @param pipeline_layout PipelineLayout for given pieline
|
|
* @param descriptor_set DescriptorSet to bind to perform draw-commands
|
|
*/
|
|
void draw(CommandBufferType command_buffer, PipelineType pipeline, PipelineLayoutType pipeline_layout, DescriptorSetType descriptor_set);
|
|
|
|
/**
|
|
* @brief Draws the Gui
|
|
* @param command_buffer Command buffer to register draw-commands
|
|
*/
|
|
void draw(vkb::core::CommandBuffer<bindingType> &command_buffer);
|
|
|
|
Drawer &get_drawer();
|
|
|
|
vk::ImageView get_font_image_view() const;
|
|
|
|
vk::Sampler get_sampler() const;
|
|
|
|
/**
|
|
* @return The stats view
|
|
*/
|
|
StatsView &get_stats_view();
|
|
|
|
bool input_event(const InputEvent &input_event);
|
|
|
|
bool is_debug_view_active() const;
|
|
|
|
/**
|
|
* @brief Starts a new ImGui frame
|
|
* to be called before drawing any window
|
|
*/
|
|
void new_frame();
|
|
|
|
void prepare(PipelineCacheType pipeline_cache, RenderPassType render_pass, std::vector<PipelineShaderStageCreateInfoType> const &shader_stages);
|
|
|
|
/**
|
|
* @brief Handles resizing of the window
|
|
* @param width New width of the window
|
|
* @param height New height of the window
|
|
*/
|
|
void resize(const uint32_t width, const uint32_t height) const;
|
|
|
|
void set_subpass(const uint32_t subpass);
|
|
|
|
/**
|
|
* @brief Shows an options windows, to be filled by the sample,
|
|
* which will be positioned at the top
|
|
* @param body ImGui commands defining the body of the window
|
|
* @param lines The number of lines of text to draw in the window
|
|
* These will help the gui to calculate the height of the window
|
|
*/
|
|
void show_options_window(std::function<void()> body, const uint32_t lines = 3);
|
|
|
|
void show_simple_window(const std::string &name, uint32_t last_fps, std::function<void()> body);
|
|
|
|
/**
|
|
* @brief Shows an overlay top window with app info and maybe stats
|
|
* @param app_name Application name
|
|
* @param stats Statistics to show (can be null)
|
|
* @param debug_info Debug info to show (can be null)
|
|
*/
|
|
void show_top_window(const std::string &app_name, const StatsType *stats = nullptr, DebugInfo *debug_info = nullptr);
|
|
|
|
/**
|
|
* @brief Updates the Gui
|
|
* @param delta_time Time passed since last update
|
|
*/
|
|
void update(const float delta_time);
|
|
|
|
bool update_buffers();
|
|
|
|
private:
|
|
static constexpr char const *default_font = "Roboto-Regular"; // The name of the default font file to use
|
|
static constexpr char const *default_window_font = "RobotoMono-Regular"; // The name of the default window font file to use
|
|
static constexpr ImGuiWindowFlags common_flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoTitleBar |
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings |
|
|
ImGuiWindowFlags_NoFocusOnAppearing;
|
|
static constexpr float overlay_alpha = 0.3f;
|
|
static constexpr double press_time_ms = 200.0;
|
|
|
|
private:
|
|
void draw_impl(vk::CommandBuffer command_buffer, vk::Pipeline pipeline, vk::PipelineLayout pipeline_layout, vk::DescriptorSet descriptor_set);
|
|
void draw_impl(vkb::core::CommandBufferCpp &command_buffer);
|
|
void prepare_impl(vk::PipelineCache pipeline_cache, vk::RenderPass render_pass, std::vector<vk::PipelineShaderStageCreateInfo> const &shader_stages);
|
|
|
|
Font &get_font(const std::string &font_name = Gui::default_font);
|
|
|
|
/**
|
|
* @brief Shows an child with app info
|
|
* @param app_name Application name
|
|
*/
|
|
void show_app_info(const std::string &app_name);
|
|
|
|
/**
|
|
* @brief Shows a moveable window with debug information
|
|
* @param debug_info The object holding the data fields to be displayed
|
|
* @param position The absolute position to set
|
|
*/
|
|
void show_debug_window(DebugInfo &debug_info, const ImVec2 &position);
|
|
|
|
/**
|
|
* @brief Shows a child with statistics
|
|
* @param stats Statistics to show
|
|
*/
|
|
void show_stats(const StatsType &stats);
|
|
|
|
/**
|
|
* @brief Updates Vulkan buffers
|
|
* @param command_buffer Command buffer to draw into
|
|
* @return Vertex buffer allocation
|
|
*/
|
|
BufferAllocationCpp update_buffers(vkb::core::CommandBufferCpp &command_buffer);
|
|
|
|
void upload_draw_data(const ImDrawData *draw_data, uint8_t *vertex_data, uint8_t *index_data);
|
|
|
|
private:
|
|
float content_scale_factor = 1.0f; // Scale factor to apply due to a difference between the window and GL pixel sizes
|
|
DebugView debug_view;
|
|
vk::DescriptorPool descriptor_pool;
|
|
vk::DescriptorSet descriptor_set;
|
|
vk::DescriptorSetLayout descriptor_set_layout;
|
|
float dpi_factor = 1.0f; // Scale factor to apply to the size of gui elements (expressed in dp)
|
|
Drawer drawer;
|
|
bool explicit_update = false;
|
|
std::vector<Font> fonts;
|
|
std::unique_ptr<vkb::core::HPPImage> font_image;
|
|
std::unique_ptr<vkb::core::HPPImageView> font_image_view;
|
|
std::unique_ptr<vkb::core::BufferCpp> index_buffer;
|
|
vk::Pipeline pipeline;
|
|
vkb::core::HPPPipelineLayout *pipeline_layout = nullptr;
|
|
bool prev_visible = true;
|
|
vkb::rendering::HPPRenderContext &render_context;
|
|
std::unique_ptr<vkb::core::HPPSampler> sampler;
|
|
StatsView stats_view;
|
|
uint32_t subpass = 0;
|
|
Timer timer; // Used to measure duration of input events
|
|
bool two_finger_tap = false; // Whether or not the GUI has detected a multi touch gesture
|
|
std::unique_ptr<vkb::core::BufferCpp> vertex_buffer;
|
|
};
|
|
|
|
using GuiC = Gui<vkb::BindingType::C>;
|
|
using GuiCpp = Gui<vkb::BindingType::Cpp>;
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline Gui<bindingType>::Gui(RenderContextType &render_context_, Window const &window, StatsType const *stats, float font_size, bool explicit_update) :
|
|
render_context{render_context_},
|
|
content_scale_factor{window.get_content_scale_factor()},
|
|
dpi_factor{window.get_dpi_factor() * content_scale_factor},
|
|
explicit_update{explicit_update},
|
|
stats_view(stats)
|
|
{
|
|
ImGui::CreateContext();
|
|
|
|
ImGuiStyle &style = ImGui::GetStyle();
|
|
|
|
// Color scheme
|
|
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.005f, 0.005f, 0.005f, 0.94f);
|
|
style.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f);
|
|
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
|
|
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
|
style.Colors[ImGuiCol_Header] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
|
style.Colors[ImGuiCol_HeaderActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
|
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
|
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.8f);
|
|
style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
|
|
style.Colors[ImGuiCol_SliderGrab] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
|
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
|
|
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(1.0f, 1.0f, 1.0f, 0.1f);
|
|
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(1.0f, 1.0f, 1.0f, 0.2f);
|
|
style.Colors[ImGuiCol_Button] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
|
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f);
|
|
style.Colors[ImGuiCol_ButtonActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
|
|
|
|
// Borderless window
|
|
style.WindowBorderSize = 0.0f;
|
|
|
|
// Global scale
|
|
style.ScaleAllSizes(dpi_factor);
|
|
|
|
// Dimensions
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
auto extent = render_context.get_surface_extent();
|
|
io.DisplaySize.x = static_cast<float>(extent.width);
|
|
io.DisplaySize.y = static_cast<float>(extent.height);
|
|
io.FontGlobalScale = 1.0f;
|
|
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
|
|
|
|
// Enable keyboard navigation
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
|
io.KeyMap[ImGuiKey_Space] = static_cast<int>(KeyCode::Space);
|
|
io.KeyMap[ImGuiKey_Enter] = static_cast<int>(KeyCode::Enter);
|
|
io.KeyMap[ImGuiKey_LeftArrow] = static_cast<int>(KeyCode::Left);
|
|
io.KeyMap[ImGuiKey_RightArrow] = static_cast<int>(KeyCode::Right);
|
|
io.KeyMap[ImGuiKey_UpArrow] = static_cast<int>(KeyCode::Up);
|
|
io.KeyMap[ImGuiKey_DownArrow] = static_cast<int>(KeyCode::Down);
|
|
io.KeyMap[ImGuiKey_Tab] = static_cast<int>(KeyCode::Tab);
|
|
io.KeyMap[ImGuiKey_Escape] = static_cast<int>(KeyCode::Backspace);
|
|
|
|
// Default font
|
|
fonts.emplace_back(default_font, font_size * dpi_factor);
|
|
|
|
// Debug window font
|
|
fonts.emplace_back(default_window_font, (font_size / 2) * dpi_factor);
|
|
|
|
// Create font texture
|
|
unsigned char *font_data;
|
|
int tex_width, tex_height;
|
|
io.Fonts->GetTexDataAsRGBA32(&font_data, &tex_width, &tex_height);
|
|
size_t upload_size = tex_width * tex_height * 4 * sizeof(char);
|
|
|
|
auto &device = render_context.get_device();
|
|
|
|
// Create target image for copy
|
|
vk::Extent3D font_extent{to_u32(tex_width), to_u32(tex_height), 1u};
|
|
|
|
font_image = std::make_unique<vkb::core::HPPImage>(device, font_extent, vk::Format::eR8G8B8A8Unorm,
|
|
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst,
|
|
VMA_MEMORY_USAGE_GPU_ONLY);
|
|
font_image->set_debug_name("GUI font image");
|
|
|
|
font_image_view = std::make_unique<vkb::core::HPPImageView>(*font_image, vk::ImageViewType::e2D);
|
|
font_image_view->set_debug_name("View on GUI font image");
|
|
|
|
// Upload font data into the vulkan image memory
|
|
{
|
|
vkb::core::BufferCpp stage_buffer = vkb::core::BufferCpp::create_staging_buffer(device, upload_size, font_data);
|
|
|
|
auto command_buffer = device.get_command_pool().request_command_buffer();
|
|
|
|
vkb::HPPFencePool fence_pool{device};
|
|
|
|
// Begin recording
|
|
command_buffer->begin(vk::CommandBufferUsageFlagBits::eOneTimeSubmit, 0);
|
|
|
|
{
|
|
// Prepare for transfer
|
|
vkb::common::HPPImageMemoryBarrier memory_barrier = {.src_stage_mask = vk::PipelineStageFlagBits::eHost,
|
|
.dst_stage_mask = vk::PipelineStageFlagBits::eTransfer,
|
|
.dst_access_mask = vk::AccessFlagBits::eTransferWrite,
|
|
.old_layout = vk::ImageLayout::eUndefined,
|
|
.new_layout = vk::ImageLayout::eTransferDstOptimal};
|
|
|
|
command_buffer->image_memory_barrier(*font_image_view, memory_barrier);
|
|
}
|
|
|
|
// Copy
|
|
vk::BufferImageCopy buffer_copy_region = {.imageSubresource = {.aspectMask = font_image_view->get_subresource_range().aspectMask,
|
|
.layerCount = font_image_view->get_subresource_range().layerCount},
|
|
.imageExtent = font_image->get_extent()};
|
|
|
|
command_buffer->copy_buffer_to_image(stage_buffer, *font_image, {buffer_copy_region});
|
|
|
|
{
|
|
// Prepare for fragment shader
|
|
vkb::common::HPPImageMemoryBarrier memory_barrier = {.src_stage_mask = vk::PipelineStageFlagBits::eTransfer,
|
|
.dst_stage_mask = vk::PipelineStageFlagBits::eFragmentShader,
|
|
.src_access_mask = vk::AccessFlagBits::eTransferWrite,
|
|
.dst_access_mask = vk::AccessFlagBits::eShaderRead,
|
|
.old_layout = vk::ImageLayout::eTransferDstOptimal,
|
|
.new_layout = vk::ImageLayout::eShaderReadOnlyOptimal};
|
|
|
|
command_buffer->image_memory_barrier(*font_image_view, memory_barrier);
|
|
}
|
|
|
|
// End recording
|
|
command_buffer->end();
|
|
|
|
auto &queue = device.get_queue_by_flags(vk::QueueFlagBits::eGraphics, 0);
|
|
|
|
queue.submit(*command_buffer, device.get_fence_pool().request_fence());
|
|
|
|
// Wait for the command buffer to finish its work before destroying the staging buffer
|
|
device.get_fence_pool().wait();
|
|
device.get_fence_pool().reset();
|
|
device.get_command_pool().reset_pool();
|
|
}
|
|
|
|
// Calculate valid filter
|
|
vk::Filter filter = vk::Filter::eLinear;
|
|
vkb::common::make_filters_valid(device.get_gpu().get_handle(), font_image->get_format(), &filter);
|
|
|
|
// Load shaders
|
|
vkb::core::HPPShaderSource vert_shader("imgui.vert.spv");
|
|
vkb::core::HPPShaderSource frag_shader("imgui.frag.spv");
|
|
|
|
std::vector<vkb::core::HPPShaderModule *> shader_modules;
|
|
shader_modules.push_back(&device.get_resource_cache().request_shader_module(vk::ShaderStageFlagBits::eVertex, vert_shader, {}));
|
|
shader_modules.push_back(&device.get_resource_cache().request_shader_module(vk::ShaderStageFlagBits::eFragment, frag_shader, {}));
|
|
|
|
pipeline_layout = &device.get_resource_cache().request_pipeline_layout(shader_modules);
|
|
|
|
// Create texture sampler
|
|
vk::SamplerCreateInfo sampler_info = {.magFilter = filter,
|
|
.minFilter = filter,
|
|
.mipmapMode = vk::SamplerMipmapMode::eNearest,
|
|
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
|
|
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
|
|
.addressModeW = vk::SamplerAddressMode::eClampToEdge,
|
|
.borderColor = vk::BorderColor::eFloatOpaqueWhite};
|
|
|
|
sampler = std::make_unique<vkb::core::HPPSampler>(device, sampler_info);
|
|
sampler->set_debug_name("GUI sampler");
|
|
|
|
if (explicit_update)
|
|
{
|
|
vertex_buffer =
|
|
std::make_unique<vkb::core::BufferCpp>(render_context.get_device(), 1, vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_GPU_TO_CPU);
|
|
vertex_buffer->set_debug_name("GUI vertex buffer");
|
|
|
|
index_buffer = std::make_unique<vkb::core::BufferCpp>(render_context.get_device(), 1, vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_GPU_TO_CPU);
|
|
index_buffer->set_debug_name("GUI index buffer");
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline Gui<bindingType>::~Gui()
|
|
{
|
|
vk::Device device = render_context.get_device().get_handle();
|
|
device.destroyDescriptorPool(descriptor_pool);
|
|
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
|
device.destroyPipeline(pipeline);
|
|
|
|
ImGui::DestroyContext();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::draw(CommandBufferType command_buffer)
|
|
{
|
|
draw(command_buffer, pipeline, pipeline_layout->get_handle(), descriptor_set);
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void
|
|
Gui<bindingType>::draw(CommandBufferType command_buffer, PipelineType pipeline, PipelineLayoutType pipeline_layout, DescriptorSetType descriptor_set)
|
|
{
|
|
if constexpr (bindingType == BindingType::Cpp)
|
|
{
|
|
draw_impl(command_buffer, pipeline, pipeline_layout, descriptor_set);
|
|
}
|
|
else
|
|
{
|
|
draw_impl(static_cast<vk::CommandBuffer>(command_buffer),
|
|
static_cast<vk::Pipeline>(pipeline),
|
|
static_cast<vk::PipelineLayout>(pipeline_layout),
|
|
static_cast<vk::DescriptorSet>(descriptor_set));
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void
|
|
Gui<bindingType>::draw_impl(vk::CommandBuffer command_buffer, vk::Pipeline pipeline, vk::PipelineLayout pipeline_layout, vk::DescriptorSet descriptor_set)
|
|
{
|
|
if (!visible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImDrawData *draw_data = ImGui::GetDrawData();
|
|
if ((!draw_data) || (draw_data->CmdListsCount == 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, descriptor_set, {});
|
|
|
|
// Push constants
|
|
auto &io = ImGui::GetIO();
|
|
auto push_transform = glm::mat4(1.0f);
|
|
push_transform = glm::translate(push_transform, glm::vec3(-1.0f, -1.0f, 0.0f));
|
|
push_transform = glm::scale(push_transform, glm::vec3(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y, 0.0f));
|
|
command_buffer.pushConstants(pipeline_layout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4), &push_transform);
|
|
|
|
vk::DeviceSize vertex_offsets[1] = {0};
|
|
vk::Buffer vertex_buffer_handle = vertex_buffer->get_handle();
|
|
command_buffer.bindVertexBuffers(0, vertex_buffer_handle, vertex_offsets);
|
|
|
|
command_buffer.bindIndexBuffer(index_buffer->get_handle(), 0, vk::IndexType::eUint16);
|
|
|
|
int32_t vertex_offset = 0;
|
|
int32_t index_offset = 0;
|
|
for (auto const *cmd_list : draw_data->CmdLists)
|
|
{
|
|
for (auto const &cmd : cmd_list->CmdBuffer)
|
|
{
|
|
vk::Rect2D scissor_rect;
|
|
scissor_rect.offset.x = std::max(static_cast<int32_t>(cmd.ClipRect.x), 0);
|
|
scissor_rect.offset.y = std::max(static_cast<int32_t>(cmd.ClipRect.y), 0);
|
|
scissor_rect.extent.width = static_cast<uint32_t>(cmd.ClipRect.z - cmd.ClipRect.x);
|
|
scissor_rect.extent.height = static_cast<uint32_t>(cmd.ClipRect.w - cmd.ClipRect.y);
|
|
|
|
command_buffer.setScissor(0, scissor_rect);
|
|
command_buffer.drawIndexed(cmd.ElemCount, 1, index_offset, vertex_offset, 0);
|
|
index_offset += cmd.ElemCount;
|
|
}
|
|
#if defined(PLATFORM__MACOS) && TARGET_OS_IOS && TARGET_OS_SIMULATOR
|
|
// iOS Simulator does not support vkCmdDrawIndexed() with vertex_offset > 0, so rebind vertex buffer instead
|
|
vertex_offsets[0] += cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
|
|
command_buffer.bindVertexBuffers(0, vertex_buffer_handle, vertex_offsets);
|
|
#else
|
|
vertex_offset += cmd_list->VtxBuffer.Size;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::draw(vkb::core::CommandBuffer<bindingType> &command_buffer)
|
|
{
|
|
if constexpr (bindingType == BindingType::Cpp)
|
|
{
|
|
draw_impl(command_buffer);
|
|
}
|
|
else
|
|
{
|
|
draw_impl(reinterpret_cast<vkb::core::CommandBufferCpp &>(command_buffer));
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::draw_impl(vkb::core::CommandBufferCpp &command_buffer)
|
|
{
|
|
if (!visible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Render commands
|
|
ImDrawData *draw_data = ImGui::GetDrawData();
|
|
if (!draw_data || draw_data->CmdListsCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
vkb::core::HPPScopedDebugLabel debug_label{command_buffer, "GUI"};
|
|
|
|
// Vertex input state
|
|
vk::VertexInputBindingDescription vertex_input_binding = {.stride = sizeof(ImDrawVert)};
|
|
|
|
// Location 0: Position
|
|
vk::VertexInputAttributeDescription pos_attr = {.location = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(ImDrawVert, pos)};
|
|
|
|
// Location 1: UV
|
|
vk::VertexInputAttributeDescription uv_attr = {.location = 1, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(ImDrawVert, uv)};
|
|
|
|
// Location 2: Color
|
|
vk::VertexInputAttributeDescription col_attr = {.location = 2, .format = vk::Format::eR8G8B8A8Unorm, .offset = offsetof(ImDrawVert, col)};
|
|
|
|
vkb::rendering::HPPVertexInputState vertex_input_state = {.bindings = {vertex_input_binding}, .attributes = {pos_attr, uv_attr, col_attr}};
|
|
|
|
command_buffer.set_vertex_input_state(vertex_input_state);
|
|
|
|
// Blend state
|
|
vkb::rendering::HPPColorBlendAttachmentState color_attachment = {.blend_enable = true,
|
|
.src_color_blend_factor = vk::BlendFactor::eSrcAlpha,
|
|
.dst_color_blend_factor = vk::BlendFactor::eOneMinusSrcAlpha,
|
|
.src_alpha_blend_factor = vk::BlendFactor::eOneMinusSrcAlpha,
|
|
.color_write_mask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
|
vk::ColorComponentFlagBits::eB};
|
|
|
|
vkb::rendering::HPPColorBlendState blend_state{.attachments = {color_attachment}};
|
|
|
|
command_buffer.set_color_blend_state(blend_state);
|
|
|
|
vkb::rendering::HPPRasterizationState rasterization_state = {.cull_mode = vk::CullModeFlagBits::eNone};
|
|
command_buffer.set_rasterization_state(rasterization_state);
|
|
|
|
vkb::rendering::HPPDepthStencilState depth_state = {.depth_test_enable = false, .depth_write_enable = false};
|
|
command_buffer.set_depth_stencil_state(depth_state);
|
|
|
|
// Bind pipeline layout
|
|
command_buffer.bind_pipeline_layout(*pipeline_layout);
|
|
|
|
command_buffer.bind_image(*font_image_view, *sampler, 0, 0, 0);
|
|
|
|
// Pre-rotation
|
|
auto push_transform = glm::mat4(1.0f);
|
|
|
|
if (render_context.has_swapchain())
|
|
{
|
|
auto transform = render_context.get_swapchain().get_transform();
|
|
|
|
glm::vec3 rotation_axis = glm::vec3(0.0f, 0.0f, 1.0f);
|
|
if (transform & vk::SurfaceTransformFlagBitsKHR::eRotate90)
|
|
{
|
|
push_transform = glm::rotate(push_transform, glm::radians(90.0f), rotation_axis);
|
|
}
|
|
else if (transform & vk::SurfaceTransformFlagBitsKHR::eRotate270)
|
|
{
|
|
push_transform = glm::rotate(push_transform, glm::radians(270.0f), rotation_axis);
|
|
}
|
|
else if (transform & vk::SurfaceTransformFlagBitsKHR::eRotate180)
|
|
{
|
|
push_transform = glm::rotate(push_transform, glm::radians(180.0f), rotation_axis);
|
|
}
|
|
}
|
|
|
|
// GUI coordinate space to screen space
|
|
auto &io = ImGui::GetIO();
|
|
push_transform = glm::translate(push_transform, glm::vec3(-1.0f, -1.0f, 0.0f));
|
|
push_transform = glm::scale(push_transform, glm::vec3(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y, 0.0f));
|
|
|
|
// Push constants
|
|
command_buffer.push_constants(push_transform);
|
|
|
|
std::vector<std::reference_wrapper<const vkb::core::BufferCpp>> vertex_buffers;
|
|
std::vector<vk::DeviceSize> vertex_offsets;
|
|
|
|
// If a render context is used, then use the frames buffer pools to allocate GUI vertex/index data from
|
|
if (!explicit_update)
|
|
{
|
|
// Save vertex buffer allocation in case we need to rebind with vertex_offset, e.g. for iOS Simulator
|
|
auto vertex_allocation = update_buffers(command_buffer);
|
|
if (!vertex_allocation.empty())
|
|
{
|
|
vertex_buffers.push_back(vertex_allocation.get_buffer());
|
|
vertex_offsets.push_back(vertex_allocation.get_offset());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vertex_buffers.push_back(*vertex_buffer);
|
|
vertex_offsets.push_back(0);
|
|
command_buffer.bind_vertex_buffers(0, vertex_buffers, vertex_offsets);
|
|
command_buffer.bind_index_buffer(*index_buffer, 0, vk::IndexType::eUint16);
|
|
}
|
|
|
|
int32_t vertex_offset = 0;
|
|
uint32_t index_offset = 0;
|
|
for (auto const *cmd_list : draw_data->CmdLists)
|
|
{
|
|
for (auto const &cmd : cmd_list->CmdBuffer)
|
|
{
|
|
vk::Rect2D scissor_rect;
|
|
scissor_rect.offset.x = std::max(static_cast<int32_t>(cmd.ClipRect.x), 0);
|
|
scissor_rect.offset.y = std::max(static_cast<int32_t>(cmd.ClipRect.y), 0);
|
|
scissor_rect.extent.width = static_cast<uint32_t>(cmd.ClipRect.z - cmd.ClipRect.x);
|
|
scissor_rect.extent.height = static_cast<uint32_t>(cmd.ClipRect.w - cmd.ClipRect.y);
|
|
|
|
// Adjust for pre-rotation if necessary
|
|
if (render_context.has_swapchain())
|
|
{
|
|
auto transform = render_context.get_swapchain().get_transform();
|
|
if (transform & vk::SurfaceTransformFlagBitsKHR::eRotate90)
|
|
{
|
|
scissor_rect.offset.x = static_cast<uint32_t>(io.DisplaySize.y - cmd.ClipRect.w);
|
|
scissor_rect.offset.y = static_cast<uint32_t>(cmd.ClipRect.x);
|
|
scissor_rect.extent.width = static_cast<uint32_t>(cmd.ClipRect.w - cmd.ClipRect.y);
|
|
scissor_rect.extent.height = static_cast<uint32_t>(cmd.ClipRect.z - cmd.ClipRect.x);
|
|
}
|
|
else if (transform & vk::SurfaceTransformFlagBitsKHR::eRotate180)
|
|
{
|
|
scissor_rect.offset.x = static_cast<uint32_t>(io.DisplaySize.x - cmd.ClipRect.z);
|
|
scissor_rect.offset.y = static_cast<uint32_t>(io.DisplaySize.y - cmd.ClipRect.w);
|
|
scissor_rect.extent.width = static_cast<uint32_t>(cmd.ClipRect.z - cmd.ClipRect.x);
|
|
scissor_rect.extent.height = static_cast<uint32_t>(cmd.ClipRect.w - cmd.ClipRect.y);
|
|
}
|
|
else if (transform & vk::SurfaceTransformFlagBitsKHR::eRotate270)
|
|
{
|
|
scissor_rect.offset.x = static_cast<uint32_t>(cmd.ClipRect.y);
|
|
scissor_rect.offset.y = static_cast<uint32_t>(io.DisplaySize.x - cmd.ClipRect.z);
|
|
scissor_rect.extent.width = static_cast<uint32_t>(cmd.ClipRect.w - cmd.ClipRect.y);
|
|
scissor_rect.extent.height = static_cast<uint32_t>(cmd.ClipRect.z - cmd.ClipRect.x);
|
|
}
|
|
}
|
|
|
|
command_buffer.set_scissor(0, {scissor_rect});
|
|
command_buffer.draw_indexed(cmd.ElemCount, 1, index_offset, vertex_offset, 0);
|
|
index_offset += cmd.ElemCount;
|
|
}
|
|
#if defined(PLATFORM__MACOS) && TARGET_OS_IOS && TARGET_OS_SIMULATOR
|
|
// iOS Simulator does not support vkCmdDrawIndexed() with vertex_offset > 0, so rebind vertex buffer instead
|
|
if (!vertex_offsets.empty())
|
|
{
|
|
vertex_offsets.back() += cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
|
|
command_buffer.bind_vertex_buffers(0, vertex_buffers, vertex_offsets);
|
|
}
|
|
#else
|
|
vertex_offset += cmd_list->VtxBuffer.Size;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline Drawer &Gui<bindingType>::get_drawer()
|
|
{
|
|
return drawer;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline Font &Gui<bindingType>::get_font(const std::string &font_name)
|
|
{
|
|
assert(!fonts.empty() && "No fonts exist");
|
|
|
|
auto it = std::ranges::find_if(fonts, [&font_name](Font &font) { return font.name == font_name; });
|
|
|
|
if (it != fonts.end())
|
|
{
|
|
return *it;
|
|
}
|
|
else
|
|
{
|
|
LOGW("Couldn't find font with name {}", font_name);
|
|
return *fonts.begin();
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline vk::ImageView Gui<bindingType>::get_font_image_view() const
|
|
{
|
|
return font_image_view->get_handle();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline vk::Sampler Gui<bindingType>::get_sampler() const
|
|
{
|
|
return sampler->get_handle();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline typename Gui<bindingType>::StatsView &Gui<bindingType>::get_stats_view()
|
|
{
|
|
return stats_view;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline bool Gui<bindingType>::input_event(const InputEvent &input_event)
|
|
{
|
|
auto &io = ImGui::GetIO();
|
|
auto capture_move_event = false;
|
|
|
|
if (input_event.get_source() == EventSource::Keyboard)
|
|
{
|
|
const auto &key_event = static_cast<const KeyInputEvent &>(input_event);
|
|
|
|
if (key_event.get_action() == KeyAction::Down)
|
|
{
|
|
io.KeysDown[static_cast<int>(key_event.get_code())] = true;
|
|
}
|
|
else if (key_event.get_action() == KeyAction::Up)
|
|
{
|
|
io.KeysDown[static_cast<int>(key_event.get_code())] = false;
|
|
}
|
|
}
|
|
else if (input_event.get_source() == EventSource::Mouse)
|
|
{
|
|
const auto &mouse_button = static_cast<const MouseButtonInputEvent &>(input_event);
|
|
|
|
io.MousePos = ImVec2{mouse_button.get_pos_x() * content_scale_factor,
|
|
mouse_button.get_pos_y() * content_scale_factor};
|
|
|
|
auto button_id = static_cast<int>(mouse_button.get_button());
|
|
|
|
if (mouse_button.get_action() == MouseAction::Down)
|
|
{
|
|
io.MouseDown[button_id] = true;
|
|
}
|
|
else if (mouse_button.get_action() == MouseAction::Up)
|
|
{
|
|
io.MouseDown[button_id] = false;
|
|
}
|
|
else if (mouse_button.get_action() == MouseAction::Move)
|
|
{
|
|
capture_move_event = io.WantCaptureMouse;
|
|
}
|
|
}
|
|
else if (input_event.get_source() == EventSource::Touchscreen)
|
|
{
|
|
const auto &touch_event = static_cast<const TouchInputEvent &>(input_event);
|
|
|
|
io.MousePos = ImVec2{touch_event.get_pos_x(), touch_event.get_pos_y()};
|
|
|
|
if (touch_event.get_action() == TouchAction::Down)
|
|
{
|
|
io.MouseDown[touch_event.get_pointer_id()] = true;
|
|
}
|
|
else if (touch_event.get_action() == TouchAction::Up)
|
|
{
|
|
io.MouseDown[touch_event.get_pointer_id()] = false;
|
|
}
|
|
else if (touch_event.get_action() == TouchAction::Move)
|
|
{
|
|
capture_move_event = io.WantCaptureMouse;
|
|
}
|
|
}
|
|
|
|
// Toggle debug UI view when tap or clicking outside the GUI windows
|
|
if (!io.WantCaptureMouse)
|
|
{
|
|
bool press_down =
|
|
(input_event.get_source() == EventSource::Mouse && static_cast<const MouseButtonInputEvent &>(input_event).get_action() == MouseAction::Down) ||
|
|
(input_event.get_source() == EventSource::Touchscreen && static_cast<const TouchInputEvent &>(input_event).get_action() == TouchAction::Down);
|
|
bool press_up =
|
|
(input_event.get_source() == EventSource::Mouse && static_cast<const MouseButtonInputEvent &>(input_event).get_action() == MouseAction::Up) ||
|
|
(input_event.get_source() == EventSource::Touchscreen && static_cast<const TouchInputEvent &>(input_event).get_action() == TouchAction::Up);
|
|
assert(!(press_down && press_up)); // can't be both, up and down at the same time
|
|
|
|
if (press_down)
|
|
{
|
|
timer.start();
|
|
if (input_event.get_source() == EventSource::Touchscreen)
|
|
{
|
|
const auto &touch_event = static_cast<const TouchInputEvent &>(input_event);
|
|
if (touch_event.get_touch_points() == 2)
|
|
{
|
|
two_finger_tap = true;
|
|
}
|
|
}
|
|
}
|
|
else if (press_up)
|
|
{
|
|
auto press_delta = timer.stop<Timer::Milliseconds>();
|
|
if (press_delta < press_time_ms)
|
|
{
|
|
if (input_event.get_source() == EventSource::Mouse)
|
|
{
|
|
const auto &mouse_button = static_cast<const MouseButtonInputEvent &>(input_event);
|
|
if (mouse_button.get_button() == MouseButton::Right)
|
|
{
|
|
debug_view.active = !debug_view.active;
|
|
}
|
|
}
|
|
else if (input_event.get_source() == EventSource::Touchscreen)
|
|
{
|
|
const auto &touch_event = static_cast<const TouchInputEvent &>(input_event);
|
|
if (two_finger_tap && touch_event.get_touch_points() == 2)
|
|
{
|
|
debug_view.active = !debug_view.active;
|
|
}
|
|
else
|
|
{
|
|
two_finger_tap = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return capture_move_event;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline bool Gui<bindingType>::is_debug_view_active() const
|
|
{
|
|
return debug_view.active;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::new_frame()
|
|
{
|
|
ImGui::NewFrame();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::prepare(PipelineCacheType pipeline_cache,
|
|
RenderPassType render_pass,
|
|
std::vector<PipelineShaderStageCreateInfoType> const &shader_stages)
|
|
{
|
|
if constexpr (bindingType == BindingType::Cpp)
|
|
{
|
|
prepare_impl(pipeline_cache, render_pass, shader_stages);
|
|
}
|
|
else
|
|
{
|
|
prepare_impl(static_cast<vk::PipelineCache>(pipeline_cache),
|
|
static_cast<vk::RenderPass>(render_pass),
|
|
reinterpret_cast<std::vector<vk::PipelineShaderStageCreateInfo> const &>(shader_stages));
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::prepare_impl(vk::PipelineCache pipeline_cache,
|
|
vk::RenderPass render_pass,
|
|
std::vector<vk::PipelineShaderStageCreateInfo> const &shader_stages)
|
|
{
|
|
vk::Device const &device = render_context.get_device().get_handle();
|
|
|
|
// Descriptor pool
|
|
vk::DescriptorPoolSize pool_size = {.type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1};
|
|
vk::DescriptorPoolCreateInfo descriptorPoolInfo = {.maxSets = 2, .poolSizeCount = 1, .pPoolSizes = &pool_size};
|
|
descriptor_pool = device.createDescriptorPool(descriptorPoolInfo);
|
|
|
|
// Descriptor set layout
|
|
vk::DescriptorSetLayoutBinding layout_binding = {
|
|
.binding = 0, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment};
|
|
vk::DescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = {.bindingCount = 1, .pBindings = &layout_binding};
|
|
descriptor_set_layout = device.createDescriptorSetLayout(descriptor_set_layout_create_info);
|
|
|
|
// Descriptor set
|
|
vk::DescriptorSetAllocateInfo descriptor_allocation = {.descriptorPool = descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = &descriptor_set_layout};
|
|
descriptor_set = device.allocateDescriptorSets(descriptor_allocation).front();
|
|
|
|
// Update descriptor set with font image
|
|
vk::DescriptorImageInfo font_descriptor = {.sampler = sampler->get_handle(),
|
|
.imageView = font_image_view->get_handle(),
|
|
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal};
|
|
vk::WriteDescriptorSet write_descriptor_set = {
|
|
.dstSet = descriptor_set, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &font_descriptor};
|
|
device.updateDescriptorSets(write_descriptor_set, nullptr);
|
|
|
|
// Setup graphics pipeline for UI rendering
|
|
vk::PipelineInputAssemblyStateCreateInfo input_assembly_state = {.topology = vk::PrimitiveTopology::eTriangleList};
|
|
|
|
vk::PipelineRasterizationStateCreateInfo rasterization_state = {
|
|
.polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eNone, .frontFace = vk::FrontFace::eCounterClockwise, .lineWidth = 1.0f};
|
|
|
|
// Enable blending
|
|
vk::PipelineColorBlendAttachmentState blend_attachment_state = {.blendEnable = true,
|
|
.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha,
|
|
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
|
|
.colorBlendOp = vk::BlendOp::eAdd,
|
|
.srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
|
|
.dstAlphaBlendFactor = vk::BlendFactor::eZero,
|
|
.alphaBlendOp = vk::BlendOp::eAdd,
|
|
.colorWriteMask = vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags};
|
|
vk::PipelineColorBlendStateCreateInfo color_blend_state = {.attachmentCount = 1, .pAttachments = &blend_attachment_state};
|
|
|
|
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state = {.depthTestEnable = false,
|
|
.depthWriteEnable = false,
|
|
.depthCompareOp = vk::CompareOp::eAlways,
|
|
.back = {.compareOp = vk::CompareOp::eAlways}};
|
|
|
|
vk::PipelineViewportStateCreateInfo viewport_state = {.viewportCount = 1, .scissorCount = 1};
|
|
|
|
vk::PipelineMultisampleStateCreateInfo multisample_state = {.rasterizationSamples = vk::SampleCountFlagBits::e1};
|
|
|
|
std::array<vk::DynamicState, 2> dynamic_state_enables = {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
|
|
vk::PipelineDynamicStateCreateInfo dynamic_state = {.dynamicStateCount = static_cast<uint32_t>(dynamic_state_enables.size()),
|
|
.pDynamicStates = dynamic_state_enables.data()};
|
|
|
|
// Vertex bindings an attributes based on ImGui vertex definition
|
|
vk::VertexInputBindingDescription vertex_input_binding = {.binding = 0, .stride = sizeof(ImDrawVert), .inputRate = vk::VertexInputRate::eVertex};
|
|
std::array<vk::VertexInputAttributeDescription, 3> vertex_input_attributes = {
|
|
{{.location = 0, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(ImDrawVert, pos)},
|
|
{.location = 1, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(ImDrawVert, uv)},
|
|
{.location = 2, .binding = 0, .format = vk::Format::eR8G8B8A8Unorm, .offset = offsetof(ImDrawVert, col)}}};
|
|
vk::PipelineVertexInputStateCreateInfo vertex_input_state_create_info = {.vertexBindingDescriptionCount = 1,
|
|
.pVertexBindingDescriptions = &vertex_input_binding,
|
|
.vertexAttributeDescriptionCount =
|
|
static_cast<uint32_t>(vertex_input_attributes.size()),
|
|
.pVertexAttributeDescriptions = vertex_input_attributes.data()};
|
|
|
|
vk::GraphicsPipelineCreateInfo pipeline_create_info = {.stageCount = static_cast<uint32_t>(shader_stages.size()),
|
|
.pStages = shader_stages.data(),
|
|
.pVertexInputState = &vertex_input_state_create_info,
|
|
.pInputAssemblyState = &input_assembly_state,
|
|
.pViewportState = &viewport_state,
|
|
.pRasterizationState = &rasterization_state,
|
|
.pMultisampleState = &multisample_state,
|
|
.pDepthStencilState = &depth_stencil_state,
|
|
.pColorBlendState = &color_blend_state,
|
|
.pDynamicState = &dynamic_state,
|
|
.layout = pipeline_layout->get_handle(),
|
|
.renderPass = render_pass,
|
|
.subpass = subpass,
|
|
.basePipelineIndex = -1};
|
|
|
|
vk::Result result;
|
|
std::tie(result, pipeline) = device.createGraphicsPipeline(pipeline_cache, pipeline_create_info);
|
|
assert(result == vk::Result::eSuccess);
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::resize(const uint32_t width, const uint32_t height) const
|
|
{
|
|
auto &io = ImGui::GetIO();
|
|
io.DisplaySize.x = static_cast<float>(width);
|
|
io.DisplaySize.y = static_cast<float>(height);
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::set_subpass(const uint32_t subpass)
|
|
{
|
|
this->subpass = subpass;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::show_app_info(const std::string &app_name)
|
|
{
|
|
// Sample name
|
|
ImGui::Text("%s", app_name.c_str());
|
|
|
|
// GPU name
|
|
auto &device = render_context.get_device();
|
|
auto device_name_label = "GPU: " + std::string(device.get_gpu().get_properties().deviceName.data());
|
|
ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize(device_name_label.c_str()).x);
|
|
ImGui::Text("%s", device_name_label.c_str());
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::show_debug_window(DebugInfo &debug_info, const ImVec2 &position)
|
|
{
|
|
auto &io = ImGui::GetIO();
|
|
auto &style = ImGui::GetStyle();
|
|
auto &font = get_font(default_window_font);
|
|
|
|
// Calculate only once
|
|
if (debug_view.label_column_width == 0)
|
|
{
|
|
debug_view.label_column_width = style.ItemInnerSpacing.x + debug_info.get_longest_label() * font.size / debug_view.scale;
|
|
}
|
|
|
|
ImGui::SetNextWindowBgAlpha(overlay_alpha);
|
|
ImGui::SetNextWindowPos(position, ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowContentSize(ImVec2{io.DisplaySize.x, 0.0f});
|
|
|
|
bool is_open = true;
|
|
const ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
|
|
|
|
ImGui::Begin("Debug Window", &is_open, flags);
|
|
ImGui::PushFont(font.handle);
|
|
|
|
auto field_count = debug_info.get_fields().size() > debug_view.max_fields ? debug_view.max_fields : debug_info.get_fields().size();
|
|
|
|
ImGui::BeginChild("Table", ImVec2(0, field_count * (font.size + style.ItemSpacing.y)), false);
|
|
ImGui::Columns(2);
|
|
ImGui::SetColumnWidth(0, debug_view.label_column_width);
|
|
ImGui::SetColumnWidth(1, io.DisplaySize.x - debug_view.label_column_width);
|
|
for (auto &field : debug_info.get_fields())
|
|
{
|
|
const std::string &label = field->label;
|
|
const std::string &value = field->to_string();
|
|
ImGui::Text("%s", label.c_str());
|
|
ImGui::NextColumn();
|
|
ImGui::Text(" %s", value.c_str());
|
|
ImGui::NextColumn();
|
|
}
|
|
ImGui::Columns(1);
|
|
ImGui::EndChild();
|
|
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::show_options_window(std::function<void()> body, const uint32_t lines)
|
|
{
|
|
// Add padding around the text so that the options are not
|
|
// too close to the edges and are easier to interact with.
|
|
// Also add double vertical padding to avoid rounded corners.
|
|
const float window_padding = ImGui::CalcTextSize("T").x;
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{window_padding, window_padding * 2.0f});
|
|
auto window_height = lines * ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 2.0f;
|
|
auto window_width = ImGui::GetIO().DisplaySize.x;
|
|
ImGui::SetNextWindowBgAlpha(overlay_alpha);
|
|
const ImVec2 size = ImVec2(window_width, 0);
|
|
ImGui::SetNextWindowSize(size, ImGuiCond_Always);
|
|
const ImVec2 pos = ImVec2(0.0f, ImGui::GetIO().DisplaySize.y - window_height);
|
|
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
|
|
const ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoSavedSettings |
|
|
ImGuiWindowFlags_NoFocusOnAppearing;
|
|
bool is_open = true;
|
|
ImGui::Begin("Options", &is_open, flags);
|
|
body();
|
|
ImGui::End();
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::show_simple_window(const std::string &name, uint32_t last_fps, std::function<void()> body)
|
|
{
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
ImGui::NewFrame();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
|
ImGui::SetNextWindowPos(ImVec2(10, 10));
|
|
ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_FirstUseEver);
|
|
ImGui::Begin("Vulkan Example", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
|
|
ImGui::TextUnformatted(name.c_str());
|
|
ImGui::TextUnformatted(std::string(render_context.get_device().get_gpu().get_properties().deviceName.data()).c_str());
|
|
ImGui::Text("%.2f ms/frame (%.1d fps)", (1000.0f / last_fps), last_fps);
|
|
ImGui::PushItemWidth(110.0f * dpi_factor);
|
|
|
|
body();
|
|
|
|
ImGui::PopItemWidth();
|
|
ImGui::End();
|
|
ImGui::PopStyleVar();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::show_stats(const StatsType &stats)
|
|
{
|
|
for (const auto &stat_index : stats.get_requested_stats())
|
|
{
|
|
// Draw graph
|
|
auto &graph_data = stats_view.get_stat_graph_data(stat_index);
|
|
const auto &graph_elements = stats.get_data(stat_index);
|
|
float graph_min = 0.0f;
|
|
float graph_max = graph_data.max_value;
|
|
|
|
if (!graph_data.has_fixed_max)
|
|
{
|
|
auto new_max = *std::max_element(graph_elements.begin(), graph_elements.end()) * stats_view.get_top_padding();
|
|
if (new_max > graph_max)
|
|
{
|
|
graph_max = new_max;
|
|
}
|
|
}
|
|
|
|
const ImVec2 graph_size = ImVec2{ImGui::GetIO().DisplaySize.x, stats_view.get_graph_height() /* dpi */ * dpi_factor};
|
|
|
|
std::stringstream graph_label;
|
|
float avg = std::accumulate(graph_elements.begin(), graph_elements.end(), 0.0f) / graph_elements.size();
|
|
|
|
// Check if the stat is available in the current platform
|
|
if (stats.is_available(stat_index))
|
|
{
|
|
auto graph_value = avg * graph_data.scale_factor;
|
|
graph_label << fmt::vformat(graph_data.name + ": " + graph_data.format, fmt::make_format_args(graph_value));
|
|
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
|
ImGui::PlotLines("", &graph_elements[0], static_cast<int>(graph_elements.size()), 0, graph_label.str().c_str(), graph_min, graph_max, graph_size);
|
|
ImGui::PopItemFlag();
|
|
}
|
|
else
|
|
{
|
|
graph_label << graph_data.name << ": not available";
|
|
ImGui::Text("%s", graph_label.str().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::show_top_window(const std::string &app_name, const StatsType *stats, DebugInfo *debug_info)
|
|
{
|
|
// Transparent background
|
|
ImGui::SetNextWindowBgAlpha(overlay_alpha);
|
|
ImVec2 size{ImGui::GetIO().DisplaySize.x, 0.0f};
|
|
ImGui::SetNextWindowSize(size, ImGuiCond_Always);
|
|
|
|
// Top left
|
|
ImVec2 pos{0.0f, 0.0f};
|
|
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
|
|
|
|
bool is_open = true;
|
|
ImGui::Begin("Top", &is_open, common_flags);
|
|
|
|
show_app_info(app_name);
|
|
|
|
if (stats)
|
|
{
|
|
show_stats(*stats);
|
|
|
|
// Reset max values if user taps on this window
|
|
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0 /* left */))
|
|
{
|
|
stats_view.reset_max_values();
|
|
}
|
|
}
|
|
|
|
if (debug_info)
|
|
{
|
|
if (debug_view.active)
|
|
{
|
|
show_debug_window(*debug_info, ImVec2{0, ImGui::GetWindowSize().y});
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::update(const float delta_time)
|
|
{
|
|
if (visible != prev_visible)
|
|
{
|
|
drawer.set_dirty(true);
|
|
prev_visible = visible;
|
|
}
|
|
|
|
if (!visible)
|
|
{
|
|
ImGui::EndFrame();
|
|
return;
|
|
}
|
|
|
|
// Update imGui
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
auto extent = render_context.get_surface_extent();
|
|
resize(extent.width, extent.height);
|
|
io.DeltaTime = delta_time;
|
|
|
|
// Render to generate draw buffers
|
|
ImGui::Render();
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline bool Gui<bindingType>::update_buffers()
|
|
{
|
|
ImDrawData *draw_data = ImGui::GetDrawData();
|
|
|
|
if (!draw_data)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
size_t vertex_buffer_size = draw_data->TotalVtxCount * sizeof(ImDrawVert);
|
|
size_t index_buffer_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx);
|
|
|
|
if ((vertex_buffer_size == 0) || (index_buffer_size == 0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool updated = false;
|
|
if (!vertex_buffer->get_handle() || (vertex_buffer_size != vertex_buffer->get_size()))
|
|
{
|
|
vertex_buffer.reset();
|
|
vertex_buffer = std::make_unique<vkb::core::BufferCpp>(render_context.get_device(), vertex_buffer_size,
|
|
vk::BufferUsageFlagBits::eVertexBuffer,
|
|
VMA_MEMORY_USAGE_GPU_TO_CPU);
|
|
vertex_buffer->set_debug_name("GUI vertex buffer");
|
|
updated = true;
|
|
}
|
|
|
|
if (!index_buffer->get_handle() || (index_buffer_size != index_buffer->get_size()))
|
|
{
|
|
index_buffer.reset();
|
|
index_buffer = std::make_unique<vkb::core::BufferCpp>(render_context.get_device(), index_buffer_size,
|
|
vk::BufferUsageFlagBits::eIndexBuffer,
|
|
VMA_MEMORY_USAGE_GPU_TO_CPU);
|
|
index_buffer->set_debug_name("GUI index buffer");
|
|
updated = true;
|
|
}
|
|
|
|
// Upload data
|
|
upload_draw_data(draw_data, vertex_buffer->map(), index_buffer->map());
|
|
|
|
vertex_buffer->flush();
|
|
index_buffer->flush();
|
|
|
|
vertex_buffer->unmap();
|
|
index_buffer->unmap();
|
|
|
|
return updated;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline vkb::BufferAllocationCpp Gui<bindingType>::update_buffers(vkb::core::CommandBufferCpp &command_buffer)
|
|
{
|
|
ImDrawData *draw_data = ImGui::GetDrawData();
|
|
|
|
if (!draw_data)
|
|
{
|
|
return vkb::BufferAllocationCpp{};
|
|
}
|
|
|
|
size_t vertex_buffer_size = draw_data->TotalVtxCount * sizeof(ImDrawVert);
|
|
size_t index_buffer_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx);
|
|
|
|
if ((vertex_buffer_size == 0) || (index_buffer_size == 0))
|
|
{
|
|
return vkb::BufferAllocationCpp{};
|
|
}
|
|
|
|
std::vector<uint8_t> vertex_data(vertex_buffer_size);
|
|
std::vector<uint8_t> index_data(index_buffer_size);
|
|
|
|
upload_draw_data(draw_data, vertex_data.data(), index_data.data());
|
|
|
|
auto vertex_allocation = render_context.get_active_frame().allocate_buffer(vk::BufferUsageFlagBits::eVertexBuffer, vertex_buffer_size);
|
|
|
|
vertex_allocation.update(vertex_data);
|
|
|
|
std::vector<std::reference_wrapper<const vkb::core::BufferCpp>> buffers;
|
|
buffers.emplace_back(std::ref(vertex_allocation.get_buffer()));
|
|
|
|
std::vector<VkDeviceSize> offsets{vertex_allocation.get_offset()};
|
|
|
|
command_buffer.bind_vertex_buffers(0, buffers, offsets);
|
|
|
|
auto index_allocation = render_context.get_active_frame().allocate_buffer(vk::BufferUsageFlagBits::eIndexBuffer, index_buffer_size);
|
|
|
|
index_allocation.update(index_data);
|
|
|
|
command_buffer.bind_index_buffer(index_allocation.get_buffer(), index_allocation.get_offset(), vk::IndexType::eUint16);
|
|
|
|
return vertex_allocation;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::upload_draw_data(const ImDrawData *draw_data, uint8_t *vertex_data, uint8_t *index_data)
|
|
{
|
|
ImDrawVert *vtx_dst = reinterpret_cast<ImDrawVert *>(vertex_data);
|
|
ImDrawIdx *idx_dst = reinterpret_cast<ImDrawIdx *>(index_data);
|
|
|
|
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
|
{
|
|
const ImDrawList *cmd_list = draw_data->CmdLists[n];
|
|
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
|
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
|
vtx_dst += cmd_list->VtxBuffer.Size;
|
|
idx_dst += cmd_list->IdxBuffer.Size;
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline Gui<bindingType>::StatsView::StatsView(const StatsType *stats)
|
|
{
|
|
if (stats == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Request graph data information for each stat and record it in graph_map
|
|
const std::set<StatIndex> &indices = stats->get_requested_stats();
|
|
|
|
for (StatIndex i : indices)
|
|
{
|
|
graph_map[i] = stats->get_graph_data(i);
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline float Gui<bindingType>::StatsView::get_graph_height() const
|
|
{
|
|
return graph_height;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline StatGraphData const &Gui<bindingType>::StatsView::get_stat_graph_data(StatIndex const &stat_index) const
|
|
{
|
|
auto graph_map_it = graph_map.find(stat_index);
|
|
|
|
assert(graph_map_it != graph_map.end() && "StatIndex not implemented in gui graph_map");
|
|
|
|
return graph_map_it->second;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline float Gui<bindingType>::StatsView::get_top_padding() const
|
|
{
|
|
return top_padding;
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::StatsView::reset_max_value(const StatIndex index)
|
|
{
|
|
auto pr = graph_map.find(index);
|
|
if (pr != graph_map.end())
|
|
{
|
|
reset_graph_max_value(pr->second);
|
|
}
|
|
}
|
|
|
|
template <vkb::BindingType bindingType>
|
|
inline void Gui<bindingType>::StatsView::reset_max_values()
|
|
{
|
|
// For every entry in the map
|
|
std::ranges::for_each(graph_map, [](auto &pr) { reset_graph_max_value(pr.second); });
|
|
}
|
|
|
|
} // namespace vkb
|