Files
Vulkan-Samples/framework/gui.h
T
2025-09-04 10:54:47 +08:00

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