// 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 #include #include #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 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 static T CheckVkResult(vk::ResultValue 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(width), .height = static_cast(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(width), .height = static_cast(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( 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