shadPS4/src/imgui/renderer/imgui_impl_vulkan.cpp
psucien 8f2883a388
video_out: HDR support (#2381)
* Initial HDR support

* fix for crashes when debug tools used
2025-02-09 15:54:54 +01:00

1284 lines
51 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on imgui_impl_vulkan.cpp from Dear ImGui repository
#include <cstdio>
#include <mutex>
#include <imgui.h>
#include "imgui_impl_vulkan.h"
#ifndef IM_MAX
#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B))
#endif
#define IDX_SIZE sizeof(ImDrawIdx)
namespace ImGui::Vulkan {
struct RenderBuffer {
vk::DeviceMemory buffer_memory{};
vk::DeviceSize buffer_size{};
vk::Buffer buffer{};
};
// Reusable buffers used for rendering 1 current in-flight frame, for RenderDrawData()
struct FrameRenderBuffers {
RenderBuffer vertex;
RenderBuffer index;
};
// Each viewport will hold 1 WindowRenderBuffers
struct WindowRenderBuffers {
uint32_t index{};
uint32_t count{};
std::vector<FrameRenderBuffers> frame_render_buffers{};
};
// Vulkan data
struct VkData {
const InitInfo init_info;
vk::DeviceSize buffer_memory_alignment = 256;
vk::PipelineCreateFlags pipeline_create_flags{};
vk::DescriptorPool descriptor_pool{};
vk::DescriptorSetLayout descriptor_set_layout{};
vk::PipelineLayout pipeline_layout{};
vk::Pipeline pipeline{};
vk::ShaderModule shader_module_vert{};
vk::ShaderModule shader_module_frag{};
std::mutex command_pool_mutex;
vk::CommandPool command_pool{};
vk::Sampler simple_sampler{};
// Font data
vk::DeviceMemory font_memory{};
vk::Image font_image{};
vk::ImageView font_view{};
ImTextureID font_texture{};
vk::CommandBuffer font_command_buffer{};
// Render buffers
WindowRenderBuffers render_buffers{};
bool enabled_blending{true};
VkData(const InitInfo init_info) : init_info(init_info) {
render_buffers.count = init_info.image_count;
render_buffers.frame_render_buffers.resize(render_buffers.count);
}
};
//-----------------------------------------------------------------------------
// SHADERS
//-----------------------------------------------------------------------------
// backends/vulkan/glsl_shader.vert, compiled with:
// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert
/*
#version 450 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aUV;
layout(location = 2) in vec4 aColor;
layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc;
out gl_PerVertex { vec4 gl_Position; };
layout(location = 0) out struct { vec4 Color; vec2 UV; } Out;
void main()
{
Out.Color = aColor;
Out.UV = aUV;
gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1);
}
*/
static uint32_t glsl_shader_vert_spv[] = {
0x07230203, 0x00010000, 0x00080001, 0x0000002e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x000a000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000f, 0x00000015,
0x0000001b, 0x0000001c, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d,
0x00000000, 0x00030005, 0x00000009, 0x00000000, 0x00050006, 0x00000009, 0x00000000, 0x6f6c6f43,
0x00000072, 0x00040006, 0x00000009, 0x00000001, 0x00005655, 0x00030005, 0x0000000b, 0x0074754f,
0x00040005, 0x0000000f, 0x6c6f4361, 0x0000726f, 0x00030005, 0x00000015, 0x00565561, 0x00060005,
0x00000019, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x00000019, 0x00000000,
0x505f6c67, 0x7469736f, 0x006e6f69, 0x00030005, 0x0000001b, 0x00000000, 0x00040005, 0x0000001c,
0x736f5061, 0x00000000, 0x00060005, 0x0000001e, 0x73755075, 0x6e6f4368, 0x6e617473, 0x00000074,
0x00050006, 0x0000001e, 0x00000000, 0x61635375, 0x0000656c, 0x00060006, 0x0000001e, 0x00000001,
0x61725475, 0x616c736e, 0x00006574, 0x00030005, 0x00000020, 0x00006370, 0x00040047, 0x0000000b,
0x0000001e, 0x00000000, 0x00040047, 0x0000000f, 0x0000001e, 0x00000002, 0x00040047, 0x00000015,
0x0000001e, 0x00000001, 0x00050048, 0x00000019, 0x00000000, 0x0000000b, 0x00000000, 0x00030047,
0x00000019, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00050048, 0x0000001e,
0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001e, 0x00000001, 0x00000023, 0x00000008,
0x00030047, 0x0000001e, 0x00000002, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002,
0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040017,
0x00000008, 0x00000006, 0x00000002, 0x0004001e, 0x00000009, 0x00000007, 0x00000008, 0x00040020,
0x0000000a, 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, 0x00040015,
0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040020,
0x0000000e, 0x00000001, 0x00000007, 0x0004003b, 0x0000000e, 0x0000000f, 0x00000001, 0x00040020,
0x00000011, 0x00000003, 0x00000007, 0x0004002b, 0x0000000c, 0x00000013, 0x00000001, 0x00040020,
0x00000014, 0x00000001, 0x00000008, 0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00040020,
0x00000017, 0x00000003, 0x00000008, 0x0003001e, 0x00000019, 0x00000007, 0x00040020, 0x0000001a,
0x00000003, 0x00000019, 0x0004003b, 0x0000001a, 0x0000001b, 0x00000003, 0x0004003b, 0x00000014,
0x0000001c, 0x00000001, 0x0004001e, 0x0000001e, 0x00000008, 0x00000008, 0x00040020, 0x0000001f,
0x00000009, 0x0000001e, 0x0004003b, 0x0000001f, 0x00000020, 0x00000009, 0x00040020, 0x00000021,
0x00000009, 0x00000008, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, 0x0004002b, 0x00000006,
0x00000029, 0x3f800000, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8,
0x00000005, 0x0004003d, 0x00000007, 0x00000010, 0x0000000f, 0x00050041, 0x00000011, 0x00000012,
0x0000000b, 0x0000000d, 0x0003003e, 0x00000012, 0x00000010, 0x0004003d, 0x00000008, 0x00000016,
0x00000015, 0x00050041, 0x00000017, 0x00000018, 0x0000000b, 0x00000013, 0x0003003e, 0x00000018,
0x00000016, 0x0004003d, 0x00000008, 0x0000001d, 0x0000001c, 0x00050041, 0x00000021, 0x00000022,
0x00000020, 0x0000000d, 0x0004003d, 0x00000008, 0x00000023, 0x00000022, 0x00050085, 0x00000008,
0x00000024, 0x0000001d, 0x00000023, 0x00050041, 0x00000021, 0x00000025, 0x00000020, 0x00000013,
0x0004003d, 0x00000008, 0x00000026, 0x00000025, 0x00050081, 0x00000008, 0x00000027, 0x00000024,
0x00000026, 0x00050051, 0x00000006, 0x0000002a, 0x00000027, 0x00000000, 0x00050051, 0x00000006,
0x0000002b, 0x00000027, 0x00000001, 0x00070050, 0x00000007, 0x0000002c, 0x0000002a, 0x0000002b,
0x00000028, 0x00000029, 0x00050041, 0x00000011, 0x0000002d, 0x0000001b, 0x0000000d, 0x0003003e,
0x0000002d, 0x0000002c, 0x000100fd, 0x00010038};
// backends/vulkan/glsl_shader.frag, compiled with:
// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag
/*
#version 450 core
layout(location = 0) out vec4 fColor;
layout(set=0, binding=0) uniform sampler2D sTexture;
layout(location = 0) in struct { vec4 Color; vec2 UV; } In;
void main()
{
fColor = In.Color * texture(sTexture, In.UV.st);
}
*/
static uint32_t glsl_shader_frag_spv[] = {
0x07230203, 0x00010000, 0x00080001, 0x0000001e, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x0007000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000d, 0x00030010,
0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d,
0x00000000, 0x00040005, 0x00000009, 0x6c6f4366, 0x0000726f, 0x00030005, 0x0000000b, 0x00000000,
0x00050006, 0x0000000b, 0x00000000, 0x6f6c6f43, 0x00000072, 0x00040006, 0x0000000b, 0x00000001,
0x00005655, 0x00030005, 0x0000000d, 0x00006e49, 0x00050005, 0x00000016, 0x78655473, 0x65727574,
0x00000000, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x0000000d, 0x0000001e,
0x00000000, 0x00040047, 0x00000016, 0x00000022, 0x00000000, 0x00040047, 0x00000016, 0x00000021,
0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006,
0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003,
0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040017, 0x0000000a, 0x00000006,
0x00000002, 0x0004001e, 0x0000000b, 0x00000007, 0x0000000a, 0x00040020, 0x0000000c, 0x00000001,
0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020,
0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001,
0x00000007, 0x00090019, 0x00000013, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x0003001b, 0x00000014, 0x00000013, 0x00040020, 0x00000015, 0x00000000,
0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000000, 0x0004002b, 0x0000000e, 0x00000018,
0x00000001, 0x00040020, 0x00000019, 0x00000001, 0x0000000a, 0x00050036, 0x00000002, 0x00000004,
0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000010, 0x00000011, 0x0000000d,
0x0000000f, 0x0004003d, 0x00000007, 0x00000012, 0x00000011, 0x0004003d, 0x00000014, 0x00000017,
0x00000016, 0x00050041, 0x00000019, 0x0000001a, 0x0000000d, 0x00000018, 0x0004003d, 0x0000000a,
0x0000001b, 0x0000001a, 0x00050057, 0x00000007, 0x0000001c, 0x00000017, 0x0000001b, 0x00050085,
0x00000007, 0x0000001d, 0x00000012, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x000100fd,
0x00010038};
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui
// contexts It is STRONGLY preferred that you use docking branch with multi-viewports (== single
// Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
static VkData* GetBackendData() {
return ImGui::GetCurrentContext() ? (VkData*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
static uint32_t FindMemoryType(vk::MemoryPropertyFlags properties, uint32_t type_bits) {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
const auto prop = v.physical_device.getMemoryProperties();
for (uint32_t i = 0; i < prop.memoryTypeCount; i++)
if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1 << i))
return i;
return 0xFFFFFFFF; // Unable to find memoryType
}
template <typename T>
static T CheckVkResult(vk::ResultValue<T> res) {
if (res.result == vk::Result::eSuccess) {
return res.value;
}
const VkData* bd = GetBackendData();
if (!bd) {
return res.value;
}
const InitInfo& v = bd->init_info;
if (v.check_vk_result_fn) {
v.check_vk_result_fn(res.result);
}
return res.value;
}
static void CheckVkErr(vk::Result res) {
if (res == vk::Result::eSuccess) {
return;
}
const VkData* bd = GetBackendData();
if (!bd) {
return;
}
const InitInfo& v = bd->init_info;
if (v.check_vk_result_fn) {
v.check_vk_result_fn(res);
}
}
// Same as IM_MEMALIGN(). 'alignment' must be a power of two.
static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize alignment) {
return (size + alignment - 1) & ~(alignment - 1);
}
void UploadTextureData::Upload() {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
vk::SubmitInfo submit_info{
.commandBufferCount = 1,
.pCommandBuffers = &command_buffer,
};
CheckVkErr(v.queue.submit({submit_info}));
CheckVkErr(v.queue.waitIdle());
v.device.destroyBuffer(upload_buffer, v.allocator);
v.device.freeMemory(upload_buffer_memory, v.allocator);
{
std::unique_lock lk(bd->command_pool_mutex);
v.device.freeCommandBuffers(bd->command_pool, {command_buffer});
}
upload_buffer = VK_NULL_HANDLE;
upload_buffer_memory = VK_NULL_HANDLE;
}
void UploadTextureData::Destroy() {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
CheckVkErr(v.device.waitIdle());
RemoveTexture(im_texture);
im_texture = nullptr;
v.device.destroyImageView(image_view, v.allocator);
image_view = VK_NULL_HANDLE;
v.device.destroyImage(image, v.allocator);
image = VK_NULL_HANDLE;
v.device.freeMemory(image_memory, v.allocator);
image_memory = VK_NULL_HANDLE;
}
// Register a texture
ImTextureID AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
vk::Sampler sampler) {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
if (sampler == VK_NULL_HANDLE) {
sampler = bd->simple_sampler;
}
// Create Descriptor Set:
vk::DescriptorSet descriptor_set;
{
vk::DescriptorSetAllocateInfo alloc_info{
.descriptorPool = bd->descriptor_pool,
.descriptorSetCount = 1,
.pSetLayouts = &bd->descriptor_set_layout,
};
descriptor_set = CheckVkResult(v.device.allocateDescriptorSets(alloc_info)).front();
}
// Update the Descriptor Set:
{
vk::DescriptorImageInfo desc_image[1]{
{
.sampler = sampler,
.imageView = image_view,
.imageLayout = image_layout,
},
};
vk::WriteDescriptorSet write_desc[1]{
{
.dstSet = descriptor_set,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = desc_image,
},
};
v.device.updateDescriptorSets({write_desc}, {});
}
return new Texture{
.descriptor_set = descriptor_set,
};
}
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
size_t size) {
ImGuiIO& io = GetIO();
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
UploadTextureData info{};
{
std::unique_lock lk(bd->command_pool_mutex);
info.command_buffer =
CheckVkResult(v.device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{
.commandPool = bd->command_pool,
.commandBufferCount = 1,
}))
.front();
CheckVkErr(info.command_buffer.begin(vk::CommandBufferBeginInfo{
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
}));
}
// Create Image
{
vk::ImageCreateInfo image_info{
.imageType = vk::ImageType::e2D,
.format = format,
.extent{
.width = width,
.height = height,
.depth = 1,
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
info.image = CheckVkResult(v.device.createImage(image_info, v.allocator));
auto req = v.device.getImageMemoryRequirements(info.image);
vk::MemoryAllocateInfo alloc_info{
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits),
};
info.image_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
CheckVkErr(v.device.bindImageMemory(info.image, info.image_memory, 0));
}
// Create Image View
{
vk::ImageViewCreateInfo view_info{
.image = info.image,
.viewType = vk::ImageViewType::e2D,
.format = format,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
};
info.image_view = CheckVkResult(v.device.createImageView(view_info, v.allocator));
}
// Create descriptor set (ImTextureID)
info.im_texture = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal);
// Create Upload Buffer
{
vk::BufferCreateInfo buffer_info{
.size = size,
.usage = vk::BufferUsageFlagBits::eTransferSrc,
.sharingMode = vk::SharingMode::eExclusive,
};
info.upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator));
auto req = v.device.getBufferMemoryRequirements(info.upload_buffer);
auto alignemtn = IM_MAX(bd->buffer_memory_alignment, req.alignment);
vk::MemoryAllocateInfo alloc_info{
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
};
info.upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
CheckVkErr(v.device.bindBufferMemory(info.upload_buffer, info.upload_buffer_memory, 0));
}
// Upload to Buffer
{
char* map = (char*)CheckVkResult(v.device.mapMemory(info.upload_buffer_memory, 0, size));
memcpy(map, data, size);
vk::MappedMemoryRange range[1]{
{
.memory = info.upload_buffer_memory,
.size = size,
},
};
CheckVkErr(v.device.flushMappedMemoryRanges(range));
v.device.unmapMemory(info.upload_buffer_memory);
}
// Copy to Image
{
vk::ImageMemoryBarrier copy_barrier[1]{
{
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = info.image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
},
};
info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
{copy_barrier});
vk::BufferImageCopy region{
.imageSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.layerCount = 1,
},
.imageExtent{
.width = width,
.height = height,
.depth = 1,
},
};
info.command_buffer.copyBufferToImage(info.upload_buffer, info.image,
vk::ImageLayout::eTransferDstOptimal, {region});
vk::ImageMemoryBarrier use_barrier[1]{{
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eShaderRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = info.image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
}};
info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
{use_barrier});
}
CheckVkErr(info.command_buffer.end());
return info;
}
void RemoveTexture(ImTextureID texture) {
IM_ASSERT(texture != nullptr);
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
v.device.freeDescriptorSets(bd->descriptor_pool, {texture->descriptor_set});
delete texture;
}
static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) {
VkData* bd = GetBackendData();
IM_ASSERT(bd != nullptr);
const InitInfo& v = bd->init_info;
if (rb.buffer != VK_NULL_HANDLE) {
v.device.destroyBuffer(rb.buffer, v.allocator);
}
if (rb.buffer_memory != VK_NULL_HANDLE) {
v.device.freeMemory(rb.buffer_memory, v.allocator);
}
const vk::DeviceSize buffer_size_aligned =
AlignBufferSize(IM_MAX(v.min_allocation_size, new_size), bd->buffer_memory_alignment);
vk::BufferCreateInfo buffer_info{
.size = buffer_size_aligned,
.usage = usage,
.sharingMode = vk::SharingMode::eExclusive,
};
rb.buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator));
const vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(rb.buffer);
bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment);
vk::MemoryAllocateInfo alloc_info{
.allocationSize = req.size,
.memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
};
rb.buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
CheckVkErr(v.device.bindBufferMemory(rb.buffer, rb.buffer_memory, 0));
rb.buffer_size = buffer_size_aligned;
}
static void SetupRenderState(ImDrawData& draw_data, vk::Pipeline pipeline, vk::CommandBuffer cmdbuf,
FrameRenderBuffers& frb, int fb_width, int fb_height) {
VkData* bd = GetBackendData();
// Bind pipeline:
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
// Bind Vertex And Index Buffer:
if (draw_data.TotalVtxCount > 0) {
vk::Buffer vertex_buffers[1] = {frb.vertex.buffer};
vk::DeviceSize vertex_offset[1] = {0};
cmdbuf.bindVertexBuffers(0, {vertex_buffers}, vertex_offset);
cmdbuf.bindIndexBuffer(frb.index.buffer, 0,
IDX_SIZE == 2 ? vk::IndexType::eUint16 : vk::IndexType::eUint32);
}
// Setup viewport:
{
vk::Viewport viewport{
.x = 0,
.y = 0,
.width = (float)fb_width,
.height = (float)fb_height,
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
cmdbuf.setViewport(0, {viewport});
}
// Setup scale and translation:
// Our visible imgui space lies from draw_data->DisplayPps (top left) to
// draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single
// viewport apps.
{
float scale[2];
scale[0] = 2.0f / draw_data.DisplaySize.x;
scale[1] = 2.0f / draw_data.DisplaySize.y;
float translate[2];
translate[0] = -1.0f - draw_data.DisplayPos.x * scale[0];
translate[1] = -1.0f - draw_data.DisplayPos.y * scale[1];
cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex,
sizeof(float) * 0, sizeof(float) * 2, scale);
cmdbuf.pushConstants(bd->pipeline_layout, vk::ShaderStageFlagBits::eVertex,
sizeof(float) * 2, sizeof(float) * 2, translate);
}
}
// Render function
void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
vk::Pipeline pipeline) {
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates !=
// framebuffer coordinates)
int fb_width = (int)(draw_data.DisplaySize.x * draw_data.FramebufferScale.x);
int fb_height = (int)(draw_data.DisplaySize.y * draw_data.FramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0) {
return;
}
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
if (pipeline == VK_NULL_HANDLE) {
pipeline = bd->pipeline;
}
// Allocate array to store enough vertex/index buffers
WindowRenderBuffers& wrb = bd->render_buffers;
wrb.index = (wrb.index + 1) % wrb.count;
FrameRenderBuffers& frb = wrb.frame_render_buffers[wrb.index];
if (draw_data.TotalVtxCount > 0) {
// Create or resize the vertex/index buffers
size_t vertex_size = AlignBufferSize(draw_data.TotalVtxCount * sizeof(ImDrawVert),
bd->buffer_memory_alignment);
size_t index_size =
AlignBufferSize(draw_data.TotalIdxCount * IDX_SIZE, bd->buffer_memory_alignment);
if (frb.vertex.buffer == VK_NULL_HANDLE || frb.vertex.buffer_size < vertex_size) {
CreateOrResizeBuffer(frb.vertex, vertex_size, vk::BufferUsageFlagBits::eVertexBuffer);
}
if (frb.index.buffer == VK_NULL_HANDLE || frb.index.buffer_size < index_size) {
CreateOrResizeBuffer(frb.index, index_size, vk::BufferUsageFlagBits::eIndexBuffer);
}
// Upload vertex/index data into a single contiguous GPU buffer
ImDrawVert* vtx_dst = nullptr;
ImDrawIdx* idx_dst = nullptr;
vtx_dst = (ImDrawVert*)CheckVkResult(
v.device.mapMemory(frb.vertex.buffer_memory, 0, vertex_size, vk::MemoryMapFlags{}));
idx_dst = (ImDrawIdx*)CheckVkResult(
v.device.mapMemory(frb.index.buffer_memory, 0, index_size, vk::MemoryMapFlags{}));
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 * IDX_SIZE);
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
vk::MappedMemoryRange range[2]{
{
.memory = frb.vertex.buffer_memory,
.size = VK_WHOLE_SIZE,
},
{
.memory = frb.index.buffer_memory,
.size = VK_WHOLE_SIZE,
},
};
CheckVkErr(v.device.flushMappedMemoryRanges({range}));
v.device.unmapMemory(frb.vertex.buffer_memory);
v.device.unmapMemory(frb.index.buffer_memory);
}
// Setup desired Vulkan state
SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height);
// Will project scissor/clipping rectangles into framebuffer space
// (0,0) unless using multi-viewports
ImVec2 clip_off = draw_data.DisplayPos;
// (1,1) unless using retina display which are often (2,2)
ImVec2 clip_scale = draw_data.FramebufferScale;
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0;
int global_idx_offset = 0;
for (int n = 0; n < draw_data.CmdListsCount; n++) {
const ImDrawList* cmd_list = draw_data.CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr) {
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to
// request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) {
SetupRenderState(draw_data, pipeline, command_buffer, frb, fb_width, fb_height);
} else {
pcmd->UserCallback(cmd_list, pcmd);
}
} else {
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x,
(pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x,
(pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
// Clamp to viewport as vk::CmdSetScissor() won't accept values that are off bounds
if (clip_min.x < 0.0f) {
clip_min.x = 0.0f;
}
if (clip_min.y < 0.0f) {
clip_min.y = 0.0f;
}
if (clip_max.x > fb_width) {
clip_max.x = (float)fb_width;
}
if (clip_max.y > fb_height) {
clip_max.y = (float)fb_height;
}
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle
vk::Rect2D scissor{
.offset{
.x = (int32_t)(clip_min.x),
.y = (int32_t)(clip_min.y),
},
.extent{
.width = (uint32_t)(clip_max.x - clip_min.x),
.height = (uint32_t)(clip_max.y - clip_min.y),
},
};
command_buffer.setScissor(0, 1, &scissor);
// Bind DescriptorSet with font or user texture
vk::DescriptorSet desc_set[1]{pcmd->TextureId->descriptor_set};
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
bd->pipeline_layout, 0, {desc_set}, {});
// Draw
command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset,
pcmd->VtxOffset + global_vtx_offset, 0);
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
}
// vk::Rect2D scissor = {{0, 0}, {(uint32_t)fb_width, (uint32_t)fb_height}};
// command_buffer.setScissor(0, 1, &scissor);
}
static void DestroyFontsTexture();
static bool CreateFontsTexture() {
ImGuiIO& io = ImGui::GetIO();
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
// Destroy existing texture (if any)
if (bd->font_view || bd->font_image || bd->font_memory || bd->font_texture) {
CheckVkErr(v.queue.waitIdle());
DestroyFontsTexture();
}
// Create command buffer
if (bd->font_command_buffer == VK_NULL_HANDLE) {
vk::CommandBufferAllocateInfo info{
.commandPool = bd->command_pool,
.commandBufferCount = 1,
};
std::unique_lock lk(bd->command_pool_mutex);
bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front();
}
// Start command buffer
{
CheckVkErr(bd->font_command_buffer.reset());
vk::CommandBufferBeginInfo begin_info{};
begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
CheckVkErr(bd->font_command_buffer.begin(&begin_info));
}
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
size_t upload_size = width * height * 4 * sizeof(char);
// Create the Image:
{
vk::ImageCreateInfo info{
.imageType = vk::ImageType::e2D,
.format = vk::Format::eR8G8B8A8Unorm,
.extent{
.width = static_cast<uint32_t>(width),
.height = static_cast<uint32_t>(height),
.depth = 1,
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
bd->font_image = CheckVkResult(v.device.createImage(info, v.allocator));
vk::MemoryRequirements req = v.device.getImageMemoryRequirements(bd->font_image);
vk::MemoryAllocateInfo alloc_info{
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits),
};
bd->font_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
CheckVkErr(v.device.bindImageMemory(bd->font_image, bd->font_memory, 0));
}
// Create the Image View:
{
vk::ImageViewCreateInfo info{
.image = bd->font_image,
.viewType = vk::ImageViewType::e2D,
.format = vk::Format::eR8G8B8A8Unorm,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
};
bd->font_view = CheckVkResult(v.device.createImageView(info, v.allocator));
}
// Create the Descriptor Set:
bd->font_texture = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
// Create the Upload Buffer:
vk::DeviceMemory upload_buffer_memory{};
vk::Buffer upload_buffer{};
{
vk::BufferCreateInfo buffer_info{
.size = upload_size,
.usage = vk::BufferUsageFlagBits::eTransferSrc,
.sharingMode = vk::SharingMode::eExclusive,
};
upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator));
vk::MemoryRequirements req = v.device.getBufferMemoryRequirements(upload_buffer);
bd->buffer_memory_alignment = IM_MAX(bd->buffer_memory_alignment, req.alignment);
vk::MemoryAllocateInfo alloc_info{
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
.memoryTypeIndex =
FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
};
upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
CheckVkErr(v.device.bindBufferMemory(upload_buffer, upload_buffer_memory, 0));
}
// Upload to Buffer:
{
char* map = (char*)CheckVkResult(v.device.mapMemory(upload_buffer_memory, 0, upload_size));
memcpy(map, pixels, upload_size);
vk::MappedMemoryRange range[1]{
{
.memory = upload_buffer_memory,
.size = upload_size,
},
};
CheckVkErr(v.device.flushMappedMemoryRanges({range}));
v.device.unmapMemory(upload_buffer_memory);
}
// Copy to Image:
{
vk::ImageMemoryBarrier copy_barrier[1]{
{
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = bd->font_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
},
};
bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
{copy_barrier});
vk::BufferImageCopy region{
.imageSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.layerCount = 1,
},
.imageExtent{
.width = static_cast<uint32_t>(width),
.height = static_cast<uint32_t>(height),
.depth = 1,
},
};
bd->font_command_buffer.copyBufferToImage(upload_buffer, bd->font_image,
vk::ImageLayout::eTransferDstOptimal, {region});
vk::ImageMemoryBarrier use_barrier[1]{{
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eShaderRead,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = bd->font_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.layerCount = 1,
},
}};
bd->font_command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eFragmentShader, {}, {},
{}, {use_barrier});
}
// Store our identifier
io.Fonts->SetTexID(bd->font_texture);
// End command buffer
vk::SubmitInfo end_info = {};
end_info.commandBufferCount = 1;
end_info.pCommandBuffers = &bd->font_command_buffer;
CheckVkErr(bd->font_command_buffer.end());
CheckVkErr(v.queue.submit({end_info}));
CheckVkErr(v.queue.waitIdle());
v.device.destroyBuffer(upload_buffer, v.allocator);
v.device.freeMemory(upload_buffer_memory, v.allocator);
return true;
}
// You probably never need to call this, as it is called by CreateFontsTexture()
// and Shutdown().
static void DestroyFontsTexture() {
ImGuiIO& io = ImGui::GetIO();
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
if (bd->font_texture) {
RemoveTexture(bd->font_texture);
bd->font_texture = nullptr;
io.Fonts->SetTexID(nullptr);
}
if (bd->font_view) {
v.device.destroyImageView(bd->font_view, v.allocator);
bd->font_view = VK_NULL_HANDLE;
}
if (bd->font_image) {
v.device.destroyImage(bd->font_image, v.allocator);
bd->font_image = VK_NULL_HANDLE;
}
if (bd->font_memory) {
v.device.freeMemory(bd->font_memory, v.allocator);
bd->font_memory = VK_NULL_HANDLE;
}
}
static void DestroyFrameRenderBuffers(vk::Device device, RenderBuffer& rb,
const vk::AllocationCallbacks* allocator) {
if (rb.buffer) {
device.destroyBuffer(rb.buffer, allocator);
rb.buffer = VK_NULL_HANDLE;
}
if (rb.buffer_memory) {
device.freeMemory(rb.buffer_memory, allocator);
rb.buffer_memory = VK_NULL_HANDLE;
}
rb.buffer_size = 0;
}
static void DestroyWindowRenderBuffers(vk::Device device, WindowRenderBuffers& buffers,
const vk::AllocationCallbacks* allocator) {
for (uint32_t n = 0; n < buffers.count; n++) {
auto& frb = buffers.frame_render_buffers[n];
DestroyFrameRenderBuffers(device, frb.index, allocator);
DestroyFrameRenderBuffers(device, frb.vertex, allocator);
}
buffers = {};
}
static void CreateShaderModules(vk::Device device, const vk::AllocationCallbacks* allocator) {
// Create the shader modules
VkData* bd = GetBackendData();
if (bd->shader_module_vert == VK_NULL_HANDLE) {
vk::ShaderModuleCreateInfo vert_info{
.codeSize = sizeof(glsl_shader_vert_spv),
.pCode = (uint32_t*)glsl_shader_vert_spv,
};
bd->shader_module_vert = CheckVkResult(device.createShaderModule(vert_info, allocator));
}
if (bd->shader_module_frag == VK_NULL_HANDLE) {
vk::ShaderModuleCreateInfo frag_info{
.codeSize = sizeof(glsl_shader_frag_spv),
.pCode = (uint32_t*)glsl_shader_frag_spv,
};
bd->shader_module_frag = CheckVkResult(device.createShaderModule(frag_info, allocator));
}
}
static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* allocator,
vk::PipelineCache pipeline_cache, vk::RenderPass render_pass,
vk::Pipeline* pipeline, uint32_t subpass) {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
CreateShaderModules(device, allocator);
vk::PipelineShaderStageCreateInfo stage[2]{
{
.stage = vk::ShaderStageFlagBits::eVertex,
.module = bd->shader_module_vert,
.pName = "main",
},
{
.stage = vk::ShaderStageFlagBits::eFragment,
.module = bd->shader_module_frag,
.pName = "main",
},
};
vk::VertexInputBindingDescription binding_desc[1]{
{
.stride = sizeof(ImDrawVert),
.inputRate = vk::VertexInputRate::eVertex,
},
};
vk::VertexInputAttributeDescription attribute_desc[3]{
{
.location = 0,
.binding = binding_desc[0].binding,
.format = vk::Format::eR32G32Sfloat,
.offset = offsetof(ImDrawVert, pos),
},
{
.location = 1,
.binding = binding_desc[0].binding,
.format = vk::Format::eR32G32Sfloat,
.offset = offsetof(ImDrawVert, uv),
},
{
.location = 2,
.binding = binding_desc[0].binding,
.format = vk::Format::eR8G8B8A8Unorm,
.offset = offsetof(ImDrawVert, col),
},
};
vk::PipelineVertexInputStateCreateInfo vertex_info{
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = binding_desc,
.vertexAttributeDescriptionCount = 3,
.pVertexAttributeDescriptions = attribute_desc,
};
vk::PipelineInputAssemblyStateCreateInfo ia_info{
.topology = vk::PrimitiveTopology::eTriangleList,
};
vk::PipelineViewportStateCreateInfo viewport_info{
.viewportCount = 1,
.scissorCount = 1,
};
vk::PipelineRasterizationStateCreateInfo raster_info{
.polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eNone,
.frontFace = vk::FrontFace::eCounterClockwise,
.lineWidth = 1.0f,
};
vk::PipelineMultisampleStateCreateInfo ms_info{
.rasterizationSamples = vk::SampleCountFlagBits::e1,
};
vk::PipelineColorBlendAttachmentState color_attachment[1]{
{
.blendEnable = VK_TRUE,
.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha,
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
.colorBlendOp = vk::BlendOp::eAdd,
.srcAlphaBlendFactor = vk::BlendFactor::eOne,
.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
.alphaBlendOp = vk::BlendOp::eAdd,
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
},
};
vk::PipelineDepthStencilStateCreateInfo depth_info{};
vk::PipelineColorBlendStateCreateInfo blend_info{
.attachmentCount = 1,
.pAttachments = color_attachment,
};
vk::DynamicState dynamic_states[2]{
vk::DynamicState::eViewport,
vk::DynamicState::eScissor,
};
vk::PipelineDynamicStateCreateInfo dynamic_state{
.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states),
.pDynamicStates = dynamic_states,
};
vk::GraphicsPipelineCreateInfo info{
.pNext = &v.pipeline_rendering_create_info,
.flags = bd->pipeline_create_flags,
.stageCount = 2,
.pStages = stage,
.pVertexInputState = &vertex_info,
.pInputAssemblyState = &ia_info,
.pViewportState = &viewport_info,
.pRasterizationState = &raster_info,
.pMultisampleState = &ms_info,
.pDepthStencilState = &depth_info,
.pColorBlendState = &blend_info,
.pDynamicState = &dynamic_state,
.layout = bd->pipeline_layout,
.renderPass = render_pass,
.subpass = subpass,
};
*pipeline =
CheckVkResult(device.createGraphicsPipelines(pipeline_cache, {info}, allocator)).front();
}
bool CreateDeviceObjects() {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
vk::Result err;
if (!bd->descriptor_pool) {
// large enough descriptor pool
vk::DescriptorPoolSize pool_sizes[]{
{vk::DescriptorType::eSampler, 1000},
{vk::DescriptorType::eCombinedImageSampler, 1000},
{vk::DescriptorType::eSampledImage, 1000},
{vk::DescriptorType::eStorageImage, 1000},
{vk::DescriptorType::eUniformTexelBuffer, 1000},
{vk::DescriptorType::eStorageTexelBuffer, 1000},
{vk::DescriptorType::eUniformBuffer, 1000},
{vk::DescriptorType::eStorageBuffer, 1000},
{vk::DescriptorType::eUniformBufferDynamic, 1000},
{vk::DescriptorType::eStorageBufferDynamic, 1000},
{vk::DescriptorType::eInputAttachment, 1000},
};
vk::DescriptorPoolCreateInfo pool_info{
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = 1000,
.poolSizeCount = std::size(pool_sizes),
.pPoolSizes = pool_sizes,
};
bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info));
}
if (!bd->descriptor_set_layout) {
vk::DescriptorSetLayoutBinding binding[1]{
{
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eFragment,
},
};
vk::DescriptorSetLayoutCreateInfo info{
.bindingCount = 1,
.pBindings = binding,
};
bd->descriptor_set_layout =
CheckVkResult(v.device.createDescriptorSetLayout(info, v.allocator));
}
if (!bd->pipeline_layout) {
// Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection
// matrix
vk::PushConstantRange push_constants[1]{
{
.stageFlags = vk::ShaderStageFlagBits::eVertex,
.offset = sizeof(float) * 0,
.size = sizeof(float) * 4,
},
};
vk::DescriptorSetLayout set_layout[1] = {bd->descriptor_set_layout};
vk::PipelineLayoutCreateInfo layout_info{
.setLayoutCount = 1,
.pSetLayouts = set_layout,
.pushConstantRangeCount = 1,
.pPushConstantRanges = push_constants,
};
bd->pipeline_layout =
CheckVkResult(v.device.createPipelineLayout(layout_info, v.allocator));
}
CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass);
if (bd->command_pool == VK_NULL_HANDLE) {
vk::CommandPoolCreateInfo info{
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = v.queue_family,
};
std::unique_lock lk(bd->command_pool_mutex);
bd->command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator));
}
if (!bd->simple_sampler) {
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |=
// ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow
// point/nearest sampling.
vk::SamplerCreateInfo info{
.magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear,
.addressModeU = vk::SamplerAddressMode::eRepeat,
.addressModeV = vk::SamplerAddressMode::eRepeat,
.addressModeW = vk::SamplerAddressMode::eRepeat,
.maxAnisotropy = 1.0f,
.minLod = -1000,
.maxLod = 1000,
};
bd->simple_sampler = CheckVkResult(v.device.createSampler(info, v.allocator));
}
return true;
}
void ImGuiImplVulkanDestroyDeviceObjects() {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
DestroyWindowRenderBuffers(v.device, bd->render_buffers, v.allocator);
DestroyFontsTexture();
if (bd->font_command_buffer) {
std::unique_lock lk(bd->command_pool_mutex);
v.device.freeCommandBuffers(bd->command_pool, {bd->font_command_buffer});
bd->font_command_buffer = VK_NULL_HANDLE;
}
if (bd->command_pool) {
std::unique_lock lk(bd->command_pool_mutex);
v.device.destroyCommandPool(bd->command_pool, v.allocator);
bd->command_pool = VK_NULL_HANDLE;
}
if (bd->shader_module_vert) {
v.device.destroyShaderModule(bd->shader_module_vert, v.allocator);
bd->shader_module_vert = VK_NULL_HANDLE;
}
if (bd->shader_module_frag) {
v.device.destroyShaderModule(bd->shader_module_frag, v.allocator);
bd->shader_module_frag = VK_NULL_HANDLE;
}
if (bd->simple_sampler) {
v.device.destroySampler(bd->simple_sampler, v.allocator);
bd->simple_sampler = VK_NULL_HANDLE;
}
if (bd->descriptor_set_layout) {
v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator);
bd->descriptor_set_layout = VK_NULL_HANDLE;
}
if (bd->pipeline_layout) {
v.device.destroyPipelineLayout(bd->pipeline_layout, v.allocator);
bd->pipeline_layout = VK_NULL_HANDLE;
}
if (bd->pipeline) {
v.device.destroyPipeline(bd->pipeline, v.allocator);
bd->pipeline = VK_NULL_HANDLE;
}
}
bool Init(InitInfo info) {
IM_ASSERT(info.instance != VK_NULL_HANDLE);
IM_ASSERT(info.physical_device != VK_NULL_HANDLE);
IM_ASSERT(info.device != VK_NULL_HANDLE);
IM_ASSERT(info.image_count >= 2);
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
auto* bd = IM_NEW(VkData)(info);
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_vulkan_shadps4";
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
CreateDeviceObjects();
CreateFontsTexture();
return true;
}
void Shutdown() {
VkData* bd = GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiImplVulkanDestroyDeviceObjects();
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
IM_DELETE(bd);
}
void OnSurfaceFormatChange(vk::Format surface_format) {
VkData* bd = GetBackendData();
const InitInfo& v = bd->init_info;
auto& pl_format = const_cast<vk::Format&>(
bd->init_info.pipeline_rendering_create_info.pColorAttachmentFormats[0]);
if (pl_format != surface_format) {
pl_format = surface_format;
if (bd->pipeline) {
v.device.destroyPipeline(bd->pipeline, v.allocator);
bd->pipeline = VK_NULL_HANDLE;
CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline,
v.subpass);
}
}
}
} // namespace ImGui::Vulkan