mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-06-01 08:13:16 +00:00
1284 lines
51 KiB
C++
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
|