Modules/VulkanGraphics/private/VulkanWindow.cpp
Шурупов Илья Викторович 6d200eb171 Port rendering stack from OpenGL to Vulkan
Replace the OpenGL rendering backend with Vulkan across the engine:

- Graphics: add VulkanContext (instance/device/swapchain/renderpass with
  depth-stencil), port Canvas to nanovg-on-Vulkan and DebugGUI to the imgui
  Vulkan backend, drop GLEW. Wait for the Wayland configure handshake before
  the first present and rebuild the swapchain to the confirmed size so the
  window maps reliably on fractional-scale outputs.
- RasterRender: reimplement RenderBuffer, shaders and object pipeline on
  Vulkan (offscreen VkImage target with MSAA resolve, SPIR-V pipelines,
  push-constant MVP, depth remap for Vulkan clip space).
- Sketch3D: port the stroke renderer (geometry-shader line expansion) and
  stroke GPU buffers to Vulkan.
- 3DEditor: port the path-tracer texture upload to a Vulkan staging upload
  and keep raster/path-tracer viewports oriented consistently.
- Add a nanovg external-VkImage bridge plus a render-target registry so 3D
  render targets can be shown inside the 2D canvas.
- Prefer the Wayland platform in GLFW regardless of DISPLAY.
- Widgets: make BasicLayout size distribution converge with a sub-pixel
  epsilon instead of an exact-zero comparison, which could otherwise spin
  forever on fractional-scale displays and block the first frame.

Vendor nanovg_vulkan and add a standalone VulkanGraphics module.
2026-10-13 03:04:08 +03:00

352 lines
11 KiB
C++

#include "VulkanWindow.hpp"
#include "SwapChain.hpp"
#include <GLFW/glfw3.h>
#include <stdexcept>
#include <cassert>
#include <cstring>
#include <chrono>
VulkanWindow::VulkanWindow() {
mSwapChain = new SwapChain();
initWindow();
initVulkan();
}
VulkanWindow::~VulkanWindow() {
cleanup();
delete mSwapChain;
}
RenderCreateInfo VulkanWindow::getRenderInfo() {
return {
mDevice, mPhysicalDevice,
mSwapChain->mSwapChainExtent, mSwapChain->mSwapChainFormat,
mCommandPool, mGraphicsQueue, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
};
}
GLFWwindow* VulkanWindow::getWindow() {
return mWindow;
}
VkDevice VulkanWindow::getDevice() {
return mDevice;
}
void VulkanWindow::setRenderPass(VkRenderPass renderPass) {
mRenderPass = renderPass;
mSwapChain->createSwapChainFramebuffers(mDevice, mRenderPass);
}
void VulkanWindow::pollEvents() {
glfwPollEvents();
if (mWindowSizeDirtyFlag) {
if (timeDeltaMs(mWindowSizeDirtyFlagTime) > mWindowSizeApplyMinDelay) {
recreateSwapChain(mWindowFramebufferSize.first, mWindowFramebufferSize.second);
mWindowSizeDirtyFlag = false;
}
}
}
void VulkanWindow::waitDevice() {
vkDeviceWaitIdle(mDevice);
}
void VulkanWindow::scheduleWindowResize(int sizeX, int sizeY) {
mWindowSizeDirtyFlagTime = std::chrono::system_clock::now();
mWindowSizeDirtyFlag = true;
mWindowFramebufferSize = std::make_pair(sizeX, sizeY);
assert(!(sizeX <= 0 || sizeY <= 0));
}
void VulkanWindow::initWindow() {
// glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
// glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
mWindow = glfwCreateWindow(800, 600, "App", nullptr, nullptr);
scheduleWindowResize(800, 600);
glfwSetWindowUserPointer(mWindow, this);
glfwSetFramebufferSizeCallback(mWindow, [](GLFWwindow *window, int sizeX, int sizeY) {
((VulkanWindow *) glfwGetWindowUserPointer(window))->scheduleWindowResize(sizeX, sizeY);
});
}
void VulkanWindow::initVulkan() {
const std::vector<const char *> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
// VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME,
};
uint32_t glfwExtensionCount = 0;
const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> instanceExtensions(glfwExtensionCount);
for (auto i = 0; i < glfwExtensionCount; i++) {
instanceExtensions[i] = glfwExtensions[i];
}
// instanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
mInstance = createInstance(instanceExtensions.data(), instanceExtensions.size(), VK_API_VERSION_1_3);
createWindowSurface();
mPhysicalDevice = pickPhysicalDevice(mInstance, [&](VkPhysicalDevice device) {
VkPhysicalDeviceProperties properties;
VkPhysicalDeviceFeatures features;
vkGetPhysicalDeviceProperties(device, &properties);
vkGetPhysicalDeviceFeatures(device, &features);
// if (properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) return false;
if (!features.geometryShader) return false;
if (!checkDeviceExtensions(device, deviceExtensions)) return false;
if (!SwapChain::checkDeviceSwapChain(device, mSurface)) return false;
return true;
});
if (!mPhysicalDevice) {
throw std::runtime_error("no suitable gpu");
}
QueuePickPolicy pickGraphicsQueue = [](const VkQueueFamilyProperties &properties, uint32_t index) -> bool {
return bool(properties.queueFlags & VK_QUEUE_GRAPHICS_BIT);
};
QueuePickPolicy pickPresentQueue = [this](const VkQueueFamilyProperties& properties, uint32_t index) -> bool {
VkBool32 presentationQueueSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, index, mSurface, &presentationQueueSupport);
return bool(presentationQueueSupport);
};
mGraphicsQueueFamilyIndex = findQueueFamilyIndex(mPhysicalDevice, pickGraphicsQueue);
mPresentationQueueFamilyIndex = findQueueFamilyIndex(mPhysicalDevice, pickPresentQueue);
if (mPresentationQueueFamilyIndex == -1 || mGraphicsQueueFamilyIndex == -1) {
throw std::runtime_error("failed to pick family queues");
}
createLogicalDevice(deviceExtensions);
getQueues();
int width, height;
glfwGetFramebufferSize(mWindow, &width, &height);
SwapChain::CreateInfo swapChainCreateInfo{
.device = mDevice,
.physicalDevice = mPhysicalDevice,
.surface = mSurface,
.sizeX = width,
.sizeY = height,
.graphicsQueueFamilyIndex = (uint32_t) mGraphicsQueueFamilyIndex,
.presentationQueueFamilyIndex = (uint32_t) mPresentationQueueFamilyIndex,
};
mSwapChain->createSwapChain(swapChainCreateInfo);
mSwapChain->createSwapChainImageViews(mDevice);
createCommandPool();
createCommandBuffer();
createSynchronizationObjects();
}
void VulkanWindow::createLogicalDevice(const std::vector<const char *> &deviceExtensions) {
float queuePriority = 1.f;
VkDeviceQueueCreateInfo graphicsQueueCreateInfos{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = (uint32_t) mGraphicsQueueFamilyIndex,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
VkDeviceQueueCreateInfo presentationQueueCreateInfos{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = (uint32_t) mPresentationQueueFamilyIndex,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
std::vector<VkDeviceQueueCreateInfo> queues = {graphicsQueueCreateInfos};
if (mPresentationQueueFamilyIndex != mGraphicsQueueFamilyIndex) {
queues.push_back(presentationQueueCreateInfos);
}
VkPhysicalDeviceFeatures features{};
VkDeviceCreateInfo deviceCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = (uint32_t) queues.size(),
.pQueueCreateInfos = queues.data(),
.enabledExtensionCount = (uint32_t) deviceExtensions.size(),
.ppEnabledExtensionNames = deviceExtensions.data(),
.pEnabledFeatures = &features,
};
vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice);
if (mDevice == VK_NULL_HANDLE) throw std::runtime_error("failed to create vulkan logical device");
}
void VulkanWindow::getQueues() {
vkGetDeviceQueue(mDevice, mGraphicsQueueFamilyIndex, 0, &mGraphicsQueue);
vkGetDeviceQueue(mDevice, mPresentationQueueFamilyIndex, 0, &mPresentQueue);
}
void VulkanWindow::createWindowSurface() {
if (glfwCreateWindowSurface(mInstance, mWindow, nullptr, &mSurface) != VK_SUCCESS) {
throw std::runtime_error("failed to create vulkan window surface");
}
}
void VulkanWindow::recreateSwapChain(int sizeX, int sizeY) {
vkDeviceWaitIdle(mDevice);
mSwapChain->destroySwapChain(mDevice);
SwapChain::CreateInfo createInfo{
.device = mDevice,
.physicalDevice = mPhysicalDevice,
.surface = mSurface,
.sizeX = sizeX,
.sizeY = sizeY,
.graphicsQueueFamilyIndex = (uint32_t) mGraphicsQueueFamilyIndex,
.presentationQueueFamilyIndex = (uint32_t) mPresentationQueueFamilyIndex,
};
mSwapChain->createSwapChain(createInfo);
mSwapChain->createSwapChainImageViews(mDevice);
mSwapChain->createSwapChainFramebuffers(mDevice, mRenderPass);
}
void VulkanWindow::createSynchronizationObjects() {
VkSemaphoreCreateInfo semaphoreCreateInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
};
bool failure = false;
failure |= vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, &mSemaphoreImageAcquired) != VK_SUCCESS;
failure |= vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, &mSemaphoreFramebufferDrawn) != VK_SUCCESS;
failure |= vkCreateFence(mDevice, &fenceCreateInfo, nullptr, &mFenceCanStartNewFrame) != VK_SUCCESS;
if (failure) throw std::runtime_error("failed to create synchronization objects");
}
VulkanWindow::FrameData VulkanWindow::startDrawFrame() {
vkWaitForFences(mDevice, 1, &mFenceCanStartNewFrame, VK_TRUE, UINT64_MAX);
vkResetFences(mDevice, 1, &mFenceCanStartNewFrame);
uint32_t imageIndex = 0;
vkAcquireNextImageKHR(mDevice, mSwapChain->mSwapChain, UINT64_MAX, mSemaphoreImageAcquired, VK_NULL_HANDLE,
&imageIndex);
vkResetCommandBuffer(mCommandBuffer, 0);
VkFramebuffer framebuffer = mSwapChain->mSwapChainFrameBuffers[imageIndex];
return {
.commandBuffer = mCommandBuffer,
.frameBuffer = framebuffer,
.extent2D = mSwapChain->mSwapChainExtent,
.swapChainImageIndex = imageIndex,
};
}
void VulkanWindow::endDrawFrame(const FrameData& frame) {
VkSemaphore waitSemaphores[] = {mSemaphoreImageAcquired};
VkSemaphore signalSemaphores[] = {mSemaphoreFramebufferDrawn};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = waitSemaphores,
.pWaitDstStageMask = waitStages,
.commandBufferCount = 1,
.pCommandBuffers = &mCommandBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = signalSemaphores,
};
if (vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, mFenceCanStartNewFrame) != VK_SUCCESS) {
throw std::runtime_error("failed to submit to graphics queue");
}
VkSwapchainKHR swapchains[] = {mSwapChain->mSwapChain};
VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = signalSemaphores,
.swapchainCount = 1,
.pSwapchains = swapchains,
.pImageIndices = &frame.swapChainImageIndex,
};
vkQueuePresentKHR(mPresentQueue, &presentInfo);
}
void VulkanWindow::destroySynchronizationObjects() {
vkDestroySemaphore(mDevice, mSemaphoreImageAcquired, nullptr);
vkDestroySemaphore(mDevice, mSemaphoreFramebufferDrawn, nullptr);
vkDestroyFence(mDevice, mFenceCanStartNewFrame, nullptr);
}
void VulkanWindow::cleanup() {
destroyCommandPool();
destroySynchronizationObjects();
mSwapChain->destroySwapChain(mDevice);
vkDestroyDevice(mDevice, nullptr);
vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
vkDestroyInstance(mInstance, nullptr);
glfwDestroyWindow(mWindow);
glfwTerminate();
}
void VulkanWindow::createCommandPool() {
VkCommandPoolCreateInfo createInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = (uint32_t) mGraphicsQueueFamilyIndex,
};
if (vkCreateCommandPool(mDevice, &createInfo, nullptr, &mCommandPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool");
}
}
void VulkanWindow::createCommandBuffer() {
VkCommandBufferAllocateInfo allocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = mCommandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
if (vkAllocateCommandBuffers(mDevice, &allocateInfo, &mCommandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to create command buffer");
}
}
void VulkanWindow::destroyCommandPool() {
vkDestroyCommandPool(mDevice, mCommandPool, nullptr);
}