391 lines
15 KiB
C++
391 lines
15 KiB
C++
#include "renderer_vk.h"
|
|
|
|
#include "imgui_impl_glfw.h"
|
|
#include "utils/utils.hpp"
|
|
|
|
static void check_vk_result(vk::Result err) {
|
|
if (err == vk::Result::eSuccess)
|
|
return;
|
|
|
|
spdlog::error("[vulkan] Error: VkResult = {}", vk::to_string(err));
|
|
abort();
|
|
}
|
|
|
|
static void check_vk_result(VkResult err) {
|
|
if (err == VK_SUCCESS)
|
|
return;
|
|
if (err < 0) {
|
|
spdlog::error("[vulkan] Error: VkResult = {}", err);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static bool is_extension_available(const std::vector<vk::ExtensionProperties>& properties, const char* extension) {
|
|
return std::ranges::any_of(properties, [extension](const vk::ExtensionProperties& p) {
|
|
return strcmp(p.extensionName, extension) == 0;
|
|
});
|
|
}
|
|
|
|
vk::PhysicalDevice renderer_vk::setup_vulkan_select_physical_device() const {
|
|
const std::vector<vk::PhysicalDevice> gpus = instance.enumeratePhysicalDevices();
|
|
IM_ASSERT(!gpus.empty());
|
|
|
|
// If a number >1 of GPUs got reported, find discrete GPU if present, or use first one available. This covers
|
|
// most common cases (multi-gpu/integrated+dedicated graphics). Handling more complicated setups (multiple
|
|
// dedicated GPUs) is out of scope of this sample.
|
|
for (auto& device: gpus) {
|
|
auto properties = device.getProperties();
|
|
|
|
if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu)
|
|
return device;
|
|
}
|
|
|
|
// Use first GPU (Integrated) is a Discrete one is not available.
|
|
if (!gpus.empty())
|
|
return gpus[0];
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
void renderer_vk::setup_vulkan(ImVector<const char*> instance_extensions) {
|
|
// Create Vulkan Instance
|
|
{
|
|
VkInstanceCreateInfo create_info = {};
|
|
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
|
|
// Enumerate available extensions
|
|
auto properties = vk::enumerateInstanceExtensionProperties();
|
|
|
|
// Enable required extensions
|
|
if (is_extension_available(properties, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
|
|
instance_extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
|
#ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME
|
|
if (is_extension_available(properties, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
|
|
instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
|
create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
|
}
|
|
#endif
|
|
|
|
// Create Vulkan Instance
|
|
create_info.enabledExtensionCount = static_cast<uint32_t>(instance_extensions.Size);
|
|
create_info.ppEnabledExtensionNames = instance_extensions.Data;
|
|
instance = vk::createInstance(create_info, allocator);
|
|
}
|
|
|
|
// Select Physical Device (GPU)
|
|
physical_device = setup_vulkan_select_physical_device();
|
|
|
|
// Select graphics queue family
|
|
{
|
|
auto queues = physical_device.getQueueFamilyProperties();
|
|
for (uint32_t i = 0; i < queues.size(); i++) {
|
|
if (queues[i].queueFlags & vk::QueueFlagBits::eGraphics) {
|
|
queue_family = i;
|
|
break;
|
|
}
|
|
}
|
|
IM_ASSERT(queue_family != static_cast<uint32_t>(-1));
|
|
}
|
|
|
|
// Create Logical Device (with 1 queue)
|
|
{
|
|
std::vector<std::string> device_extensions;
|
|
device_extensions.emplace_back("VK_KHR_swapchain");
|
|
|
|
// Enumerate physical device extension
|
|
auto properties = physical_device.enumerateDeviceExtensionProperties();
|
|
|
|
#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
|
|
if (is_extension_available(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME))
|
|
device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
|
|
#endif
|
|
|
|
device = vk::su::createDevice(physical_device, queue_family, device_extensions);
|
|
queue = device.getQueue(queue_family, 0);
|
|
}
|
|
|
|
// Create Descriptor Pool
|
|
// The example only requires a single combined image sampler descriptor for the font image and only uses one descriptor set (for that)
|
|
// If you wish to load e.g. additional textures you may need to alter pools sizes.
|
|
{
|
|
std::vector<vk::DescriptorPoolSize> pool_sizes;
|
|
pool_sizes.emplace_back(vk::DescriptorType::eCombinedImageSampler, 1);
|
|
|
|
vk::DescriptorPoolCreateInfo descriptor_pool_create_info;
|
|
descriptor_pool_create_info.setMaxSets(1);
|
|
descriptor_pool_create_info.setPoolSizeCount(pool_sizes.size());
|
|
descriptor_pool_create_info.setPoolSizes(pool_sizes);
|
|
|
|
descriptor_pool = device.createDescriptorPool(descriptor_pool_create_info);
|
|
}
|
|
}
|
|
|
|
// All the ImGui_ImplVulkanH_XXX structures/functions are optional helpers used by the demo.
|
|
// Your real engine/app may not use them.
|
|
void renderer_vk::setup_vulkan_window(VkSurfaceKHR surface, int width,
|
|
int height) {
|
|
main_window_data.Surface = surface;
|
|
|
|
// Check for WSI support
|
|
vk::Bool32 res;
|
|
const auto err = physical_device.getSurfaceSupportKHR(queue_family, main_window_data.Surface, &res);
|
|
check_vk_result(err);
|
|
if (res != VK_TRUE) {
|
|
fprintf(stderr, "Error no WSI support on physical device 0\n");
|
|
exit(-1);
|
|
}
|
|
|
|
// Select Surface Format
|
|
constexpr VkFormat requestSurfaceImageFormat[] = {
|
|
VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM
|
|
};
|
|
constexpr VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
|
|
main_window_data.SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(physical_device, main_window_data.Surface, requestSurfaceImageFormat,
|
|
(size_t) IM_ARRAYSIZE(requestSurfaceImageFormat),
|
|
requestSurfaceColorSpace);
|
|
|
|
// Select Present Mode
|
|
#ifdef APP_USE_UNLIMITED_FRAME_RATE
|
|
VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR };
|
|
#else
|
|
VkPresentModeKHR present_modes[] = {VK_PRESENT_MODE_FIFO_KHR};
|
|
#endif
|
|
main_window_data.PresentMode = ImGui_ImplVulkanH_SelectPresentMode(physical_device, main_window_data.Surface, &present_modes[0],
|
|
IM_ARRAYSIZE(present_modes));
|
|
//printf("[vulkan] Selected PresentMode = %d\n", wd->PresentMode);
|
|
|
|
// Create SwapChain, RenderPass, Framebuffer, etc.
|
|
IM_ASSERT(min_image_count >= 2);
|
|
|
|
ImGui_ImplVulkanH_CreateOrResizeWindow(instance, physical_device, device, &main_window_data, queue_family,
|
|
reinterpret_cast<VkAllocationCallbacks*>(allocator), width,
|
|
height, min_image_count);
|
|
}
|
|
|
|
void renderer_vk::cleanup_vulkan() const {
|
|
device.destroyDescriptorPool(descriptor_pool);
|
|
#ifdef APP_USE_VULKAN_DEBUG_REPORT
|
|
// Remove the debug report callback
|
|
auto vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkDestroyDebugReportCallbackEXT");
|
|
vkDestroyDebugReportCallbackEXT(g_Instance, g_DebugReport, g_Allocator);
|
|
#endif // APP_USE_VULKAN_DEBUG_REPORT
|
|
|
|
device.destroy();
|
|
instance.destroy();
|
|
}
|
|
|
|
void renderer_vk::cleanup_vulkan_window() {
|
|
ImGui_ImplVulkanH_DestroyWindow(instance, device, &main_window_data,
|
|
reinterpret_cast<VkAllocationCallbacks*>(allocator));
|
|
}
|
|
|
|
void renderer_vk::frame_render(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) {
|
|
vk::Semaphore image_acquired_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore;
|
|
vk::Semaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
|
|
|
|
vk::Result err = device.acquireNextImageKHR(wd->Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE,
|
|
&wd->FrameIndex);
|
|
if (err == vk::Result::eErrorOutOfDateKHR || err == vk::Result::eSuboptimalKHR) {
|
|
swap_chain_rebuild = true;
|
|
return;
|
|
}
|
|
|
|
check_vk_result(err);
|
|
|
|
ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex];
|
|
const vk::CommandBuffer cmd_buf = fd->CommandBuffer;
|
|
const vk::Fence fence = fd->Fence; {
|
|
err = device.waitForFences(1, &fence, VK_TRUE, UINT64_MAX);
|
|
|
|
// wait indefinitely instead of periodically checking
|
|
check_vk_result(err);
|
|
|
|
err = device.resetFences(1, &fence);
|
|
check_vk_result(err);
|
|
} {
|
|
const vk::CommandPool command_pool = fd->CommandPool;
|
|
device.resetCommandPool(command_pool);
|
|
|
|
vk::CommandBufferBeginInfo info = {};
|
|
info.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
|
|
|
|
cmd_buf.begin(info);
|
|
} {
|
|
const vk::Framebuffer framebuffer = fd->Framebuffer;
|
|
const vk::RenderPass render_pass = wd->RenderPass;
|
|
std::vector<vk::ClearValue> clear_values;
|
|
const auto clear_color = wd->ClearValue.color.float32;
|
|
const auto clear_depth = wd->ClearValue.depthStencil.depth;
|
|
const auto clear_stencil = wd->ClearValue.depthStencil.stencil;
|
|
vk::ClearValue clear_value;
|
|
clear_value.color = vk::ClearColorValue(std::array<float, 4>{
|
|
clear_color[0], clear_color[1], clear_color[2], clear_color[3]
|
|
});
|
|
clear_value.depthStencil = vk::ClearDepthStencilValue(clear_depth, clear_stencil);
|
|
|
|
clear_values.emplace_back((clear_value.color));
|
|
|
|
vk::RenderPassBeginInfo info;
|
|
info.setRenderPass(render_pass);
|
|
info.setFramebuffer(framebuffer);
|
|
info.renderArea.extent.width = wd->Width;
|
|
info.renderArea.extent.height = wd->Height;
|
|
info.setClearValues(clear_values);
|
|
|
|
cmd_buf.beginRenderPass(info, vk::SubpassContents::eInline);
|
|
}
|
|
|
|
// Record dear imgui primitives into command buffer
|
|
ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer);
|
|
|
|
// Submit command buffer
|
|
vkCmdEndRenderPass(fd->CommandBuffer); {
|
|
vk::PipelineStageFlags wait_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
|
|
vk::SubmitInfo info;
|
|
info.setWaitSemaphores(image_acquired_semaphore);
|
|
info.setWaitDstStageMask(wait_stage);
|
|
info.setCommandBuffers(cmd_buf);
|
|
info.setSignalSemaphores(render_complete_semaphore);
|
|
|
|
cmd_buf.end();
|
|
err = queue.submit(1, &info, fence);
|
|
check_vk_result(err);
|
|
}
|
|
}
|
|
|
|
void renderer_vk::frame_present(ImGui_ImplVulkanH_Window* wd) {
|
|
if (swap_chain_rebuild)
|
|
return;
|
|
vk::Semaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
|
|
vk::SwapchainKHR swapchain = wd->Swapchain;
|
|
uint32_t frame_index = wd->FrameIndex;
|
|
vk::PresentInfoKHR info;
|
|
info.setWaitSemaphores(render_complete_semaphore);
|
|
info.setSwapchains(swapchain);
|
|
info.setImageIndices(frame_index);
|
|
|
|
auto err = queue.presentKHR(info);
|
|
|
|
if (err == vk::Result::eErrorOutOfDateKHR || err == vk::Result::eSuboptimalKHR) {
|
|
swap_chain_rebuild = true;
|
|
return;
|
|
}
|
|
check_vk_result(err);
|
|
wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->SemaphoreCount; // Now we can use the next set of semaphores
|
|
}
|
|
|
|
void renderer_vk::pre_init() {
|
|
renderer::pre_init();
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
|
}
|
|
|
|
bool renderer_vk::init(GLFWwindow* window_handle) {
|
|
if (has_initialized_)
|
|
return true;
|
|
|
|
if (!glfwVulkanSupported()) {
|
|
throw std::runtime_error("Vulkan not supported");
|
|
}
|
|
init_vulkan(window_handle);
|
|
|
|
has_initialized_ = true;
|
|
return true;
|
|
}
|
|
|
|
void renderer_vk::shutdown() {
|
|
renderer::shutdown();
|
|
|
|
ImGui_ImplGlfw_Shutdown();
|
|
ImGui_ImplVulkan_Shutdown();
|
|
}
|
|
|
|
std::shared_ptr<shader> renderer_vk::load_shader(const std::string& entry_name) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<pixel_shader_drawer> renderer_vk::create_pixel_shader_drawer() {
|
|
return nullptr;
|
|
}
|
|
|
|
void renderer_vk::new_frame(GLFWwindow* window_handle) {
|
|
// Start the Dear ImGui frame
|
|
ImGui_ImplVulkan_NewFrame();
|
|
ImGui_ImplGlfw_NewFrame();
|
|
ImGui::NewFrame();
|
|
}
|
|
|
|
void renderer_vk::end_frame(GLFWwindow* window_handle) {
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
// Rendering
|
|
ImGui::Render();
|
|
ImDrawData* main_draw_data = ImGui::GetDrawData();
|
|
const bool main_is_minimized = (main_draw_data->DisplaySize.x <= 0.0f || main_draw_data->DisplaySize.y <= 0.0f);
|
|
main_window_data.ClearValue.color.float32[0] = clear_color.x * clear_color.w;
|
|
main_window_data.ClearValue.color.float32[1] = clear_color.y * clear_color.w;
|
|
main_window_data.ClearValue.color.float32[2] = clear_color.z * clear_color.w;
|
|
main_window_data.ClearValue.color.float32[3] = clear_color.w;
|
|
if (!main_is_minimized)
|
|
frame_render(&main_window_data, main_draw_data);
|
|
|
|
// Update and Render additional Platform Windows
|
|
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
|
{
|
|
ImGui::UpdatePlatformWindows();
|
|
ImGui::RenderPlatformWindowsDefault();
|
|
}
|
|
|
|
// Present Main Platform Window
|
|
if (!main_is_minimized)
|
|
frame_present(&main_window_data);
|
|
}
|
|
|
|
void renderer_vk::resize(int width, int height) {
|
|
}
|
|
|
|
std::shared_ptr<texture> renderer_vk::create_texture(const unsigned char* data, int width, int height) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<render_target> renderer_vk::create_render_target(int width, int height, texture_format format) {
|
|
return nullptr;
|
|
}
|
|
|
|
void renderer_vk::init_vulkan(GLFWwindow* window_handle) {
|
|
ImVector<const char*> extensions;
|
|
uint32_t extensions_count = 0;
|
|
const char** glfw_extensions = glfwGetRequiredInstanceExtensions(&extensions_count);
|
|
for (uint32_t i = 0; i < extensions_count; i++)
|
|
extensions.push_back(glfw_extensions[i]);
|
|
setup_vulkan(extensions);
|
|
|
|
// Create Window Surface
|
|
VkSurfaceKHR surface;
|
|
VkResult err = glfwCreateWindowSurface(instance, window_handle, reinterpret_cast<VkAllocationCallbacks*>(allocator), &surface);
|
|
check_vk_result(err);
|
|
|
|
// Create Framebuffers
|
|
int w, h;
|
|
glfwGetFramebufferSize(window_handle, &w, &h);
|
|
setup_vulkan_window(surface, w, h);
|
|
|
|
ImGui_ImplGlfw_InitForVulkan(window_handle, true);
|
|
|
|
ImGui_ImplVulkan_InitInfo init_info = {};
|
|
init_info.Instance = instance;
|
|
init_info.PhysicalDevice = physical_device;
|
|
init_info.Device = device;
|
|
init_info.QueueFamily = queue_family;
|
|
init_info.Queue = queue;
|
|
init_info.PipelineCache = pipeline_cache;
|
|
init_info.DescriptorPool = descriptor_pool;
|
|
init_info.RenderPass = main_window_data.RenderPass;
|
|
init_info.Subpass = 0;
|
|
init_info.MinImageCount = min_image_count;
|
|
init_info.ImageCount = main_window_data.ImageCount;
|
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
|
init_info.Allocator = reinterpret_cast<VkAllocationCallbacks*>(allocator);
|
|
init_info.CheckVkResultFn = check_vk_result;
|
|
ImGui_ImplVulkan_Init(&init_info);
|
|
}
|