425 lines
16 KiB
C++
425 lines
16 KiB
C++
#include "renderer.h"
|
|
|
|
#include "imgui_impl_glfw.h"
|
|
#include "render_target.h"
|
|
|
|
#include "application/application.h"
|
|
#include "texture.h"
|
|
|
|
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::CommandPool renderer::get_command_pool() const {
|
|
return main_window_data.Frames[main_window_data.FrameIndex].CommandPool;
|
|
}
|
|
|
|
vk::CommandBuffer renderer::create_command_buffer(vk::CommandBufferLevel level, bool begin) const {
|
|
vk::CommandBufferAllocateInfo alloc_info;
|
|
alloc_info.setCommandPool(get_command_pool());
|
|
alloc_info.setLevel(level);
|
|
alloc_info.setCommandBufferCount(1);
|
|
|
|
vk::CommandBuffer command_buffer;
|
|
auto err = device.allocateCommandBuffers(&alloc_info, &command_buffer);
|
|
check_vk_result(err);
|
|
|
|
// If requested, also start the new command buffer
|
|
if (begin) {
|
|
vk::CommandBufferBeginInfo begin_info;
|
|
begin_info.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
|
|
command_buffer.begin(begin_info);
|
|
}
|
|
|
|
return command_buffer;
|
|
}
|
|
|
|
void renderer::end_command_buffer(vk::CommandBuffer command_buffer) {
|
|
command_buffer.end();
|
|
|
|
vk::SubmitInfo submit_info;
|
|
submit_info.setCommandBuffers(command_buffer);
|
|
queue.submit(submit_info, nullptr);
|
|
}
|
|
|
|
void renderer::init_vulkan(GLFWwindow* window_handle) {
|
|
std::vector<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);
|
|
}
|
|
|
|
vk::PhysicalDevice renderer::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) {
|
|
if (const auto properties = device.getProperties(); 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::setup_vulkan(std::vector<const char*> instance_extensions) {
|
|
// Create Vulkan Instance
|
|
{
|
|
vk::InstanceCreateInfo create_info;
|
|
|
|
// 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::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
|
|
}
|
|
#endif
|
|
|
|
// Create Vulkan Instance
|
|
create_info.setPEnabledExtensionNames(instance_extensions);
|
|
instance = vk::createInstance(create_info, allocator);
|
|
}
|
|
|
|
// Select Physical Device (GPU)
|
|
physical_device = setup_vulkan_select_physical_device();
|
|
|
|
// Select graphics queue family
|
|
{
|
|
const 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<const char*> 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
|
|
|
|
std::vector<float> queue_priority = {1.0f};
|
|
vk::DeviceQueueCreateInfo queue_info;
|
|
queue_info.setQueueFamilyIndex(queue_family);
|
|
queue_info.setQueuePriorities(queue_priority);
|
|
|
|
vk::DeviceCreateInfo create_info;
|
|
create_info.setQueueCreateInfos(queue_info);
|
|
create_info.setPEnabledExtensionNames(device_extensions);
|
|
device = physical_device.createDevice(create_info, allocator);
|
|
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);
|
|
|
|
auto properties = physical_device.getProperties();
|
|
vk::DescriptorPoolCreateInfo descriptor_pool_create_info;
|
|
descriptor_pool_create_info.setMaxSets(properties.limits.maxDescriptorSetStorageImages);
|
|
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::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::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::cleanup_vulkan_window() {
|
|
ImGui_ImplVulkanH_DestroyWindow(instance, device, &main_window_data,
|
|
reinterpret_cast<VkAllocationCallbacks*>(allocator));
|
|
}
|
|
|
|
void renderer::frame_render(ImDrawData* draw_data) {
|
|
vk::Semaphore image_acquired_semaphore = main_window_data.FrameSemaphores[main_window_data.SemaphoreIndex].ImageAcquiredSemaphore;
|
|
vk::Semaphore render_complete_semaphore = main_window_data.FrameSemaphores[main_window_data.SemaphoreIndex].RenderCompleteSemaphore;
|
|
|
|
vk::Result err = device.acquireNextImageKHR(main_window_data.Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE,
|
|
&main_window_data.FrameIndex);
|
|
if (err == vk::Result::eErrorOutOfDateKHR || err == vk::Result::eSuboptimalKHR) {
|
|
swap_chain_rebuild_ = true;
|
|
return;
|
|
}
|
|
|
|
check_vk_result(err);
|
|
|
|
ImGui_ImplVulkanH_Frame* fd = &main_window_data.Frames[main_window_data.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 = main_window_data.RenderPass;
|
|
|
|
const auto clear_color = main_window_data.ClearValue.color.float32;
|
|
const auto clear_depth = main_window_data.ClearValue.depthStencil.depth;
|
|
const auto clear_stencil = main_window_data.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);
|
|
|
|
std::vector<vk::ClearValue> clear_values;
|
|
clear_values.emplace_back(clear_value);
|
|
|
|
vk::RenderPassBeginInfo info;
|
|
info.setRenderPass(render_pass);
|
|
info.setFramebuffer(framebuffer);
|
|
info.renderArea.extent.width = main_window_data.Width;
|
|
info.renderArea.extent.height = main_window_data.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::frame_present() {
|
|
if (swap_chain_rebuild_)
|
|
return;
|
|
vk::Semaphore render_complete_semaphore = main_window_data.FrameSemaphores[main_window_data.SemaphoreIndex].RenderCompleteSemaphore;
|
|
vk::SwapchainKHR swapchain = main_window_data.Swapchain;
|
|
uint32_t frame_index = main_window_data.FrameIndex;
|
|
|
|
vk::PresentInfoKHR info;
|
|
info.setWaitSemaphores(render_complete_semaphore);
|
|
info.setSwapchains(swapchain);
|
|
info.setImageIndices(frame_index);
|
|
|
|
try {
|
|
(void)queue.presentKHR(info);
|
|
} catch (const vk::OutOfDateKHRError& e) {
|
|
swap_chain_rebuild_ = true;
|
|
return;
|
|
}
|
|
main_window_data.SemaphoreIndex = (main_window_data.SemaphoreIndex + 1) % main_window_data.SemaphoreCount; // Now we can use the next set of semaphores
|
|
}
|
|
|
|
void renderer::pre_init() {
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
|
}
|
|
|
|
bool renderer::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::shutdown() {
|
|
ImGui_ImplGlfw_Shutdown();
|
|
ImGui_ImplVulkan_Shutdown();
|
|
|
|
cleanup_vulkan_window();
|
|
cleanup_vulkan();
|
|
}
|
|
|
|
void renderer::new_frame(GLFWwindow* window_handle) {
|
|
// Resize swap chain?
|
|
if (swap_chain_rebuild_)
|
|
{
|
|
int width, height;
|
|
glfwGetFramebufferSize(window_handle, &width, &height);
|
|
if (width > 0 && height > 0)
|
|
{
|
|
ImGui_ImplVulkan_SetMinImageCount(min_image_count);
|
|
ImGui_ImplVulkanH_CreateOrResizeWindow(instance, physical_device, device, &main_window_data, queue_family, reinterpret_cast<VkAllocationCallbacks*>(allocator), width, height, min_image_count);
|
|
main_window_data.FrameIndex = 0;
|
|
swap_chain_rebuild_ = false;
|
|
}
|
|
}
|
|
|
|
// Start the Dear ImGui frame
|
|
ImGui_ImplVulkan_NewFrame();
|
|
ImGui_ImplGlfw_NewFrame();
|
|
ImGui::NewFrame();
|
|
}
|
|
|
|
void renderer::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_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();
|
|
}
|
|
|
|
std::shared_ptr<texture> renderer::create_texture(const uint8_t* data, int width, int height, vk::Format format) {
|
|
auto out = std::make_shared<texture>();
|
|
out->init_data(data, width, height, format);
|
|
return out;
|
|
}
|
|
|
|
std::shared_ptr<render_target> renderer::create_render_target(int width, int height, vk::Format format) {
|
|
auto out = std::make_shared<render_target>();
|
|
out->init(width, height, format);
|
|
return out;
|
|
}
|