diff --git a/CMakeLists.txt b/CMakeLists.txt index 6688e3bc5..9f1f40296 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ set(CMAKE_MODULE_PATH "${love_SOURCE_DIR}/extra/cmake" ${CMAKE_MODULE_PATH}) # Needed for shared libs on Linux. (-fPIC). set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) -set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD 17) if(MSVC) set(LOVE_CONSOLE_EXE_NAME lovec) @@ -66,6 +66,8 @@ if(POLICY CMP0072) endif() if(MEGA) + find_package(Vulkan REQUIRED) + # LOVE_MSVC_DLLS contains runtime DLLs that should be bundled with the love # binary (in e.g. the installer). Example: msvcp140.dll. set(LOVE_MSVC_DLLS ${MEGA_MSVC_DLLS}) @@ -73,7 +75,7 @@ if(MEGA) # LOVE_INCLUDE_DIRS contains the search directories for #include. It's mostly # not needed for MEGA builds, since almost all the libraries (except LuaJIT) # are CMake targets, causing include paths to be added automatically. - set(LOVE_INCLUDE_DIRS) + set(LOVE_INCLUDE_DIRS ${Vulkan_INCLUDE_DIRS}) if(APPLE) # Some files do #include , but building with megasource @@ -96,6 +98,7 @@ if(MEGA) ${MEGA_SDL2MAIN} ${MEGA_SDL2} ${MEGA_ZLIB} + ${Vulkan_LIBRARIES} ) # These DLLs are moved next to the love binary in a post-build step to @@ -568,13 +571,24 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/Texture.h ) +set(LOVE_SRC_MODULE_GRAPHICS_VULKAN + src/modules/graphics/vulkan/Graphics.h + src/modules/graphics/vulkan/Graphics.cpp + src/modules/graphics/vulkan/Shader.h + src/modules/graphics/vulkan/Shader.cpp + src/modules/graphics/vulkan/ShaderStage.h + src/modules/graphics/vulkan/ShaderStage.cpp +) + set(LOVE_SRC_MODULE_GRAPHICS ${LOVE_SRC_MODULE_GRAPHICS_ROOT} ${LOVE_SRC_MODULE_GRAPHICS_OPENGL} + ${LOVE_SRC_MODULE_GRAPHICS_VULKAN} ) source_group("modules\\graphics" FILES ${LOVE_SRC_MODULE_GRAPHICS_ROOT}) source_group("modules\\graphics\\opengl" FILES ${LOVE_SRC_MODULE_GRAPHICS_OPENGL}) +source_group("modules\\graphics\\vulkan" FILES ${LOVE_SRC_MODULE_GRAPHICS_VULKAN}) # # love.image diff --git a/src/common/config.h b/src/common/config.h index 34aa3f2ed..f7787d399 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -124,6 +124,8 @@ # define LOVE_LEGENDARY_ACCELEROMETER_AS_JOYSTICK_HACK #endif +#define LOVE_GRAPHICS_VULKAN + #if defined(LOVE_MACOS) || defined(LOVE_IOS) # define LOVE_GRAPHICS_METAL #endif diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 92e1b3408..9f8d8f46f 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -109,6 +109,7 @@ namespace opengl { extern love::graphics::Graphics *createInstance(); } #ifdef LOVE_GRAPHICS_METAL namespace metal { extern love::graphics::Graphics *createInstance(); } #endif +namespace vulkan { extern love::graphics::Graphics* createInstance(); } static const Renderer rendererOrder[] = { RENDERER_METAL, @@ -148,6 +149,9 @@ Graphics *Graphics::createInstance() { for (auto r : rendererOrder) { + // FIX ME: proper selection of vulkan backend + instance = vulkan::createInstance(); + if (std::find(_renderers.begin(), _renderers.end(), r) == _renderers.end()) continue; @@ -157,7 +161,6 @@ Graphics *Graphics::createInstance() if (r == RENDERER_METAL) instance = metal::createInstance(); #endif - if (instance != nullptr) break; } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index d5709f642..253875769 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -70,6 +70,7 @@ enum Renderer RENDERER_NONE, RENDERER_OPENGL, RENDERER_METAL, + RENDERER_VULKAN, RENDERER_MAX_ENUM }; diff --git a/src/modules/graphics/ShaderStage.cpp b/src/modules/graphics/ShaderStage.cpp index a035481f8..352604f0c 100644 --- a/src/modules/graphics/ShaderStage.cpp +++ b/src/modules/graphics/ShaderStage.cpp @@ -18,6 +18,8 @@ * 3. This notice may not be removed or altered from any source distribution. **/ +#include + #include "ShaderStage.h" #include "common/Exception.h" #include "Graphics.h" diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp new file mode 100644 index 000000000..6bf5a278b --- /dev/null +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -0,0 +1,861 @@ +#include "Graphics.h" +#include "SDL_vulkan.h" +#include "window/Window.h" +#include "common/Exception.h" +#include "Shader.h" + +#include +#include +#include +#include +#include + + +namespace love { + namespace graphics { + namespace vulkan { + const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" + }; + + const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; + +#ifdef NDEBUG + const bool enableValidationLayers = false; +#else + const bool enableValidationLayers = true; +#endif + + const int MAX_FRAMES_IN_FLIGHT = 2; + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + const char* Graphics::getName() const { + return "love.graphics.vulkan"; + } + + Graphics::Graphics() { + } + + void Graphics::initVulkan() { + if (!init) { + std::cout << "initVulkan" << std::endl; + init = true; + createVulkanInstance(); + std::cout << "create vulkan instance" << std::endl; + createSurface(); + std::cout << "create surface" << std::endl; + pickPhysicalDevice(); + std::cout << "create physical device" << std::endl; + createLogicalDevice(); + std::cout << "create logical device" << std::endl; + createSwapChain(); + std::cout << "create swap chain" << std::endl; + createImageViews(); + std::cout << "create image views" << std::endl; + createRenderPass(); + std::cout << "create render pass" << std::endl; + createGraphicsPipeline(); + std::cout << "create graphics pipeline" << std::endl; + createFramebuffers(); + std::cout << "create frame buffers" << std::endl; + createCommandPool(); + std::cout << "create command pool" << std::endl; + createCommandBuffers(); + std::cout << "create command buffers" << std::endl; + createSyncObjects(); + std::cout << "create sync objects" << std::endl; + } + } + + Graphics::~Graphics() { + if (init) { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores.at(i), nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores.at(i), nullptr); + vkDestroyFence(device, inFlightFences.at(i), nullptr); + } + if (vkDeviceWaitIdle(device) != VK_SUCCESS) { + throw love::Exception("vkDeviceWaitIdle failed"); + } + vkDestroyCommandPool(device, commandPool, nullptr); + for (auto framebuffer : swapChainFramBuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + } + } + + void Graphics::present(void* screenshotCallbackdata) { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { + vkWaitForFences(device, 1, &imagesInFlight.at(imageIndex), VK_TRUE, UINT64_MAX); + } + imagesInFlight[imageIndex] = inFlightFences[currentFrame]; + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = { imageAvailableSemaphores.at(currentFrame) }; + VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + + VkSemaphore signalSemaphores[] = { renderFinishedSemaphores.at(currentFrame) }; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences.at(currentFrame)) != VK_SUCCESS) { + throw love::Exception("failed to submit draw command buffer"); + } + + 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) % MAX_FRAMES_IN_FLIGHT; + } + + void Graphics::createVulkanInstance() { + if (enableValidationLayers && !checkValidationSupport()) { + throw love::Exception("validation layers requested, but not available"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "LOVE"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); //todo, get this version from somewhere else? + appInfo.pEngineName = "LOVE Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); //todo, same as above + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + createInfo.pNext = nullptr; + + auto window = Module::getInstance(M_WINDOW); + const void* handle = window->getHandle(); + + unsigned int count; + if (SDL_Vulkan_GetInstanceExtensions((SDL_Window*)handle, &count, nullptr) != SDL_TRUE) { + throw love::Exception("couldn't retrieve sdl vulkan extensions"); + } + + std::vector extensions = {}; // can add more here + size_t addition_extension_count = extensions.size(); + extensions.resize(addition_extension_count + count); + + if (SDL_Vulkan_GetInstanceExtensions((SDL_Window*)handle, &count, extensions.data() + addition_extension_count) != SDL_TRUE) { + throw love::Exception("couldn't retrieve sdl vulkan extensions"); + } + + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + createInfo.ppEnabledLayerNames = nullptr; + } + + if (vkCreateInstance( + &createInfo, + nullptr, + &instance) != VK_SUCCESS) { + throw love::Exception("couldn't create vulkan instance"); + } + } + + bool Graphics::checkValidationSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + void Graphics::pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw love::Exception("failed to find GPUs with Vulkan support"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + std::multimap candidates; + + for (const auto& device : devices) { + int score = rateDeviceSuitability(device); + candidates.insert(std::make_pair(score, device)); + } + + if (candidates.rbegin()->first > 0) { + physicalDevice = candidates.rbegin()->second; + } + else { + throw love::Exception("failed to find a suitable gpu"); + } + } + + bool Graphics::checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + int Graphics::rateDeviceSuitability(VkPhysicalDevice device) { + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + int score = 1; + + // optional + + if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + score += 1000; + } + + // definitely needed + + QueueFamilyIndices indices = findQueueFamilies(device); + if (!indices.isComplete()) { + score = 0; + } + + bool extensionsSupported = checkDeviceExtensionSupport(device); + if (!extensionsSupported) { + score = 0; + } + + if (extensionsSupported) { + auto swapChainSupport = querySwapChainSupport(device); + bool swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + if (!swapChainAdequate) { + score = 0; + } + } + + return score; + } + + Graphics::QueueFamilyIndices Graphics::findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + void Graphics::createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + // can this be removed? + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw love::Exception("failed to create logical device"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void Graphics::createSurface() { + auto window = Module::getInstance(M_WINDOW); + const void* handle = window->getHandle(); + if (SDL_Vulkan_CreateSurface((SDL_Window*)handle, instance, &surface) != SDL_TRUE) { + throw love::Exception("failed to create window surface"); + } + } + + Graphics::SwapChainSupportDetails Graphics::querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + void Graphics::createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; + createInfo.pQueueFamilyIndices = nullptr; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw love::Exception("failed to create swap chain"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkSurfaceFormatKHR Graphics::chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR Graphics::chooseSwapPresentMode(const std::vector& availablePresentModes) { + // needed ? + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D Graphics::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != UINT32_MAX) { + return capabilities.currentExtent; + } + else { + auto window = Module::getInstance(M_WINDOW); + const void* handle = window->getHandle(); + + int width, height; + // is this the equivalent of glfwGetFramebufferSize ? + SDL_Vulkan_GetDrawableSize((SDL_Window*)handle, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + void Graphics::createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages.at(i); + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews.at(i)) != VK_SUCCESS) { + throw love::Exception("failed to create image views"); + } + } + } + + void Graphics::createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + 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; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + 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; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw love::Exception("failed to create render pass"); + } + } + + static VkShaderModule createShaderModule(VkDevice device, const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw love::Exception("failed to create shader module"); + } + + return shaderModule; + } + + void Graphics::createGraphicsPipeline() { + // love::graphics::vulkan::Shader* shader = dynamic_cast(getShader()); + // auto shaderStages = shader->getShaderStages(); + + auto vertShaderCode = readFile("vert.spv"); + auto fragShaderCode = readFile("frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(device, vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(device, fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo }; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + // todo later + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = nullptr; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = nullptr; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; // Optional + multisampling.pSampleMask = nullptr; // Optional + multisampling.alphaToCoverageEnable = VK_FALSE; // Optional + multisampling.alphaToOneEnable = VK_FALSE; // Optional + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw love::Exception("failed to create pipeline layout"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + // pipelineInfo.stageCount = static_cast(shaderStages.size()); + // pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = nullptr; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = nullptr; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.basePipelineIndex = -1; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw love::Exception("failed to create graphics pipeline"); + } + + vkDestroyShaderModule(device, vertShaderModule, nullptr); + vkDestroyShaderModule(device, fragShaderModule, nullptr); + } + + void Graphics::createFramebuffers() { + swapChainFramBuffers.resize(swapChainImageViews.size()); + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews.at(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; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramBuffers.at(i)) != VK_SUCCESS) { + throw love::Exception("failed to create framebuffers"); + } + } + } + + void Graphics::createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + poolInfo.flags = 0; + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw love::Exception("failed to create command pool"); + } + } + + void Graphics::createCommandBuffers() { + commandBuffers.resize(swapChainFramBuffers.size()); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw love::Exception("failed to allocate command buffers"); + } + + for (size_t i = 0; i < commandBuffers.size(); i++) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; + beginInfo.pInheritanceInfo = nullptr; + + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw love::Exception("failed to begin recording command buffer"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramBuffers.at(i); + renderPassInfo.renderArea.offset = { 0, 0 }; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + // this definitely doesn't belong in here, but leaving here for future reference + vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffers[i]); + if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { + throw love::Exception("failed to record command buffer"); + } + } + } + + void Graphics::createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores.at(i)) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores.at(i)) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences.at(i)) != VK_SUCCESS) { + throw love::Exception("failed to create synchronization objects for a frame!"); + } + } + } + + love::graphics::Graphics* createInstance() { + love::graphics::Graphics* instance = nullptr; + + try { + instance = new Graphics(); + } + catch (love::Exception& e) { + printf("Cannot create Vulkan renderer: %s\n", e.what()); + } + + return instance; + } + } + } +} diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h new file mode 100644 index 000000000..efa1651ce --- /dev/null +++ b/src/modules/graphics/vulkan/Graphics.h @@ -0,0 +1,140 @@ +#ifndef LOVE_GRAPHICS_VULKAN_GRAPHICS_H +#define LOVE_GRAPHICS_VULKAN_GRAPHICS_H + +#include "graphics/Graphics.h" +#include + +#include + +#include +#include + + +namespace love { + namespace graphics { + namespace vulkan { + class Graphics final : public love::graphics::Graphics { + public: + Graphics(); + + void initVulkan(); + + virtual ~Graphics(); + + const char* getName() const override; + + const VkDevice getDevice() const { + return device; + } + + // implementation for virtual functions + Texture* newTexture(const Texture::Settings& settings, const Texture::Slices* data = nullptr) override { return nullptr; } + Buffer* newBuffer(const Buffer::Settings& settings, const std::vector& format, const void* data, size_t size, size_t arraylength) override { return nullptr; } + void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override {} + void clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) override {} + Matrix4 computeDeviceProjection(const Matrix4& projection, bool rendertotexture) const override { return Matrix4(); } + void discard(const std::vector& colorbuffers, bool depthstencil) override { } + void present(void* screenshotCallbackdata) override; + void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override {} + bool setMode(void* context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) override { return false; } + void unSetMode() override {} + void setActive(bool active) override {} + int getRequestedBackbufferMSAA() const override { return 0; } + int getBackbufferMSAA() const override { return 0; } + void setColor(Colorf c) override {} + void setScissor(const Rect& rect) override {} + void setScissor() override {} + void setStencilMode(StencilAction action, CompareMode compare, int value, love::uint32 readmask, love::uint32 writemask) override {} + void setDepthMode(CompareMode compare, bool write) override {} + void setFrontFaceWinding(Winding winding) override {} + void setColorMask(ColorChannelMask mask) override {} + void setBlendState(const BlendState& blend) override {} + void setPointSize(float size) override {} + void setWireframe(bool enable) override {} + PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override { return PIXELFORMAT_UNKNOWN; } + bool isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB = false) override { return false; } + Renderer getRenderer() const override { return RENDERER_VULKAN; } + bool usesGLSLES() const override { return false; } + RendererInfo getRendererInfo() const override { return {}; } + void draw(const DrawCommand& cmd) override {} + void draw(const DrawIndexedCommand& cmd) override {} + void drawQuads(int start, int count, const VertexAttributes& attributes, const BufferBindings& buffers, Texture* texture) override {} + + protected: + ShaderStage* newShaderStageInternal(ShaderStageType stage, const std::string& cachekey, const std::string& source, bool gles) override { return nullptr; } + Shader* newShaderInternal(StrongRef stages[SHADERSTAGE_MAX_ENUM]) override { return nullptr; } + StreamBuffer* newStreamBuffer(BufferUsage type, size_t size) override { return nullptr; } + bool dispatch(int x, int y, int z) override { return false; } + void initCapabilities() override {} + void getAPIStats(int& shaderswitches) const override {} + void setRenderTargetsInternal(const RenderTargets& rts, int pixelw, int pixelh, bool hasSRGBtexture) override {} + + private: + bool init = false; + // vulkan specific member functions and variables + + struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } + }; + + struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + void createVulkanInstance(); + bool checkValidationSupport(); + void pickPhysicalDevice(); + int rateDeviceSuitability(VkPhysicalDevice device); + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device); + void createLogicalDevice(); + void createSurface(); + bool checkDeviceExtensionSupport(VkPhysicalDevice device); + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device); + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats); + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes); + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities); + void createSwapChain(); + void createImageViews(); + void createRenderPass(); + void createGraphicsPipeline(); + void createFramebuffers(); + void createCommandPool(); + void createCommandBuffers(); + void createSyncObjects(); + + VkInstance instance; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VkQueue graphicsQueue; + VkQueue presentQueue; + VkSurfaceKHR surface; + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + VkPipelineLayout pipelineLayout; + VkRenderPass renderPass; + VkPipeline graphicsPipeline; + std::vector swapChainFramBuffers; + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + std::vector imagesInFlight; + size_t currentFrame = 0; + }; + } + } +} + +#endif diff --git a/src/modules/graphics/vulkan/Shader.cpp b/src/modules/graphics/vulkan/Shader.cpp new file mode 100644 index 000000000..3e26be7e4 --- /dev/null +++ b/src/modules/graphics/vulkan/Shader.cpp @@ -0,0 +1,44 @@ +#include "Shader.h" + +#include "libraries/glslang/glslang/Public/ShaderLang.h" +#include "libraries/glslang/SPIRV/GlslangToSpv.h" +#include + +namespace love { + namespace graphics { + namespace vulkan { + static VkShaderStageFlagBits getStageBit(ShaderStageType type) { + switch (type) { + case SHADERSTAGE_VERTEX: + return VK_SHADER_STAGE_VERTEX_BIT; + case SHADERSTAGE_PIXEL: + return VK_SHADER_STAGE_FRAGMENT_BIT; + case SHADERSTAGE_COMPUTE: + return VK_SHADER_STAGE_COMPUTE_BIT; + } + throw love::Exception("invalid type"); + } + + Shader::Shader(StrongRef stages[]) + : graphics::Shader(stages) { + + if (false) { + for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) { + if (!stages[i]) + continue; + + auto stage = dynamic_cast(stages[i].get()); + + VkPipelineShaderStageCreateInfo shaderStageInfo{}; + shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStageInfo.stage = getStageBit(stage->getStageType()); + shaderStageInfo.module = stage->getShaderModule(); + shaderStageInfo.pName = "main"; + + shaderStages.push_back(shaderStageInfo); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/modules/graphics/vulkan/Shader.h b/src/modules/graphics/vulkan/Shader.h new file mode 100644 index 000000000..1cdedd605 --- /dev/null +++ b/src/modules/graphics/vulkan/Shader.h @@ -0,0 +1,30 @@ +#ifndef LOVE_GRAPHICS_VULKAN_SHADER_H +#define LOVE_GRAPHICS_VULKAN_SHADER_H + +#include +#include +#include "libraries/glslang/glslang/Public/ShaderLang.h" +#include "libraries/glslang/SPIRV/GlslangToSpv.h" +#include + + +namespace love { + namespace graphics { + namespace vulkan { + class Shader final : public graphics::Shader { + public: + Shader(StrongRef stages[]); + virtual ~Shader() = default; + + const std::vector& getShaderStages() const { + return shaderStages; + } + + private: + std::vector shaderStages; + }; + } + } +} + +#endif diff --git a/src/modules/graphics/vulkan/ShaderStage.cpp b/src/modules/graphics/vulkan/ShaderStage.cpp new file mode 100644 index 000000000..3152ecf5c --- /dev/null +++ b/src/modules/graphics/vulkan/ShaderStage.cpp @@ -0,0 +1,195 @@ +#include "ShaderStage.h" + +#include "Graphics.h" + +#include +#include + + +namespace love { + namespace graphics { + namespace vulkan { + // TODO: Use love.graphics to determine actual limits? + static const TBuiltInResource defaultTBuiltInResource = { + /* .MaxLights = */ 32, + /* .MaxClipPlanes = */ 6, + /* .MaxTextureUnits = */ 32, + /* .MaxTextureCoords = */ 32, + /* .MaxVertexAttribs = */ 64, + /* .MaxVertexUniformComponents = */ 16384, + /* .MaxVaryingFloats = */ 128, + /* .MaxVertexTextureImageUnits = */ 32, + /* .MaxCombinedTextureImageUnits = */ 80, + /* .MaxTextureImageUnits = */ 32, + /* .MaxFragmentUniformComponents = */ 16384, + /* .MaxDrawBuffers = */ 8, + /* .MaxVertexUniformVectors = */ 4096, + /* .MaxVaryingVectors = */ 32, + /* .MaxFragmentUniformVectors = */ 4096, + /* .MaxVertexOutputVectors = */ 32, + /* .MaxFragmentInputVectors = */ 31, + /* .MinProgramTexelOffset = */ -8, + /* .MaxProgramTexelOffset = */ 7, + /* .MaxClipDistances = */ 8, + /* .MaxComputeWorkGroupCountX = */ 65535, + /* .MaxComputeWorkGroupCountY = */ 65535, + /* .MaxComputeWorkGroupCountZ = */ 65535, + /* .MaxComputeWorkGroupSizeX = */ 1024, + /* .MaxComputeWorkGroupSizeY = */ 1024, + /* .MaxComputeWorkGroupSizeZ = */ 64, + /* .MaxComputeUniformComponents = */ 1024, + /* .MaxComputeTextureImageUnits = */ 32, + /* .MaxComputeImageUniforms = */ 16, + /* .MaxComputeAtomicCounters = */ 4096, + /* .MaxComputeAtomicCounterBuffers = */ 8, + /* .MaxVaryingComponents = */ 128, + /* .MaxVertexOutputComponents = */ 128, + /* .MaxGeometryInputComponents = */ 128, + /* .MaxGeometryOutputComponents = */ 128, + /* .MaxFragmentInputComponents = */ 128, + /* .MaxImageUnits = */ 192, + /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 144, + /* .MaxCombinedShaderOutputResources = */ 144, + /* .MaxImageSamples = */ 32, + /* .MaxVertexImageUniforms = */ 16, + /* .MaxTessControlImageUniforms = */ 16, + /* .MaxTessEvaluationImageUniforms = */ 16, + /* .MaxGeometryImageUniforms = */ 16, + /* .MaxFragmentImageUniforms = */ 16, + /* .MaxCombinedImageUniforms = */ 80, + /* .MaxGeometryTextureImageUnits = */ 16, + /* .MaxGeometryOutputVertices = */ 256, + /* .MaxGeometryTotalOutputComponents = */ 1024, + /* .MaxGeometryUniformComponents = */ 1024, + /* .MaxGeometryVaryingComponents = */ 64, + /* .MaxTessControlInputComponents = */ 128, + /* .MaxTessControlOutputComponents = */ 128, + /* .MaxTessControlTextureImageUnits = */ 16, + /* .MaxTessControlUniformComponents = */ 1024, + /* .MaxTessControlTotalOutputComponents = */ 4096, + /* .MaxTessEvaluationInputComponents = */ 128, + /* .MaxTessEvaluationOutputComponents = */ 128, + /* .MaxTessEvaluationTextureImageUnits = */ 16, + /* .MaxTessEvaluationUniformComponents = */ 1024, + /* .MaxTessPatchComponents = */ 120, + /* .MaxPatchVertices = */ 32, + /* .MaxTessGenLevel = */ 64, + /* .MaxViewports = */ 16, + /* .MaxVertexAtomicCounters = */ 4096, + /* .MaxTessControlAtomicCounters = */ 4096, + /* .MaxTessEvaluationAtomicCounters = */ 4096, + /* .MaxGeometryAtomicCounters = */ 4096, + /* .MaxFragmentAtomicCounters = */ 4096, + /* .MaxCombinedAtomicCounters = */ 4096, + /* .MaxAtomicCounterBindings = */ 8, + /* .MaxVertexAtomicCounterBuffers = */ 8, + /* .MaxTessControlAtomicCounterBuffers = */ 8, + /* .MaxTessEvaluationAtomicCounterBuffers = */ 8, + /* .MaxGeometryAtomicCounterBuffers = */ 8, + /* .MaxFragmentAtomicCounterBuffers = */ 8, + /* .MaxCombinedAtomicCounterBuffers = */ 8, + /* .MaxAtomicCounterBufferSize = */ 16384, + /* .MaxTransformFeedbackBuffers = */ 4, + /* .MaxTransformFeedbackInterleavedComponents = */ 64, + /* .MaxCullDistances = */ 8, + /* .MaxCombinedClipAndCullDistances = */ 8, + /* .MaxSamples = */ 32, + /* .maxMeshOutputVerticesNV = */ 256, + /* .maxMeshOutputPrimitivesNV = */ 512, + /* .maxMeshWorkGroupSizeX_NV = */ 32, + /* .maxMeshWorkGroupSizeY_NV = */ 1, + /* .maxMeshWorkGroupSizeZ_NV = */ 1, + /* .maxTaskWorkGroupSizeX_NV = */ 32, + /* .maxTaskWorkGroupSizeY_NV = */ 1, + /* .maxTaskWorkGroupSizeZ_NV = */ 1, + /* .maxMeshViewCountNV = */ 4, + /* .maxDualSourceDrawBuffersEXT = */ 1, + /* .limits = */{ + /* .nonInductiveForLoops = */ 1, + /* .whileLoops = */ 1, + /* .doWhileLoops = */ 1, + /* .generalUniformIndexing = */ 1, + /* .generalAttributeMatrixVectorIndexing = */ 1, + /* .generalVaryingIndexing = */ 1, + /* .generalSamplerIndexing = */ 1, + /* .generalVariableIndexing = */ 1, + /* .generalConstantMatrixVectorIndexing = */ 1, + } + }; + + static EShLanguage getShaderStage(ShaderStageType stage) { + switch (stage) { + case SHADERSTAGE_VERTEX: return EShLangVertex; + case SHADERSTAGE_PIXEL: return EShLangFragment; + case SHADERSTAGE_COMPUTE: return EShLangCompute; + case SHADERSTAGE_MAX_ENUM: return EShLangCount; + } + return EShLangCount; + } + + ShaderStage::ShaderStage(love::graphics::Graphics* gfx, ShaderStageType stage, const std::string& glsl, bool gles, const std::string& cachekey) + : love::graphics::ShaderStage(gfx, stage, glsl, gles, cachekey) { + if (false) { + using namespace glslang; + + auto shaderStage = getShaderStage(stage); + + TShader* shader = new TShader(shaderStage); + shader->setEnvInput(EShSourceGlsl, shaderStage, EShClientVulkan, 450); + shader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2); + shader->setEnvTarget(EShTargetSpv, EShTargetSpv_1_5); + shader->setAutoMapLocations(true); + shader->setAutoMapBindings(true); + shader->setEnvInputVulkanRulesRelaxed(); + shader->setGlobalUniformBinding(0); + shader->setGlobalUniformSet(0); + + const std::string& source = glsl; + const char* csrc = source.c_str(); + int srclen = (int)source.length(); + shader->setStringsWithLengths(&csrc, &srclen, 1); + + int defaultversion = 450; + EProfile defaultprofile = ECoreProfile; + bool forcedefault = false; + bool forwardcompat = true; + + if (!shader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings)) { + const char* stagename = "unknown"; + ShaderStage::getConstant(stage, stagename); + + std::string err = "Error parsing " + std::string(stagename) + " shader:\n\n" + + std::string(shader->getInfoLog()) + "\n" + + std::string(shader->getInfoDebugLog()); + + delete shader; + + throw love::Exception("%s", err.c_str()); + } + + auto intermediate = shader->getIntermediate(); + std::vector code; + GlslangToSpv(*intermediate, code); + + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + Graphics* vkGfx = (Graphics*)gfx; + device = vkGfx->getDevice(); + + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw love::Exception("failed to create shader module"); + } + } + + } + + ShaderStage::~ShaderStage() { + if (false) + vkDestroyShaderModule(device, shaderModule, nullptr); + } + } + } +} diff --git a/src/modules/graphics/vulkan/ShaderStage.h b/src/modules/graphics/vulkan/ShaderStage.h new file mode 100644 index 000000000..49ced1f4f --- /dev/null +++ b/src/modules/graphics/vulkan/ShaderStage.h @@ -0,0 +1,29 @@ +#ifndef LOVE_GRAPHICS_VULKAN_SHADERSTAGE_H +#define LOVE_GRAPHICS_VULKAN_SHADERSTAGE_H + +#include "graphics/ShaderStage.h" +#include "modules/graphics/Graphics.h" +#include + +namespace love { + namespace graphics { + namespace vulkan { + class ShaderStage final : public graphics::ShaderStage { + public: + ShaderStage(love::graphics::Graphics* gfx, ShaderStageType stage, const std::string& glsl, bool gles, const std::string& cachekey); + virtual ~ShaderStage(); + + VkShaderModule getShaderModule() const { + return shaderModule; + } + + private: + VkShaderModule shaderModule; + VkDevice device; + + }; + } + } +} + +#endif diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index d05062ae6..0b0b2ed37 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -21,6 +21,7 @@ // LOVE #include "common/config.h" #include "graphics/Graphics.h" +#include "graphics/vulkan/Graphics.h" #include "Window.h" #ifdef LOVE_ANDROID @@ -137,6 +138,7 @@ void Window::setGLFramebufferAttributes(bool sRGB) void Window::setGLContextAttributes(const ContextAttribs &attribs) { +#ifndef LOVE_GRAPHICS_VULKAN int profilemask = 0; int contextflags = 0; @@ -154,10 +156,12 @@ void Window::setGLContextAttributes(const ContextAttribs &attribs) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, attribs.versionMinor); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextflags); +#endif } bool Window::checkGLVersion(const ContextAttribs &attribs, std::string &outversion) { +#ifndef LOVE_GRAPHICS_VULKAN typedef unsigned char GLubyte; typedef unsigned int GLenum; typedef const GLubyte *(APIENTRY *glGetStringPtr)(GLenum name); @@ -202,6 +206,9 @@ bool Window::checkGLVersion(const ContextAttribs &attribs, std::string &outversi return false; return true; +#else + return true; +#endif } std::vector Window::getContextAttribsList() const @@ -314,11 +321,13 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla const auto create = [&](const ContextAttribs *attribs) -> bool { +#ifndef LOVE_GRAPHICS_VULKAN if (glcontext) { SDL_GL_DeleteContext(glcontext); glcontext = nullptr; } +#endif #ifdef LOVE_GRAPHICS_METAL if (metalView) @@ -335,6 +344,7 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla window = nullptr; } +#ifndef LOVE_GRAPHICS_VULKAN window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags); if (!window) @@ -366,6 +376,16 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla } return true; + +#else + window = SDL_CreateWindow(title.c_str(), x, y, w, h, SDL_WINDOW_VULKAN); + + love::graphics::Graphics* gfx = graphics.get(); + love::graphics::vulkan::Graphics* vgfx = (love::graphics::vulkan::Graphics*)gfx; + vgfx->initVulkan(); + + return true; +#endif }; if (renderer == graphics::RENDERER_OPENGL) @@ -577,19 +597,21 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) { if (renderer == graphics::RENDERER_OPENGL) sdlflags |= SDL_WINDOW_OPENGL; - #ifdef LOVE_GRAPHICS_METAL if (renderer == graphics::RENDERER_METAL) sdlflags |= SDL_WINDOW_METAL; #endif - if (f.resizable) + if (renderer == graphics::RENDERER_VULKAN) + sdlflags |= SDL_WINDOW_VULKAN; + + if (f.resizable) sdlflags |= SDL_WINDOW_RESIZABLE; - if (f.borderless) + if (f.borderless) sdlflags |= SDL_WINDOW_BORDERLESS; - if (isHighDPIAllowed()) + if (isHighDPIAllowed()) sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI; if (!createWindowAndContext(x, y, width, height, sdlflags, renderer))