From 1a69c58458eeac23eff283ea01c626e50dc70b74 Mon Sep 17 00:00:00 2001 From: SimoneN64 Date: Fri, 20 Sep 2024 23:03:53 +0200 Subject: [PATCH] Some progress --- external/imgui/CMakeLists.txt | 4 +- src/frontend/CMakeLists.txt | 8 +- src/frontend/ImGuiWidget.cpp | 444 ++++++++++++++++++++++++++++++++++ src/frontend/ImGuiWidget.hpp | 43 ++++ src/frontend/KaizenQt.cpp | 24 +- src/frontend/KaizenQt.hpp | 2 +- src/frontend/MainWindow.cpp | 150 ++++++++++-- src/frontend/MainWindow.hpp | 26 +- src/frontend/RenderWidget.hpp | 3 - src/frontend/mainwindow.ui | 146 ----------- 10 files changed, 658 insertions(+), 192 deletions(-) create mode 100644 src/frontend/ImGuiWidget.cpp create mode 100644 src/frontend/ImGuiWidget.hpp delete mode 100644 src/frontend/mainwindow.ui diff --git a/external/imgui/CMakeLists.txt b/external/imgui/CMakeLists.txt index bb82a7c8..c837818b 100644 --- a/external/imgui/CMakeLists.txt +++ b/external/imgui/CMakeLists.txt @@ -6,6 +6,4 @@ file(GLOB HEADERS *.h) list(APPEND SOURCES backends/imgui_impl_sdl3.cpp backends/imgui_impl_vulkan.cpp) add_library(imgui ${SOURCES} ${HEADERS}) -target_include_directories(imgui PRIVATE . backends ../SDL/include) -target_compile_definitions(imgui PRIVATE IMGUI_IMPL_VULKAN_USE_VOLK) -target_compile_definitions(imgui PRIVATE IMGUI_IMPL_VULKAN_NO_PROTOTYPES) \ No newline at end of file +target_include_directories(imgui PRIVATE . backends ../SDL/include) \ No newline at end of file diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 58a3bb67..9f3c7677 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -6,6 +6,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +add_compile_definitions(IMGUI_IMPL_VULKAN_USE_VOLK) +add_compile_definitions(IMGUI_IMPL_VULKAN_NO_PROTOTYPES) + if (WIN32) add_compile_definitions(NOMINMAX) endif () @@ -68,7 +71,6 @@ add_executable(kaizen-qt RenderWidget.hpp EmuThread.hpp EmuThread.cpp - mainwindow.ui MainWindow.hpp MainWindow.cpp SettingsWindow.hpp @@ -79,7 +81,9 @@ add_executable(kaizen-qt AudioSettings.hpp AudioSettings.cpp InputSettings.hpp - InputSettings.cpp) + InputSettings.cpp + ImGuiWidget.hpp + ImGuiWidget.cpp) include(CheckCCompilerFlag) diff --git a/src/frontend/ImGuiWidget.cpp b/src/frontend/ImGuiWidget.cpp new file mode 100644 index 00000000..97a6aa9b --- /dev/null +++ b/src/frontend/ImGuiWidget.cpp @@ -0,0 +1,444 @@ +#include +#include +#include +#include +#include + +static void check_vk_result(VkResult err) { + if (err == 0) + return; + fprintf(stderr, "[vulkan] Error: VkResult = %d\n", err); + if (err < 0) + abort(); +} + +#ifdef APP_USE_VULKAN_DEBUG_REPORT +static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, + uint64_t object, size_t location, int32_t messageCode, + const char *pLayerPrefix, const char *pMessage, void *pUserData) { + (void)flags; + (void)object; + (void)location; + (void)messageCode; + (void)pUserData; + (void)pLayerPrefix; // Unused arguments + fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage); + return VK_FALSE; +} +#endif // APP_USE_VULKAN_DEBUG_REPORT + +static bool IsExtensionAvailable(const std::vector &properties, const char *extension) { + for (const VkExtensionProperties &p : properties) + if (strcmp(p.extensionName, extension) == 0) + return true; + return false; +} + +void ImGuiWidget::SelectGpu() { + uint32_t gpu_count; + VkResult err = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr); + check_vk_result(err); + IM_ASSERT(gpu_count > 0); + + std::vector gpus; + gpus.resize(gpu_count); + err = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data()); + check_vk_result(err); + + // 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 (VkPhysicalDevice &device : gpus) { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device, &properties); + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + gpu = device; + return; + } + } + + // Use first GPU (Integrated) if a Discrete one is not available. + if (gpu_count > 0) { + gpu = gpus[0]; + return; + } + + Util::panic("[ImGui Widget] Could not find a suitable Vulkan GPU!"); +} + +void ImGuiWidget::InitVulkan(char const *const *instanceExtensions, uint32_t instanceExtensionsCount) { + VkResult err; +#ifdef IMGUI_IMPL_VULKAN_USE_VOLK + volkInitialize(); +#endif + + // Create Vulkan Instance + { + VkInstanceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + + // Enumerate available extensions + uint32_t properties_count; + std::vector properties; + vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, nullptr); + properties.resize(properties_count); + err = vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, properties.data()); + check_vk_result(err); + + std::vector extensions; + extensions.resize(instanceExtensionsCount); + memcpy(extensions.data(), instanceExtensions, instanceExtensionsCount * sizeof(const char *)); + + // Enable required extensions + if (IsExtensionAvailable(properties, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME + if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) { + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + } +#endif + + // Enabling validation layers +#ifdef APP_USE_VULKAN_DEBUG_REPORT + const char *layers[] = {"VK_LAYER_KHRONOS_validation"}; + create_info.enabledLayerCount = 1; + create_info.ppEnabledLayerNames = layers; + instance_extensions.push_back("VK_EXT_debug_report"); +#endif + + // Create Vulkan Instance + create_info.enabledExtensionCount = static_cast(extensions.size()); + create_info.ppEnabledExtensionNames = extensions.data(); + err = vkCreateInstance(&create_info, nullptr, &instance); + check_vk_result(err); +#ifdef IMGUI_IMPL_VULKAN_USE_VOLK + volkLoadInstance(instance); +#endif + + // Setup the debug report callback +#ifdef APP_USE_VULKAN_DEBUG_REPORT + auto f_vkCreateDebugReportCallbackEXT = + (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); + IM_ASSERT(f_vkCreateDebugReportCallbackEXT != nullptr); + VkDebugReportCallbackCreateInfoEXT debug_report_ci = {}; + debug_report_ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; + debug_report_ci.flags = + VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; + debug_report_ci.pfnCallback = debug_report; + debug_report_ci.pUserData = nullptr; + err = f_vkCreateDebugReportCallbackEXT(instance, &debug_report_ci, nullptr, &g_DebugReport); + check_vk_result(err); +#endif + } + + // Select Physical Device (GPU) + SelectGpu(); + + // Select graphics queue family + { + uint32_t count; + vkGetPhysicalDeviceQueueFamilyProperties(gpu, &count, nullptr); + VkQueueFamilyProperties *queues = (VkQueueFamilyProperties *)malloc(sizeof(VkQueueFamilyProperties) * count); + vkGetPhysicalDeviceQueueFamilyProperties(gpu, &count, queues); + for (uint32_t i = 0; i < count; i++) + if (queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + queueFamily = i; + break; + } + free(queues); + IM_ASSERT(queueFamily != (uint32_t)-1); + } + + // Create Logical Device (with 1 queue) + { + std::vector device_extensions; + device_extensions.push_back("VK_KHR_swapchain"); + + // Enumerate physical device extension + uint32_t properties_count; + std::vector properties; + vkEnumerateDeviceExtensionProperties(gpu, nullptr, &properties_count, nullptr); + properties.resize(properties_count); + vkEnumerateDeviceExtensionProperties(gpu, nullptr, &properties_count, properties.data()); +#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME + if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) + device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); +#endif + + const float queue_priority[] = {1.0f}; + VkDeviceQueueCreateInfo queue_info[1] = {}; + queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info[0].queueFamilyIndex = queueFamily; + queue_info[0].queueCount = 1; + queue_info[0].pQueuePriorities = queue_priority; + VkDeviceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_info.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]); + create_info.pQueueCreateInfos = queue_info; + create_info.enabledExtensionCount = (uint32_t)device_extensions.size(); + create_info.ppEnabledExtensionNames = device_extensions.data(); + err = vkCreateDevice(gpu, &create_info, nullptr, &device); + check_vk_result(err); + vkGetDeviceQueue(device, queueFamily, 0, &queue); + } + + // 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. + { + VkDescriptorPoolSize pool_sizes[] = { + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1}, + }; + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = 1; + pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); + pool_info.pPoolSizes = pool_sizes; + err = vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptorPool); + check_vk_result(err); + } +} + +void ImGuiWidget::InitWindow() { + mainWindowData.Surface = surface; + + // Check for WSI support + VkBool32 res; + vkGetPhysicalDeviceSurfaceSupportKHR(gpu, queueFamily, mainWindowData.Surface, &res); + if (res != VK_TRUE) { + fprintf(stderr, "Error no WSI support on physical device 0\n"); + exit(-1); + } + + // Select Surface Format + const VkFormat requestSurfaceImageFormat[] = {VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM}; + const VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + mainWindowData.SurfaceFormat = + ImGui_ImplVulkanH_SelectSurfaceFormat(gpu, mainWindowData.Surface, requestSurfaceImageFormat, + (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); + + // Select Present Mode +#ifdef APP_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 + mainWindowData.PresentMode = + ImGui_ImplVulkanH_SelectPresentMode(gpu, mainWindowData.Surface, &present_modes[0], IM_ARRAYSIZE(present_modes)); + // printf("[vulkan] Selected PresentMode = %d\n", mainWindowData.PresentMode); + + // Create SwapChain, RenderPass, Framebuffer, etc. + IM_ASSERT(minImageCount >= 2); + ImGui_ImplVulkanH_CreateOrResizeWindow(instance, gpu, device, &mainWindowData, queueFamily, nullptr, width(), + height(), minImageCount); +} + +ImGuiWidget::ImGuiWidget(QWidget *parent) : QWidget(parent) { + setAttribute(Qt::WA_OpaquePaintEvent); + setAttribute(Qt::WA_NoSystemBackground); + setFocusPolicy(Qt::StrongFocus); + // HOWEVER, this important attribute stops the "paintEvent" slot from being called, + // thus we'll need to write our own method that paints to the screen every frame. + setAttribute(Qt::WA_PaintOnScreen); + windowHandle()->setSurfaceType(QWindow::VulkanSurface); + + SDL_InitSubSystem(SDL_INIT_VIDEO); +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + auto winPtr = reinterpret_cast(winId()); + + auto props = SDL_CreateProperties(); +#ifdef SDL_PLATFORM_LINUX + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER, winPtr); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, reinterpret_cast(winPtr)); +#elif SDL_PLATFORM_WINDOWS + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER, winPtr); +#else + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER, winPtr); +#endif + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, true); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true); + + window = SDL_CreateWindowWithProperties(props); + + uint32_t extensionCount = 0; + auto extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount); + InitVulkan(extensions, extensionCount); + + if (!SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface)) { + Util::panic("[ImGui Widget]: Failed to create Vulkan surface.\n"); + } + + InitWindow(); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + (void)io; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + // io.ConfigFlags |= ImGuiConfigFlags_ViewportsNoTaskBarIcons; + // io.ConfigFlags |= ImGuiConfigFlags_ViewportsNoMerge; + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + // ImGui::StyleColorsLight(); + + // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. + ImGuiStyle &style = ImGui::GetStyle(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { + style.WindowRounding = 0.0f; + style.Colors[ImGuiCol_WindowBg].w = 1.0f; + } + + // Setup Platform/Renderer backends + ImGui_ImplSDL3_InitForVulkan(window); + ImGui_ImplVulkan_InitInfo init_info = {}; + init_info.Instance = instance; + init_info.PhysicalDevice = gpu; + init_info.Device = device; + init_info.QueueFamily = queueFamily; + init_info.Queue = queue; + init_info.PipelineCache = pipelineCache; + init_info.DescriptorPool = descriptorPool; + init_info.RenderPass = mainWindowData.RenderPass; + init_info.Subpass = 0; + init_info.MinImageCount = minImageCount; + init_info.ImageCount = mainWindowData.ImageCount; + init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + init_info.Allocator = nullptr; + init_info.CheckVkResultFn = check_vk_result; + ImGui_ImplVulkan_Init(&init_info); +} + +void ImGuiWidget::resizeEvent(QResizeEvent *event) { + if (event->size().width() > 0 && event->size().height() > 0 && + (swapChainRebuild || mainWindowData.Width != event->size().width() || + mainWindowData.Height != event->size().height())) { + ImGui_ImplVulkan_SetMinImageCount(minImageCount); + ImGui_ImplVulkanH_CreateOrResizeWindow(instance, gpu, device, &mainWindowData, queueFamily, nullptr, + event->size().width(), event->size().height(), minImageCount); + mainWindowData.FrameIndex = 0; + swapChainRebuild = false; + } +} + +void ImGuiWidget::showEvent(QShowEvent *) { + connect(&timer, &QTimer::timeout, this, &ImGuiWidget::Repaint); + timer.setInterval(16); + timer.start(); +} + +void ImGuiWidget::Repaint() { + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + ImGui::ShowDemoWindow(); + ImGui::Render(); + + FrameRender(ImGui::GetDrawData()); + + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + + FramePresent(); +} + +void ImGuiWidget::FrameRender(ImDrawData *drawData) { + VkResult err; + + VkSemaphore image_acquired_semaphore = + mainWindowData.FrameSemaphores[mainWindowData.SemaphoreIndex].ImageAcquiredSemaphore; + VkSemaphore render_complete_semaphore = + mainWindowData.FrameSemaphores[mainWindowData.SemaphoreIndex].RenderCompleteSemaphore; + err = vkAcquireNextImageKHR(device, mainWindowData.Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE, + &mainWindowData.FrameIndex); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { + swapChainRebuild = true; + return; + } + check_vk_result(err); + + ImGui_ImplVulkanH_Frame *fd = &mainWindowData.Frames[mainWindowData.FrameIndex]; + { + err = vkWaitForFences(device, 1, &fd->Fence, VK_TRUE, + UINT64_MAX); // wait indefinitely instead of periodically checking + check_vk_result(err); + + err = vkResetFences(device, 1, &fd->Fence); + check_vk_result(err); + } + { + err = vkResetCommandPool(device, fd->CommandPool, 0); + check_vk_result(err); + VkCommandBufferBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + err = vkBeginCommandBuffer(fd->CommandBuffer, &info); + check_vk_result(err); + } + { + VkRenderPassBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + info.renderPass = mainWindowData.RenderPass; + info.framebuffer = fd->Framebuffer; + info.renderArea.extent.width = mainWindowData.Width; + info.renderArea.extent.height = mainWindowData.Height; + info.clearValueCount = 1; + info.pClearValues = &mainWindowData.ClearValue; + vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); + } + + // Record dear imgui primitives into command buffer + ImGui_ImplVulkan_RenderDrawData(drawData, fd->CommandBuffer); + + // Submit command buffer + vkCmdEndRenderPass(fd->CommandBuffer); + { + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &image_acquired_semaphore; + info.pWaitDstStageMask = &wait_stage; + info.commandBufferCount = 1; + info.pCommandBuffers = &fd->CommandBuffer; + info.signalSemaphoreCount = 1; + info.pSignalSemaphores = &render_complete_semaphore; + + err = vkEndCommandBuffer(fd->CommandBuffer); + check_vk_result(err); + err = vkQueueSubmit(queue, 1, &info, fd->Fence); + check_vk_result(err); + } +} + +void ImGuiWidget::FramePresent() { + if (swapChainRebuild) + return; + VkSemaphore render_complete_semaphore = + mainWindowData.FrameSemaphores[mainWindowData.SemaphoreIndex].RenderCompleteSemaphore; + VkPresentInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &render_complete_semaphore; + info.swapchainCount = 1; + info.pSwapchains = &mainWindowData.Swapchain; + info.pImageIndices = &mainWindowData.FrameIndex; + VkResult err = vkQueuePresentKHR(queue, &info); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) { + swapChainRebuild = true; + return; + } + check_vk_result(err); + mainWindowData.SemaphoreIndex = + (mainWindowData.SemaphoreIndex + 1) % mainWindowData.SemaphoreCount; // Now we can use the next set of semaphores +} diff --git a/src/frontend/ImGuiWidget.hpp b/src/frontend/ImGuiWidget.hpp new file mode 100644 index 00000000..246bbb0e --- /dev/null +++ b/src/frontend/ImGuiWidget.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +class ImGuiWidget : public QWidget { + QTimer timer; + + SDL_Window *window{}; + VkInstance instance = VK_NULL_HANDLE; + VkPhysicalDevice gpu = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + uint32_t queueFamily = static_cast(-1); + VkQueue queue = VK_NULL_HANDLE; + VkDebugReportCallbackEXT debugReport = VK_NULL_HANDLE; + VkPipelineCache pipelineCache = VK_NULL_HANDLE; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + VkSurfaceKHR surface; + + ImGui_ImplVulkanH_Window mainWindowData; + uint32_t minImageCount = 2; + bool swapChainRebuild = false; + + void SelectGpu(); + void InitVulkan(char const *const *, uint32_t); + void InitWindow(); + void FrameRender(ImDrawData *); + void FramePresent(); + +public slots: + void Repaint(); + +public: + [[nodiscard]] QPaintEngine *paintEngine() const override { return nullptr; } + ImGuiWidget(QWidget* parent); + + + void resizeEvent(QResizeEvent *event) override; + void showEvent(QShowEvent *) override; +}; diff --git a/src/frontend/KaizenQt.cpp b/src/frontend/KaizenQt.cpp index 89df71a3..ec7b2689 100644 --- a/src/frontend/KaizenQt.cpp +++ b/src/frontend/KaizenQt.cpp @@ -8,9 +8,9 @@ namespace fs = std::filesystem; KaizenQt::KaizenQt() noexcept : QWidget(nullptr) { - mainWindow = std::make_unique(); + mainWindow = std::make_unique(); settingsWindow = std::make_unique(); - emuThread = std::make_unique(*mainWindow->view.vulkanWidget, *settingsWindow); + emuThread = std::make_unique(*mainWindow->vulkanWidget, *settingsWindow); ConnectMainWindowSignalsToSlots(); Util::RPC::GetInstance().Update(Util::RPC::Idling); @@ -25,13 +25,13 @@ KaizenQt::KaizenQt() noexcept : QWidget(nullptr) { } void KaizenQt::ConnectMainWindowSignalsToSlots() noexcept { - connect(mainWindow.get(), &MainWindowController::OpenSettings, this, [this]() { settingsWindow->show(); }); - connect(mainWindow.get(), &MainWindowController::OpenROM, this, &KaizenQt::LoadROM); - connect(mainWindow.get(), &MainWindowController::Exit, this, &KaizenQt::Quit); - connect(mainWindow.get(), &MainWindowController::Reset, emuThread.get(), &EmuThread::Reset); - connect(mainWindow.get(), &MainWindowController::Stop, emuThread.get(), &EmuThread::Stop); - connect(mainWindow.get(), &MainWindowController::Stop, this, [this]() { mainWindow->setWindowTitle("Kaizen"); }); - connect(mainWindow.get(), &MainWindowController::Pause, emuThread.get(), &EmuThread::TogglePause); + connect(mainWindow.get(), &MainWindow::OpenSettings, this, [this]() { settingsWindow->show(); }); + connect(mainWindow.get(), &MainWindow::OpenROM, this, &KaizenQt::LoadROM); + connect(mainWindow.get(), &MainWindow::Exit, this, &KaizenQt::Quit); + connect(mainWindow.get(), &MainWindow::Reset, emuThread.get(), &EmuThread::Reset); + connect(mainWindow.get(), &MainWindow::Stop, emuThread.get(), &EmuThread::Stop); + connect(mainWindow.get(), &MainWindow::Stop, this, [this]() { mainWindow->setWindowTitle("Kaizen"); }); + connect(mainWindow.get(), &MainWindow::Pause, emuThread.get(), &EmuThread::TogglePause); } void KaizenQt::dragEnterEvent(QDragEnterEvent *event) { @@ -46,9 +46,9 @@ void KaizenQt::dropEvent(QDropEvent *event) { } void KaizenQt::LoadROM(const QString &fileName) noexcept { - mainWindow->view.actionPause->setEnabled(true); - mainWindow->view.actionReset->setEnabled(true); - mainWindow->view.actionStop->setEnabled(true); + mainWindow->actionPause->setEnabled(true); + mainWindow->actionReset->setEnabled(true); + mainWindow->actionStop->setEnabled(true); emuThread->start(); emuThread->core.LoadROM(fileName.toStdString()); auto gameNameDB = emuThread->core.cpu->GetMem().rom.gameNameDB; diff --git a/src/frontend/KaizenQt.hpp b/src/frontend/KaizenQt.hpp index 8f9765a5..604a4706 100644 --- a/src/frontend/KaizenQt.hpp +++ b/src/frontend/KaizenQt.hpp @@ -35,7 +35,7 @@ public: private: void Quit() noexcept; void ConnectMainWindowSignalsToSlots() noexcept; - std::unique_ptr mainWindow; + std::unique_ptr mainWindow; std::unique_ptr settingsWindow; std::unique_ptr emuThread; }; diff --git a/src/frontend/MainWindow.cpp b/src/frontend/MainWindow.cpp index 1be86ffe..37375ef0 100644 --- a/src/frontend/MainWindow.cpp +++ b/src/frontend/MainWindow.cpp @@ -3,49 +3,157 @@ #include #include #include +#include +#include +#include -MainWindowController::MainWindowController() noexcept { - view.setupUi(this); - view.actionPause->setDisabled(true); - view.actionReset->setDisabled(true); - view.actionStop->setDisabled(true); - view.vulkanWidget->hide(); +MainWindow::MainWindow() noexcept { + if (objectName().isEmpty()) + setObjectName("MainWindow"); + resize(800, 646); + actionAbout = new QAction(this); + actionAbout->setObjectName("actionAbout"); + actionOpen = new QAction(this); + actionOpen->setObjectName("actionOpen"); + actionExit = new QAction(this); + actionExit->setObjectName("actionExit"); + actionPause = new QAction(this); + actionPause->setObjectName("actionPause"); + actionReset = new QAction(this); + actionReset->setObjectName("actionReset"); + actionStop = new QAction(this); + actionStop->setObjectName("actionStop"); + actionSettings = new QAction(this); + actionSettings->setObjectName("actionSettings"); + centralwidget = new QWidget(this); + centralwidget->setObjectName("centralwidget"); + verticalLayout = new QVBoxLayout(centralwidget); + verticalLayout->setSpacing(0); + verticalLayout->setObjectName("verticalLayout"); + verticalLayout->setContentsMargins(0, 0, 0, 0); + horizontalLayout = new QHBoxLayout(centralwidget); + horizontalLayout->setSpacing(0); + verticalLayout->setObjectName("horizontalLayout"); + verticalLayout->setContentsMargins(0, 0, 0, 0); + vulkanWidget = new RenderWidget(centralwidget); + vulkanWidget->setObjectName("vulkanWidget"); + debugger = new ImGuiWidget(centralwidget); + debugger->setObjectName("debugger"); + + horizontalLayout->addWidget(vulkanWidget); + horizontalLayout->addWidget(debugger); + verticalLayout->addLayout(horizontalLayout); + + setCentralWidget(centralwidget); + menubar = new QMenuBar(this); + menubar->setObjectName("menubar"); + menubar->setGeometry(QRect(0, 0, 800, 22)); + menuFile = new QMenu(menubar); + menuFile->setObjectName("menuFile"); + menuEmulation = new QMenu(menubar); + menuEmulation->setObjectName("menuEmulation"); + menuAbout = new QMenu(menubar); + menuAbout->setObjectName("menuAbout"); + setMenuBar(menubar); + statusbar = new QStatusBar(this); + statusbar->setObjectName("statusbar"); + setStatusBar(statusbar); + + menubar->addAction(menuFile->menuAction()); + menubar->addAction(menuEmulation->menuAction()); + menubar->addAction(menuAbout->menuAction()); + menuFile->addAction(actionOpen); + menuFile->addAction(actionExit); + menuEmulation->addAction(actionSettings); + menuEmulation->addAction(actionPause); + menuEmulation->addAction(actionReset); + menuEmulation->addAction(actionStop); + menuAbout->addAction(actionAbout); + + Retranslate(); + + QMetaObject::connectSlotsByName(this); + actionPause->setDisabled(true); + actionReset->setDisabled(true); + actionStop->setDisabled(true); + vulkanWidget->hide(); ConnectSignalsToSlots(); } -void MainWindowController::ConnectSignalsToSlots() noexcept { - connect(view.actionOpen, &QAction::triggered, this, [this]() { +void MainWindow::Retranslate() { + setWindowTitle(QCoreApplication::translate("MainWindow", "Kaizen", nullptr)); + actionAbout->setText(QCoreApplication::translate("MainWindow", "About Kaizen", nullptr)); +#if QT_CONFIG(statustip) + actionAbout->setStatusTip(QCoreApplication::translate("MainWindow", "About this emulator", nullptr)); +#endif // QT_CONFIG(statustip) + actionOpen->setText(QCoreApplication::translate("MainWindow", "Open...", nullptr)); +#if QT_CONFIG(statustip) + actionOpen->setStatusTip(QCoreApplication::translate("MainWindow", "Open a ROM", nullptr)); +#endif // QT_CONFIG(statustip) +#if QT_CONFIG(shortcut) + actionOpen->setShortcut(QCoreApplication::translate("MainWindow", "Ctrl+O", nullptr)); +#endif // QT_CONFIG(shortcut) + actionExit->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr)); +#if QT_CONFIG(statustip) + actionExit->setStatusTip(QCoreApplication::translate("MainWindow", "Quit the emulator", nullptr)); +#endif // QT_CONFIG(statustip) + actionPause->setText(QCoreApplication::translate("MainWindow", "Pause", nullptr)); +#if QT_CONFIG(statustip) + actionPause->setStatusTip(QCoreApplication::translate("MainWindow", "Pause the emulation", nullptr)); +#endif // QT_CONFIG(statustip) + actionReset->setText(QCoreApplication::translate("MainWindow", "Reset", nullptr)); +#if QT_CONFIG(statustip) + actionReset->setStatusTip(QCoreApplication::translate("MainWindow", "Reset the emulation", nullptr)); +#endif // QT_CONFIG(statustip) + actionStop->setText(QCoreApplication::translate("MainWindow", "Stop", nullptr)); +#if QT_CONFIG(statustip) + actionStop->setStatusTip(QCoreApplication::translate("MainWindow", "Stop the emulation", nullptr)); +#endif // QT_CONFIG(statustip) + actionSettings->setText(QCoreApplication::translate("MainWindow", "Settings", nullptr)); +#if QT_CONFIG(tooltip) + actionSettings->setToolTip(QCoreApplication::translate("MainWindow", "Settings", nullptr)); +#endif // QT_CONFIG(tooltip) +#if QT_CONFIG(statustip) + actionSettings->setStatusTip(QCoreApplication::translate("MainWindow", "Open the settings window", nullptr)); +#endif // QT_CONFIG(statustip) + menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr)); + menuEmulation->setTitle(QCoreApplication::translate("MainWindow", "Emulation", nullptr)); + menuAbout->setTitle(QCoreApplication::translate("MainWindow", "Help", nullptr)); +} // retranslateUi + +void MainWindow::ConnectSignalsToSlots() noexcept { + connect(actionOpen, &QAction::triggered, this, [this]() { QString file_name = QFileDialog::getOpenFileName( this, "Nintendo 64 executable", QString(), "All supported types (*.zip *.ZIP *.7z *.7Z *.rar *.RAR *.tar *.TAR *.n64 *.N64 *.v64 *.V64 *.z64 *.Z64)"); if (!file_name.isEmpty()) { emit OpenROM(file_name); - view.vulkanWidget->show(); + vulkanWidget->show(); } }); - connect(view.actionExit, &QAction::triggered, this, [this]() { emit Exit(); }); + connect(actionExit, &QAction::triggered, this, [this]() { emit Exit(); }); - connect(this, &MainWindowController::destroyed, this, [this]() { emit Exit(); }); + connect(this, &MainWindow::destroyed, this, [this]() { emit Exit(); }); - connect(view.actionReset, &QAction::triggered, this, [this]() { emit Reset(); }); + connect(actionReset, &QAction::triggered, this, [this]() { emit Reset(); }); - connect(view.actionStop, &QAction::triggered, this, [this]() { - view.vulkanWidget->hide(); - view.actionPause->setDisabled(true); - view.actionReset->setDisabled(true); - view.actionStop->setDisabled(true); + connect(actionStop, &QAction::triggered, this, [this]() { + vulkanWidget->hide(); + actionPause->setDisabled(true); + actionReset->setDisabled(true); + actionStop->setDisabled(true); emit Stop(); }); - connect(view.actionPause, &QAction::triggered, this, [this]() { + connect(actionPause, &QAction::triggered, this, [this]() { textPauseToggle = !textPauseToggle; - view.actionPause->setText(textPauseToggle ? "Resume" : "Pause"); + actionPause->setText(textPauseToggle ? "Resume" : "Pause"); emit Pause(); }); - connect(view.actionAbout, &QAction::triggered, this, [this]() { + connect(actionAbout, &QAction::triggered, this, [this]() { QMessageBox::about(this, tr("About Kaizen"), tr("Kaizen is a Nintendo 64 emulator that strives to offer a friendly user " "experience and great compatibility.\n" @@ -53,5 +161,5 @@ void MainWindowController::ConnectSignalsToSlots() noexcept { "Nintendo 64 is a registered trademarks of Nintendo Co., Ltd.")); }); - connect(view.actionSettings, &QAction::triggered, this, [this]() { emit OpenSettings(); }); + connect(actionSettings, &QAction::triggered, this, [this]() { emit OpenSettings(); }); } diff --git a/src/frontend/MainWindow.hpp b/src/frontend/MainWindow.hpp index 65cd6ef9..9215e472 100644 --- a/src/frontend/MainWindow.hpp +++ b/src/frontend/MainWindow.hpp @@ -1,18 +1,36 @@ #pragma once #include #include +#include #include -#include "ui_mainwindow.h" +#include -class MainWindowController : public QMainWindow { +class MainWindow : public QMainWindow { Q_OBJECT public: - MainWindowController() noexcept; + MainWindow() noexcept; - Ui::MainWindow view; + QAction *actionAbout; + QAction *actionOpen; + QAction *actionExit; + QAction *actionPause; + QAction *actionReset; + QAction *actionStop; + QAction *actionSettings; + QWidget *centralwidget; + QVBoxLayout *verticalLayout; + QHBoxLayout *horizontalLayout; + RenderWidget *vulkanWidget; + ImGuiWidget *debugger; + QMenuBar *menubar; + QMenu *menuFile; + QMenu *menuEmulation; + QMenu *menuAbout; + QStatusBar *statusbar; private: + void Retranslate(); void ConnectSignalsToSlots() noexcept; bool textPauseToggle = false; diff --git a/src/frontend/RenderWidget.hpp b/src/frontend/RenderWidget.hpp index e08745ca..1facb518 100644 --- a/src/frontend/RenderWidget.hpp +++ b/src/frontend/RenderWidget.hpp @@ -1,12 +1,9 @@ #pragma once #undef signals #include -#include #include #include #include -#include -#include struct QtInstanceFactory : Vulkan::InstanceFactory { VkInstance create_instance(const VkInstanceCreateInfo *info) override { diff --git a/src/frontend/mainwindow.ui b/src/frontend/mainwindow.ui deleted file mode 100644 index c1ad4475..00000000 --- a/src/frontend/mainwindow.ui +++ /dev/null @@ -1,146 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 800 - 646 - - - - Kaizen - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - 0 - 0 - 800 - 22 - - - - - File - - - - - - - Emulation - - - - - - - - - Help - - - - - - - - - - - About Kaizen - - - About this emulator - - - - - Open... - - - Open a ROM - - - Ctrl+O - - - - - Exit - - - Quit the emulator - - - - - Pause - - - Pause the emulation - - - - - Reset - - - Reset the emulation - - - - - Stop - - - Stop the emulation - - - - - Settings - - - Settings - - - Open the settings window - - - - - - RenderWidget - QWidget -
RenderWidget.hpp
-
-
- - -