diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 8a4f7500f..b51db2154 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -65,29 +65,100 @@ const char *Graphics::getName() const return "love.graphics.vulkan"; } -const VkDevice Graphics::getDevice() const +VkDevice Graphics::getDevice() const { return device; } -const VmaAllocator Graphics::getVmaAllocator() const +VmaAllocator Graphics::getVmaAllocator() const { return vmaAllocator; } +static void checkOptionalInstanceExtensions(OptionalInstanceExtensions& ext) +{ + uint32_t count; + + vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + + std::vector extensions(count); + + vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data()); + + for (const auto& extension : extensions) + { + if (strcmp(extension.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) + ext.physicalDeviceProperties2 = true; + } +} + Graphics::Graphics() { if (SDL_Vulkan_LoadLibrary(nullptr)) throw love::Exception("could not find vulkan"); volkInitializeCustom((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()); + + if (isDebugEnabled() && !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_API_VERSION(0, 1, 0, 0); // get this version from somewhere else? + appInfo.pEngineName = "LOVE Game Framework"; + appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + createInfo.pNext = nullptr; + + // GetInstanceExtensions works with a null window parameter as long as + // SDL_Vulkan_LoadLibrary has been called (which we do earlier). + unsigned int count; + if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, nullptr) != SDL_TRUE) + throw love::Exception("couldn't retrieve sdl vulkan extensions"); + + std::vector extensions = {}; + + checkOptionalInstanceExtensions(optionalInstanceExtensions); + + if (optionalInstanceExtensions.physicalDeviceProperties2) + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + size_t additional_extension_count = extensions.size(); + extensions.resize(additional_extension_count + count); + + if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, extensions.data() + additional_extension_count) != SDL_TRUE) + throw love::Exception("couldn't retrieve sdl vulkan extensions"); + + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + if (isDebugEnabled()) + { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) + throw love::Exception("couldn't create vulkan instance"); + + volkLoadInstance(instance); } Graphics::~Graphics() { + defaultConstantTexCoord.set(nullptr); defaultConstantColor.set(nullptr); defaultTexture.set(nullptr); + Volatile::unloadAll(); + cleanup(); + vkDestroyInstance(instance, nullptr); + SDL_Vulkan_UnloadLibrary(); } @@ -302,14 +373,14 @@ void Graphics::discard(const std::vector &colorbuffers, bool depthstencil) startRenderPass(); } -void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData) +void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallbackData) { flushBatchedDraws(); if (renderPassState.active) endRenderPass(); - if (present) + if (submitMode == SUBMIT_PRESENT) { if (pendingScreenshotCallbacks.empty()) Vulkan::cmdTransitionImageLayout( @@ -435,7 +506,7 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData) VkFence fence = VK_NULL_HANDLE; - if (present) + if (submitMode == SUBMIT_PRESENT) { submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; @@ -447,7 +518,7 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData) if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS) throw love::Exception("failed to submit draw command buffer"); - if (!present) + if (submitMode == SUBMIT_NOPRESENT || submitMode == SUBMIT_RESTART) { vkQueueWaitIdle(graphicsQueue); @@ -458,7 +529,8 @@ void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData) callbacks.clear(); } - startRecordingGraphicsCommands(); + if (submitMode == SUBMIT_RESTART) + startRecordingGraphicsCommands(); } } @@ -475,7 +547,7 @@ void Graphics::present(void *screenshotCallbackdata) deprecations.draw(this); - submitGpuCommands(true, screenshotCallbackdata); + submitGpuCommands(SUBMIT_PRESENT, screenshotCallbackdata); VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; @@ -540,64 +612,80 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int readbackCallbacks.clear(); readbackCallbacks.resize(MAX_FRAMES_IN_FLIGHT); - createVulkanInstance(); + bool createBaseObjects = physicalDevice == VK_NULL_HANDLE; + createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createPipelineCache(); - initVMA(); - initCapabilities(); + + if (createBaseObjects) + { + pickPhysicalDevice(); + createLogicalDevice(); + createPipelineCache(); + initVMA(); + initCapabilities(); + } + + msaaSamples = getMsaaCount(requestedMsaa); + createSwapChain(); createImageViews(); createScreenshotCallbackBuffers(); - createSyncObjects(); createColorResources(); createDepthResources(); transitionColorDepthLayouts = true; - createCommandPool(); - createCommandBuffers(); + + if (createBaseObjects) + { + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } beginFrame(); - if (batchedDrawState.vb[0] == nullptr) + if (createBaseObjects) { - // Initial sizes that should be good enough for most cases. It will - // resize to fit if needed, later. - batchedDrawState.vb[0] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1); - batchedDrawState.vb[1] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1); - batchedDrawState.indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX); - } + if (batchedDrawState.vb[0] == nullptr) + { + // Initial sizes that should be good enough for most cases. It will + // resize to fit if needed, later. + batchedDrawState.vb[0] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1); + batchedDrawState.vb[1] = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1); + batchedDrawState.indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX); + } - // sometimes the VertexTexCoord is not set, so we manually adjust it to (0, 0) - if (defaultConstantTexCoord == nullptr) - { - float zeroTexCoord[2] = { 0.0f, 0.0f }; - Buffer::DataDeclaration format("ConstantTexCoord", DATAFORMAT_FLOAT_VEC2); - Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC); - defaultConstantTexCoord = newBuffer(settings, { format }, zeroTexCoord, sizeof(zeroTexCoord), 1); - } + // sometimes the VertexTexCoord is not set, so we manually adjust it to (0, 0) + if (defaultConstantTexCoord == nullptr) + { + float zeroTexCoord[2] = { 0.0f, 0.0f }; + Buffer::DataDeclaration format("ConstantTexCoord", DATAFORMAT_FLOAT_VEC2); + Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC); + defaultConstantTexCoord = newBuffer(settings, { format }, zeroTexCoord, sizeof(zeroTexCoord), 1); + } - // sometimes the VertexColor is not set, so we manually adjust it to white color - if (defaultConstantColor == nullptr) - { - uint8 whiteColor[] = { 255, 255, 255, 255 }; - Buffer::DataDeclaration format("ConstantColor", DATAFORMAT_UNORM8_VEC4); - Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC); - defaultConstantColor = newBuffer(settings, { format }, whiteColor, sizeof(whiteColor), 1); - } + // sometimes the VertexColor is not set, so we manually adjust it to white color + if (defaultConstantColor == nullptr) + { + uint8 whiteColor[] = { 255, 255, 255, 255 }; + Buffer::DataDeclaration format("ConstantColor", DATAFORMAT_UNORM8_VEC4); + Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, BUFFERDATAUSAGE_STATIC); + defaultConstantColor = newBuffer(settings, { format }, whiteColor, sizeof(whiteColor), 1); + } - createDefaultTexture(); - createDefaultShaders(); - Shader::current = Shader::standardShaders[Shader::StandardShader::STANDARD_DEFAULT]; - createQuadIndexBuffer(); - createFanIndexBuffer(); + createDefaultTexture(); + createDefaultShaders(); + Shader::current = Shader::standardShaders[Shader::StandardShader::STANDARD_DEFAULT]; + createQuadIndexBuffer(); + createFanIndexBuffer(); + + frameCounter = 0; + currentFrame = 0; + } restoreState(states.back()); Vulkan::resetShaderSwitches(); - frameCounter = 0; - currentFrame = 0; created = true; drawCalls = 0; drawCallsBatched = 0; @@ -659,14 +747,12 @@ void Graphics::getAPIStats(int &shaderswitches) const void Graphics::unSetMode() { - renderPassUsages.clear(); - framebufferUsages.clear(); - pipelineUsages.clear(); - + submitGpuCommands(SUBMIT_NOPRESENT); + created = false; - vkDeviceWaitIdle(device); - Volatile::unloadAll(); - cleanup(); + + cleanupSwapChain(); + vkDestroySurfaceKHR(instance, surface, nullptr); } void Graphics::setActive(bool enable) @@ -1332,75 +1418,6 @@ const OptionalDeviceExtensions &Graphics::getEnabledOptionalDeviceExtensions() c return optionalDeviceExtensions; } -static void checkOptionalInstanceExtensions(OptionalInstanceExtensions &ext) -{ - uint32_t count; - - vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); - - std::vector extensions(count); - - vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data()); - - for (const auto &extension : extensions) - { - if (strcmp(extension.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) - ext.physicalDeviceProperties2 = true; - } -} - -void Graphics::createVulkanInstance() -{ - if (isDebugEnabled() && !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_API_VERSION(0, 1, 0, 0); // get this version from somewhere else? - appInfo.pEngineName = "LOVE Game Framework"; - appInfo.engineVersion = VK_MAKE_API_VERSION(0, VERSION_MAJOR, VERSION_MINOR, VERSION_REV); - appInfo.apiVersion = VK_API_VERSION_1_3; - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - createInfo.pNext = nullptr; - - // GetInstanceExtensions works with a null window parameter as long as - // SDL_Vulkan_LoadLibrary has been called (which we do earlier). - unsigned int count; - if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, nullptr) != SDL_TRUE) - throw love::Exception("couldn't retrieve sdl vulkan extensions"); - - std::vector extensions = {}; - - checkOptionalInstanceExtensions(optionalInstanceExtensions); - - if (optionalInstanceExtensions.physicalDeviceProperties2) - extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - - size_t additional_extension_count = extensions.size(); - extensions.resize(additional_extension_count + count); - - if (SDL_Vulkan_GetInstanceExtensions(nullptr, &count, extensions.data() + additional_extension_count) != SDL_TRUE) - throw love::Exception("couldn't retrieve sdl vulkan extensions"); - - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - if (isDebugEnabled()) - { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } - - if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) - throw love::Exception("couldn't create vulkan instance"); - - volkLoadInstance(instance); -} - bool Graphics::checkValidationSupport() { uint32_t layerCount; @@ -1458,7 +1475,6 @@ void Graphics::pickPhysicalDevice() minUniformBufferOffsetAlignment = properties.limits.minUniformBufferOffsetAlignment; deviceApiVersion = properties.apiVersion; - msaaSamples = getMsaaCount(requestedMsaa); depthStencilFormat = findDepthFormat(); } @@ -1573,7 +1589,7 @@ static void findOptionalDeviceExtensions(VkPhysicalDevice physicalDevice, Option optionalDeviceExtensions.memoryRequirements2 = true; if (strcmp(extension.extensionName, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME) == 0) optionalDeviceExtensions.dedicatedAllocation = true; - if (strcmp(extension.extensionName, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) == 0) + if (strcmp(extension.extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0) optionalDeviceExtensions.memoryBudget = true; if (strcmp(extension.extensionName, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME) == 0) optionalDeviceExtensions.shaderFloatControls = true; @@ -3040,8 +3056,6 @@ void Graphics::createDefaultTexture() void Graphics::cleanup() { - cleanupSwapChain(); - for (auto &cleanUpFns : cleanUpFunctions) for (auto &cleanUpFn : cleanUpFns) cleanUpFn(); @@ -3076,8 +3090,6 @@ void Graphics::cleanup() vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyPipelineCache(device, pipelineCache, nullptr); vkDestroyDevice(device, nullptr); - vkDestroySurfaceKHR(instance, surface, nullptr); - vkDestroyInstance(instance, nullptr); } void Graphics::cleanupSwapChain() @@ -3087,8 +3099,11 @@ void Graphics::cleanupSwapChain() vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation); vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation); } - vkDestroyImageView(device, colorImageView, nullptr); - vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation); + if (colorImage) + { + vkDestroyImageView(device, colorImageView, nullptr); + vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation); + } vkDestroyImageView(device, depthImageView, nullptr); vmaDestroyImage(vmaAllocator, depthImage, depthImageAllocation); for (const auto &swapChainImageView : swapChainImageViews) diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 04161b9e8..7d90542b8 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -256,6 +256,14 @@ struct ScreenshotReadbackBuffer VmaAllocation imageAllocation; }; +enum SubmitMode +{ + SUBMIT_PRESENT, + SUBMIT_NOPRESENT, + SUBMIT_RESTART, + SUBMIT_MAXENUM, +}; + class Graphics final : public love::graphics::Graphics { public: @@ -300,12 +308,12 @@ public: // internal functions. - const VkDevice getDevice() const; - const VmaAllocator getVmaAllocator() const; + VkDevice getDevice() const; + VmaAllocator getVmaAllocator() const; VkCommandBuffer getCommandBufferForDataTransfer(); void queueCleanUp(std::function cleanUp); void addReadbackCallback(std::function callback); - void submitGpuCommands(bool present, void *screenshotCallbackData = nullptr); + void submitGpuCommands(SubmitMode, void *screenshotCallbackData = nullptr); const VkDeviceSize getMinUniformBufferOffsetAlignment() const; VkSampler getCachedSampler(const SamplerState &sampler); void setComputeShader(Shader *computeShader); @@ -326,7 +334,6 @@ protected: void setRenderTargetsInternal(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture) override; private: - void createVulkanInstance(); bool checkValidationSupport(); void pickPhysicalDevice(); int rateDeviceSuitability(VkPhysicalDevice device); diff --git a/src/modules/graphics/vulkan/GraphicsReadback.cpp b/src/modules/graphics/vulkan/GraphicsReadback.cpp index 51a8a01b8..257a3b219 100644 --- a/src/modules/graphics/vulkan/GraphicsReadback.cpp +++ b/src/modules/graphics/vulkan/GraphicsReadback.cpp @@ -44,7 +44,7 @@ GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod if (method == READBACK_IMMEDIATE) { - vgfx->submitGpuCommands(false); + vgfx->submitGpuCommands(SUBMIT_RESTART); if (stagingBuffer.get()) { status = readbackBuffer(stagingBuffer, 0, size); gfx->releaseTemporaryBuffer(stagingBuffer); @@ -79,7 +79,7 @@ GraphicsReadback::GraphicsReadback(love::graphics::Graphics *gfx, ReadbackMethod }); if (method == READBACK_IMMEDIATE) - vgfx->submitGpuCommands(false); + vgfx->submitGpuCommands(SUBMIT_RESTART); } GraphicsReadback::~GraphicsReadback() @@ -89,7 +89,7 @@ GraphicsReadback::~GraphicsReadback() void GraphicsReadback::wait() { if (status == STATUS_WAITING) - vgfx->submitGpuCommands(false); + vgfx->submitGpuCommands(SUBMIT_RESTART); } void GraphicsReadback::update()