From 7aadf086de4c5e689d7e8999a837d3d62a68797b Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Mon, 11 Apr 2022 23:11:33 -0400 Subject: [PATCH] Add basic vulkan renderer to mdlviewer Right now it just displays a red screen --- CMakeLists.txt | 1 + mdlviewer/CMakeLists.txt | 2 +- mdlviewer/include/mainwindow.h | 4 + mdlviewer/src/mainwindow.cpp | 54 +++++ renderer/CMakeLists.txt | 5 + renderer/include/renderer.hpp | 30 +++ renderer/src/renderer.cpp | 404 +++++++++++++++++++++++++++++++++ 7 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 renderer/CMakeLists.txt create mode 100644 renderer/include/renderer.hpp create mode 100644 renderer/src/renderer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a94eec..d8f0f87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,5 +30,6 @@ else() set(LIBRARIES fmt::fmt ${LIBRARIES}) endif() +add_subdirectory(renderer) add_subdirectory(exdviewer) add_subdirectory(mdlviewer) \ No newline at end of file diff --git a/mdlviewer/CMakeLists.txt b/mdlviewer/CMakeLists.txt index d122f95..3289a57 100644 --- a/mdlviewer/CMakeLists.txt +++ b/mdlviewer/CMakeLists.txt @@ -4,7 +4,7 @@ add_executable(mdlviewer target_include_directories(mdlviewer PUBLIC include) -target_link_libraries(mdlviewer PUBLIC libxiv ${LIBRARIES} Qt5::Core Qt5::Widgets) +target_link_libraries(mdlviewer PUBLIC libxiv ${LIBRARIES} Qt5::Core Qt5::Widgets renderer) install(TARGETS mdlviewer DESTINATION "${INSTALL_BIN_PATH}") diff --git a/mdlviewer/include/mainwindow.h b/mdlviewer/include/mainwindow.h index 59c6816..bcabac1 100644 --- a/mdlviewer/include/mainwindow.h +++ b/mdlviewer/include/mainwindow.h @@ -2,6 +2,8 @@ #include +#include "renderer.hpp" + class GameData; class MainWindow : public QMainWindow { @@ -10,4 +12,6 @@ public: private: GameData& data; + + Renderer* renderer; }; \ No newline at end of file diff --git a/mdlviewer/src/mainwindow.cpp b/mdlviewer/src/mainwindow.cpp index 54dff01..18d5472 100644 --- a/mdlviewer/src/mainwindow.cpp +++ b/mdlviewer/src/mainwindow.cpp @@ -4,12 +4,48 @@ #include #include #include +#include #include "gamedata.h" #include "exhparser.h" #include "exdparser.h" #include "mdlparser.h" +class VulkanWindow : public QWindow +{ +public: + VulkanWindow(Renderer* renderer, QVulkanInstance* instance) : m_renderer(renderer), m_instance(instance) { + setSurfaceType(VulkanSurface); + setVulkanInstance(instance); + } + + void exposeEvent(QExposeEvent *) { + if (isExposed()) { + if (!m_initialized) { + m_initialized = true; + render(); + } + } + } + + bool event(QEvent *e) { + if (e->type() == QEvent::UpdateRequest) + render(); + + return QWindow::event(e); + } + + void render() { + m_renderer->render(); + requestUpdate(); + } + +private: + bool m_initialized = false; + Renderer* m_renderer; + QVulkanInstance* m_instance; +}; + MainWindow::MainWindow(GameData& data) : data(data) { setWindowTitle("mdlviewer"); @@ -18,4 +54,22 @@ MainWindow::MainWindow(GameData& data) : data(data) { auto layout = new QHBoxLayout(); dummyWidget->setLayout(layout); + + renderer = new Renderer(); + + QVulkanInstance inst; + inst.setVkInstance(renderer->instance); + inst.setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect); + inst.create(); + + VulkanWindow* vkWindow = new VulkanWindow(renderer, &inst); + vkWindow->show(); + vkWindow->setVulkanInstance(&inst); + + auto surface = inst.surfaceForWindow(vkWindow); + renderer->initSwapchain(surface); + + auto widget = QWidget::createWindowContainer(vkWindow); + layout->addWidget(widget); + } \ No newline at end of file diff --git a/renderer/CMakeLists.txt b/renderer/CMakeLists.txt new file mode 100644 index 0000000..27fdd12 --- /dev/null +++ b/renderer/CMakeLists.txt @@ -0,0 +1,5 @@ +find_package(Vulkan REQUIRED) + +add_library(renderer src/renderer.cpp) +target_include_directories(renderer PUBLIC include) +target_link_libraries(renderer PUBLIC Vulkan::Vulkan fmt::fmt) \ No newline at end of file diff --git a/renderer/include/renderer.hpp b/renderer/include/renderer.hpp new file mode 100644 index 0000000..7d06130 --- /dev/null +++ b/renderer/include/renderer.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class Renderer { +public: + Renderer(); + + void initSwapchain(VkSurfaceKHR surface); + + void render(); + + VkInstance instance = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + VkQueue graphicsQueue = VK_NULL_HANDLE, presentQueue = VK_NULL_HANDLE; + VkCommandPool commandPool = VK_NULL_HANDLE; + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + VkExtent2D swapchainExtent; + std::vector swapchainImages; + std::vector swapchainViews; + std::vector swapchainFramebuffers; + VkRenderPass renderPass; + std::array commandBuffers; + std::array inFlightFences; + std::array imageAvailableSemaphores, renderFinishedSemaphores; + uint32_t currentFrame = 0; +}; \ No newline at end of file diff --git a/renderer/src/renderer.cpp b/renderer/src/renderer.cpp new file mode 100644 index 0000000..b79436b --- /dev/null +++ b/renderer/src/renderer.cpp @@ -0,0 +1,404 @@ +#include "renderer.hpp" + +#include +#include +#include +#include +#include + +Renderer::Renderer() { + VkApplicationInfo applicationInfo = {}; + + const std::array instanceExtensions = {"VK_KHR_surface", "VK_KHR_xcb_surface", "VK_EXT_debug_utils", "VK_KHR_xlib_surface"}; + + VkInstanceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.ppEnabledExtensionNames = instanceExtensions.data(); + createInfo.enabledExtensionCount = instanceExtensions.size(); + + vkCreateInstance(&createInfo, nullptr, &instance); + + // pick physical device + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (auto device : devices) { + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + } + + physicalDevice = devices[0]; + + uint32_t extensionCount = 0; + vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, + &extensionCount, nullptr); + + std::vector extensionProperties(extensionCount); + vkEnumerateDeviceExtensionProperties( + physicalDevice, nullptr, &extensionCount, extensionProperties.data()); + + // we want to choose the portability subset on platforms that + // support it, this is a requirement of the portability spec + std::vector deviceExtensions = {"VK_KHR_swapchain"}; + for (auto extension : extensionProperties) { + if (!strcmp(extension.extensionName, "VK_KHR_portability_subset")) + deviceExtensions.push_back("VK_KHR_portability_subset"); + } + + uint32_t graphicsFamilyIndex = 0, presentFamilyIndex = 0; + + // create logical device + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, + nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, + queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueCount > 0 && + queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + graphicsFamilyIndex = i; + } + + i++; + } + + std::vector queueCreateInfos; + + if (graphicsFamilyIndex == presentFamilyIndex) { + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = graphicsFamilyIndex; + queueCreateInfo.queueCount = 1; + + float queuePriority = 1.0f; + queueCreateInfo.pQueuePriorities = &queuePriority; + + queueCreateInfos.push_back(queueCreateInfo); + } else { + // graphics + { + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = graphicsFamilyIndex; + queueCreateInfo.queueCount = 1; + + float queuePriority = 1.0f; + queueCreateInfo.pQueuePriorities = &queuePriority; + + queueCreateInfos.push_back(queueCreateInfo); + } + + // present + { + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = presentFamilyIndex; + queueCreateInfo.queueCount = 1; + + float queuePriority = 1.0f; + queueCreateInfo.pQueuePriorities = &queuePriority; + + queueCreateInfos.push_back(queueCreateInfo); + } + } + + VkDeviceCreateInfo deviceCeateInfo = {}; + deviceCeateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + deviceCeateInfo.pQueueCreateInfos = queueCreateInfos.data(); + deviceCeateInfo.queueCreateInfoCount = + static_cast(queueCreateInfos.size()); + deviceCeateInfo.ppEnabledExtensionNames = deviceExtensions.data(); + deviceCeateInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + + VkPhysicalDeviceFeatures enabledFeatures = {}; + + vkCreateDevice(physicalDevice, &deviceCeateInfo, nullptr, &device); + + // get queues + vkGetDeviceQueue(device, graphicsFamilyIndex, 0, &graphicsQueue); + vkGetDeviceQueue(device, presentFamilyIndex, 0, &presentQueue); + + // command pool + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = graphicsFamilyIndex; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool); + + fmt::print("Initialized renderer!\n"); +} + +void Renderer::initSwapchain(VkSurfaceKHR surface) { + fmt::print("Creating swapchain...\n"); + + // TODO: fix this pls + VkBool32 supported; + vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, 0, + surface, &supported); + + // query swapchain support + VkSurfaceCapabilitiesKHR capabilities; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physicalDevice, surface, &capabilities); + + std::vector formats; + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR( + physicalDevice, surface, &formatCount, nullptr); + + formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR( + physicalDevice, surface, &formatCount, formats.data()); + + std::vector presentModes; + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR( + physicalDevice, surface, &presentModeCount, nullptr); + + presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR( + physicalDevice, surface, &presentModeCount, + presentModes.data()); + + // choosing swapchain features + VkSurfaceFormatKHR swapchainSurfaceFormat = formats[0]; + for (const auto& availableFormat : formats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + swapchainSurfaceFormat = availableFormat; + } + } + + VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; + for (const auto& availablePresentMode : presentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + swapchainPresentMode = availablePresentMode; + } + } + + uint32_t imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && + imageCount > capabilities.maxImageCount) { + imageCount = capabilities.maxImageCount; + } + + // create swapchain + VkSwapchainCreateInfoKHR createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = swapchainSurfaceFormat.format; + createInfo.imageColorSpace = swapchainSurfaceFormat.colorSpace; + createInfo.imageExtent.width = capabilities.currentExtent.width; + createInfo.imageExtent.height = capabilities.currentExtent.height; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = swapchainPresentMode; + createInfo.clipped = VK_TRUE; + + vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapchain); + + swapchainExtent = capabilities.currentExtent; + + vkGetSwapchainImagesKHR(device, swapchain, &imageCount, + nullptr); + swapchainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapchain, &imageCount, swapchainImages.data()); + + swapchainViews.resize(swapchainImages.size()); + + for (size_t i = 0; i < swapchainImages.size(); i++) { + VkImageViewCreateInfo view_create_info = {}; + view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_create_info.image = swapchainImages[i]; + view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_create_info.format = swapchainSurfaceFormat.format; + view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_create_info.subresourceRange.baseMipLevel = 0; + view_create_info.subresourceRange.levelCount = 1; + view_create_info.subresourceRange.baseArrayLayer = 0; + view_create_info.subresourceRange.layerCount = 1; + + vkCreateImageView(device, &view_create_info, nullptr,&swapchainViews[i]); + } + + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = swapchainSurfaceFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependency.dependencyFlags = 0; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass); + + swapchainFramebuffers.resize(swapchainViews.size()); + + for (size_t i = 0; i < swapchainViews.size(); i++) { + VkImageView attachments[] = {swapchainViews[i]}; + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapchainExtent.width; + framebufferInfo.height = swapchainExtent.height; + framebufferInfo.layers = 1; + + vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapchainFramebuffers[i]); + } + + // allocate command buffers + for(int i = 0; i < 3; i++) { + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]); + } + + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceCreateInfo = {}; + fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < 3; i++) { + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]); + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]); + vkCreateFence(device, &fenceCreateInfo, nullptr, &inFlightFences[i]); + } +} + +void Renderer::render() { + vkWaitForFences( + device, 1, + &inFlightFences[currentFrame], + VK_TRUE, std::numeric_limits::max()); + + uint32_t imageIndex = 0; + VkResult result = vkAcquireNextImageKHR( + device, swapchain, + std::numeric_limits::max(), + imageAvailableSemaphores[currentFrame], + VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + fmt::print("error out of date\n"); + return; + } + + VkCommandBuffer commandBuffer = commandBuffers[currentFrame]; + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkRenderPassBeginInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapchainFramebuffers[imageIndex]; + + static float i = 0; + VkClearValue clearValue = {}; + clearValue.color.float32[0] = sin(i); + i += 0.01; + clearValue.color.float32[3] = 1; + + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearValue; + renderPassInfo.renderArea.extent = swapchainExtent; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdEndRenderPass(commandBuffer); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + vkResetFences(device, 1,&inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) + return; + + // present + VkPresentInfoKHR presentInfo = {}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + VkSwapchainKHR swapChains[] = {swapchain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + vkQueuePresentKHR(presentQueue, &presentInfo); + + currentFrame = (currentFrame + 1) % 3; +} \ No newline at end of file