Browse Source

hello world triangle

niki 3 years ago
parent
commit
65074dae15

+ 16 - 2
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 <SDL2/SDL.h>, 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

+ 2 - 0
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

+ 4 - 1
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;
 		}

+ 1 - 0
src/modules/graphics/Graphics.h

@@ -70,6 +70,7 @@ enum Renderer
 	RENDERER_NONE,
 	RENDERER_OPENGL,
 	RENDERER_METAL,
+	RENDERER_VULKAN,
 	RENDERER_MAX_ENUM
 };
 

+ 2 - 0
src/modules/graphics/ShaderStage.cpp

@@ -18,6 +18,8 @@
  * 3. This notice may not be removed or altered from any source distribution.
  **/
 
+#include <iostream>
+
 #include "ShaderStage.h"
 #include "common/Exception.h"
 #include "Graphics.h"

+ 861 - 0
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 <vector>
+#include <cstring>
+#include <set>
+#include <fstream>
+#include <iostream>
+
+
+namespace love {
+	namespace graphics {
+		namespace vulkan {
+			const std::vector<const char*> validationLayers = {
+				"VK_LAYER_KHRONOS_validation"
+			};
+
+			const std::vector<const char*> 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<char> 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<char> 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<love::window::Window>(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<const char*> 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<uint32_t>(extensions.size());
+				createInfo.ppEnabledExtensionNames = extensions.data();
+
+				if (enableValidationLayers) {
+					createInfo.enabledLayerCount = static_cast<uint32_t>(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<VkLayerProperties> 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<VkPhysicalDevice> devices(deviceCount);
+				vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
+
+				std::multimap<int, VkPhysicalDevice> 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<VkExtensionProperties> availableExtensions(extensionCount);
+				vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
+
+				std::set<std::string> 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<VkQueueFamilyProperties> 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<VkDeviceQueueCreateInfo> queueCreateInfos;
+				std::set<uint32_t> 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<uint32_t>(queueCreateInfos.size());
+				createInfo.pQueueCreateInfos = queueCreateInfos.data();
+				createInfo.pEnabledFeatures = &deviceFeatures;
+
+				createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
+				createInfo.ppEnabledExtensionNames = deviceExtensions.data();
+
+				// can this be removed?
+				if (enableValidationLayers) {
+					createInfo.enabledLayerCount = static_cast<uint32_t>(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<love::window::Window>(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<VkSurfaceFormatKHR>& 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<VkPresentModeKHR>& 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<love::window::Window>(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<uint32_t>(width),
+						static_cast<uint32_t>(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<char>& code) {
+				VkShaderModuleCreateInfo createInfo{};
+				createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+				createInfo.codeSize = code.size();
+				createInfo.pCode = reinterpret_cast<const uint32_t*>(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<love::graphics::vulkan::Shader*>(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<uint32_t>(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;
+			}
+		}
+	}
+}

+ 140 - 0
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 <vulkan/vulkan.h>
+
+#include <common/config.h>
+
+#include <optional>
+#include <iostream>
+
+
+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<Buffer::DataDeclaration>& 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<OptionalColorD>& colors, OptionalInt stencil, OptionalDouble depth) override {}
+				Matrix4 computeDeviceProjection(const Matrix4& projection, bool rendertotexture) const override { return Matrix4(); }
+				void discard(const std::vector<bool>& 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<love::graphics::ShaderStage> 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<uint32_t> graphicsFamily;
+					std::optional<uint32_t> presentFamily;
+
+					bool isComplete() {
+						return graphicsFamily.has_value() && presentFamily.has_value();
+					}
+				};
+
+				struct SwapChainSupportDetails {
+					VkSurfaceCapabilitiesKHR capabilities;
+					std::vector<VkSurfaceFormatKHR> formats;
+					std::vector<VkPresentModeKHR> 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<VkSurfaceFormatKHR>& availableFormats);
+				VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& 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<VkImage> swapChainImages;
+				VkFormat swapChainImageFormat;
+				VkExtent2D swapChainExtent;
+				std::vector<VkImageView> swapChainImageViews;
+				VkPipelineLayout pipelineLayout;
+				VkRenderPass renderPass;
+				VkPipeline graphicsPipeline;
+				std::vector<VkFramebuffer> swapChainFramBuffers;
+				VkCommandPool commandPool;
+				std::vector<VkCommandBuffer> commandBuffers;
+
+				std::vector<VkSemaphore> imageAvailableSemaphores;
+				std::vector<VkSemaphore> renderFinishedSemaphores;
+				std::vector<VkFence> inFlightFences;
+				std::vector<VkFence> imagesInFlight;
+				size_t currentFrame = 0;
+			};
+		}
+	}
+}
+
+#endif

+ 44 - 0
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 <vector>
+
+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<love::graphics::ShaderStage> stages[])
+				: graphics::Shader(stages) {
+
+				if (false) {
+					for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) {
+						if (!stages[i]) 
+							continue;
+					
+						auto stage = dynamic_cast<ShaderStage*>(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);
+					}
+				}
+			}
+		}
+	}
+}

+ 30 - 0
src/modules/graphics/vulkan/Shader.h

@@ -0,0 +1,30 @@
+#ifndef LOVE_GRAPHICS_VULKAN_SHADER_H
+#define LOVE_GRAPHICS_VULKAN_SHADER_H
+
+#include <graphics/Shader.h>
+#include <graphics/vulkan/ShaderStage.h>
+#include "libraries/glslang/glslang/Public/ShaderLang.h"
+#include "libraries/glslang/SPIRV/GlslangToSpv.h"
+#include <vulkan/vulkan.h>
+
+
+namespace love {
+	namespace graphics {
+		namespace vulkan {
+			class Shader final : public graphics::Shader {
+			public:
+				Shader(StrongRef<love::graphics::ShaderStage> stages[]);
+				virtual ~Shader() = default;
+
+				const std::vector<VkPipelineShaderStageCreateInfo>& getShaderStages() const {
+					return shaderStages;
+				}
+
+			private:
+				std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+			};
+		}
+	}
+}
+
+#endif

+ 195 - 0
src/modules/graphics/vulkan/ShaderStage.cpp

@@ -0,0 +1,195 @@
+#include "ShaderStage.h"
+
+#include "Graphics.h"
+
+#include <libraries/glslang/glslang/Public/ShaderLang.h>
+#include <libraries/glslang/SPIRV/GlslangToSpv.h>
+
+
+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<unsigned int> code;
+					GlslangToSpv(*intermediate, code);
+
+					VkShaderModuleCreateInfo createInfo{};
+					createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+					createInfo.codeSize = code.size();
+					createInfo.pCode = reinterpret_cast<const uint32_t*>(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);
+			}
+		}
+	}
+}

+ 29 - 0
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 <vulkan/vulkan.h>
+
+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

+ 26 - 4
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::ContextAttribs> 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))