diff --git a/CMakeLists.txt b/CMakeLists.txt index 976b8b031..be5315b04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -606,6 +606,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_VULKAN src/modules/graphics/vulkan/Buffer.cpp src/modules/graphics/vulkan/Texture.h src/modules/graphics/vulkan/Texture.cpp + src/modules/graphics/vulkan/Vulkan.h + src/modules/graphics/vulkan/Vulkan.cpp src/modules/graphics/vulkan/vk_mem_alloc.h src/modules/graphics/vulkan/UseVMA.cpp ) diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 53b755016..5b6e05cda 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -5,6 +5,7 @@ #include "common/Exception.h" #include "Shader.h" #include "graphics/Texture.h" +#include "Vulkan.h" #include #include @@ -112,7 +113,7 @@ namespace love { const auto& commandBuffer = commandBuffers.at(imageIndex); vkCmdBeginRenderPass(commandBuffers.at(imageIndex), &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + currentGraphicsPipeline = VK_NULL_HANDLE; } void Graphics::endRecordingGraphicsCommands() { @@ -215,7 +216,6 @@ namespace love { createRenderPass(); createDefaultShaders(); createDescriptorSetLayout(); - createGraphicsPipeline(); createFramebuffers(); createCommandPool(); createCommandBuffers(); @@ -313,17 +313,19 @@ namespace love { std::vector buffers; std::vector offsets; - // vertices - buffers.push_back((VkBuffer)cmd.buffers->info[0].buffer->getHandle()); - offsets.push_back((VkDeviceSize)cmd.buffers->info[0].offset); + bool useConstantColorBuffer; + GraphicsPipelineConfiguration configuration; + createVulkanVertexFormat(*cmd.attributes, useConstantColorBuffer, configuration); - // tex coords - buffers.push_back((VkBuffer)cmd.buffers->info[1].buffer->getHandle()); - offsets.push_back((VkDeviceSize)cmd.buffers->info[1].offset); + for (uint32_t i = 0; i < 2; i++) { + buffers.push_back((VkBuffer)cmd.buffers->info[i].buffer->getHandle()); + offsets.push_back((VkDeviceSize)cmd.buffers->info[i].offset); + } - //constant color - buffers.push_back((VkBuffer)batchedDrawBuffers[currentFrame].constantColorBuffer->getHandle()); - offsets.push_back((VkDeviceSize)0); + if (useConstantColorBuffer) { + buffers.push_back((VkBuffer)batchedDrawBuffers[currentFrame].constantColorBuffer->getHandle()); + offsets.push_back((VkDeviceSize)0); + } if (cmd.texture == nullptr) { setTexture(standardTexture); @@ -332,6 +334,8 @@ namespace love { setTexture(cmd.texture); } + ensureGraphicsPipelineConfiguration(configuration); + vkCmdBindDescriptorSets(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, getDescriptorSet(currentFrame), 0, nullptr); vkCmdBindVertexBuffers(commandBuffers.at(imageIndex), 0, buffers.size(), buffers.data(), offsets.data()); vkCmdBindIndexBuffer(commandBuffers.at(imageIndex), (VkBuffer)cmd.indexBuffer->getHandle(), 0, getVulkanIndexBufferType(cmd.indexType)); @@ -930,7 +934,7 @@ namespace love { uboLayoutBinding.binding = 0; uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboLayoutBinding.descriptorCount = 1; - uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutBinding samplerLayoutBinding{}; samplerLayoutBinding.binding = 1; @@ -970,6 +974,8 @@ namespace love { poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); // FIXME: When using more than 128 textures at once we will run out of memory. + // we probably want to reuse descriptors per flight image + // and use multiple pools in case of too many allocations poolInfo.maxSets = 128 * static_cast(MAX_FRAMES_IN_FLIGHT); if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { @@ -1039,53 +1045,85 @@ namespace love { return newDescriptorSets; } - void Graphics::createGraphicsPipeline() { + void Graphics::createVulkanVertexFormat( + VertexAttributes vertexAttributes, + bool& useConstantVertexColor, + GraphicsPipelineConfiguration& configuration) { + std::set usedBuffers; + std::vector bindingDescriptions; + std::vector attributeDescriptions; + + auto allBits = vertexAttributes.enableBits; + + bool usesColor = false; + + for (uint32_t i = 0; i < 32; i++) { // change to loop like in opengl implementation ? + uint32 bit = 1u << i; + if (allBits & bit) { + if (i == ATTRIB_COLOR) { + usesColor = true; + } + + auto attrib = vertexAttributes.attribs[i]; + auto bufferBinding = attrib.bufferIndex; + if (usedBuffers.find(bufferBinding) == usedBuffers.end()) { // use .contains() when c++20 is enabled + usedBuffers.insert(bufferBinding); + + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = bufferBinding; + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.stride = vertexAttributes.bufferLayouts[bufferBinding].stride; + bindingDescriptions.push_back(bindingDescription); + } + + VkVertexInputAttributeDescription attributeDescription{}; + attributeDescription.location = i; + attributeDescription.binding = bufferBinding; + attributeDescription.offset = attrib.offsetFromVertex; + attributeDescription.format = Vulkan::getVulkanVertexFormat(attrib.format); + + attributeDescriptions.push_back(attributeDescription); + } + } + + // do we need to use a constant VertexColor? + if (!usesColor) { + constexpr uint32_t constantColorBufferBinding = 2; + + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = constantColorBufferBinding; + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.stride = 0; // no stride, will always read the same color multiple times. + bindingDescriptions.push_back(bindingDescription); + + VkVertexInputAttributeDescription attributeDescription{}; + attributeDescription.binding = constantColorBufferBinding; + attributeDescription.location = ATTRIB_COLOR; + attributeDescription.offset = 0; + attributeDescription.format = VK_FORMAT_R32G32B32A32_SFLOAT; + + useConstantVertexColor = true; + } + else { + useConstantVertexColor = false; + } + + configuration.vertexInputBindingDescriptions = bindingDescriptions; + configuration.vertexInputAttributeDescriptions = attributeDescriptions; + } + + VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration configuration) { + auto shader = reinterpret_cast(love::graphics::vulkan::Shader::standardShaders[Shader::STANDARD_DEFAULT]); auto shaderStages = shader->getShaderStages(); VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - VkVertexInputBindingDescription vertexBindingDescription; - vertexBindingDescription.binding = 0; - vertexBindingDescription.stride = 2 * sizeof(float); // just position for now - vertexBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - VkVertexInputAttributeDescription positionInputAttributeDescription; - positionInputAttributeDescription.binding = 0; - positionInputAttributeDescription.location = 0; - positionInputAttributeDescription.format = VK_FORMAT_R32G32_SFLOAT; - positionInputAttributeDescription.offset = 0; - - VkVertexInputBindingDescription texCoordsBindingDescription; - texCoordsBindingDescription.binding = 1; - texCoordsBindingDescription.stride = sizeof(STf_RGBAub); - texCoordsBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - VkVertexInputAttributeDescription texCoordsInputAttributeDescription; - texCoordsInputAttributeDescription.binding = 1; - texCoordsInputAttributeDescription.location = 1; - texCoordsInputAttributeDescription.format = VK_FORMAT_R32G32_SFLOAT; - texCoordsInputAttributeDescription.offset = 0; - - VkVertexInputBindingDescription constantColorBindingDescription; - constantColorBindingDescription.binding = 2; - constantColorBindingDescription.stride = 0; - constantColorBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - VkVertexInputAttributeDescription constantColorInputAttributeDescription; - constantColorInputAttributeDescription.binding = 2; - constantColorInputAttributeDescription.location = 2; - constantColorInputAttributeDescription.format = VK_FORMAT_R32G32B32A32_SFLOAT; - constantColorInputAttributeDescription.offset = 0; - - std::vector bindingDescriptions = { vertexBindingDescription, texCoordsBindingDescription, constantColorBindingDescription }; - std::vector vertexInputAttributeDescriptions = { positionInputAttributeDescription, texCoordsInputAttributeDescription, constantColorInputAttributeDescription }; - - vertexInputInfo.vertexBindingDescriptionCount = bindingDescriptions.size(); - vertexInputInfo.pVertexBindingDescriptions = bindingDescriptions.data(); - vertexInputInfo.vertexAttributeDescriptionCount = vertexInputAttributeDescriptions.size(); - vertexInputInfo.pVertexAttributeDescriptions = vertexInputAttributeDescriptions.data(); + vertexInputInfo.vertexBindingDescriptionCount = configuration.vertexInputBindingDescriptions.size(); + vertexInputInfo.pVertexBindingDescriptions = configuration.vertexInputBindingDescriptions.data(); + vertexInputInfo.vertexAttributeDescriptionCount = configuration.vertexInputAttributeDescriptions.size(); + vertexInputInfo.pVertexAttributeDescriptions = configuration.vertexInputAttributeDescriptions.data(); VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; @@ -1176,9 +1214,32 @@ namespace love { pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; pipelineInfo.basePipelineIndex = -1; + VkPipeline graphicsPipeline; if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw love::Exception("failed to create graphics pipeline"); } + return graphicsPipeline; + } + + void Graphics::ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration configuration) { + VkPipeline pipeline = VK_NULL_HANDLE; + for (auto const& p : graphicsPipelines) { + if (p.first == configuration) { + pipeline = p.second; + break; + } + } + if (pipeline != VK_NULL_HANDLE) { + if (currentGraphicsPipeline != pipeline) { + vkCmdBindPipeline(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + currentGraphicsPipeline = pipeline; + } + } else { + VkPipeline newPipeLine = createGraphicsPipeline(configuration); + graphicsPipelines.push_back(std::make_pair(configuration, newPipeLine)); + vkCmdBindPipeline(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, newPipeLine); + currentGraphicsPipeline = newPipeLine; + } } void Graphics::createFramebuffers() { @@ -1257,6 +1318,45 @@ namespace love { standardTexture = newTexture(settings); } + bool operator==(const Graphics::GraphicsPipelineConfiguration& first, const Graphics::GraphicsPipelineConfiguration& other) { + if (first.vertexInputAttributeDescriptions.size() != other.vertexInputAttributeDescriptions.size()) { + return false; + } + if (first.vertexInputBindingDescriptions.size() != other.vertexInputBindingDescriptions.size()) { + return false; + } + for (uint32_t i = 0; i < first.vertexInputAttributeDescriptions.size(); i++) { + const VkVertexInputAttributeDescription& x = first.vertexInputAttributeDescriptions[i]; + const VkVertexInputAttributeDescription& y = other.vertexInputAttributeDescriptions[i]; + if (x.binding != y.binding) { + return false; + } + if (x.location != y.location) { + return false; + } + if (x.offset != y.offset) { + return false; + } + if (x.format != y.format) { + return false; + } + } + for (uint32_t i = 0; i < first.vertexInputBindingDescriptions.size(); i++) { + const VkVertexInputBindingDescription& x = first.vertexInputBindingDescriptions[i]; + const VkVertexInputBindingDescription& y = other.vertexInputBindingDescriptions[i]; + if (x.binding != y.binding) { + return false; + } + if (x.inputRate != y.inputRate) { + return false; + } + if (x.stride != y.stride) { + return false; + } + } + return true; + } + void Graphics::cleanup() { vkDeviceWaitIdle(device); @@ -1284,8 +1384,12 @@ namespace love { vkDestroyFramebuffer(device, swapChainFramBuffers[i], nullptr); } vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); - vkDestroyPipeline(device, graphicsPipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + for (auto const& p : graphicsPipelines) { + vkDestroyPipeline(device, p.second, nullptr); + } + graphicsPipelines.clear(); + currentGraphicsPipeline = VK_NULL_HANDLE; + // vkDestroyPipelineLayout(device, pipelineLayout, nullptr); FIXME vkDestroyRenderPass(device, renderPass, nullptr); for (size_t i = 0; i < swapChainImageViews.size(); i++) { vkDestroyImageView(device, swapChainImageViews[i], nullptr); @@ -1303,7 +1407,6 @@ namespace love { createSwapChain(); createImageViews(); createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); createUniformBuffers(); createDescriptorPool(); diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index c2c3e5ea4..cf212d704 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -80,6 +80,13 @@ namespace love { VkCommandBuffer beginSingleTimeCommands(); void endSingleTimeCommands(VkCommandBuffer); + struct GraphicsPipelineConfiguration { + std::vector vertexInputBindingDescriptions; + std::vector vertexInputAttributeDescriptions; + + friend static bool operator==(const GraphicsPipelineConfiguration& first, const GraphicsPipelineConfiguration& other); + }; + protected: graphics::ShaderStage* newShaderStageInternal(ShaderStageType stage, const std::string& cachekey, const std::string& source, bool gles) override { std::cout << "newShaderStageInternal "; @@ -125,6 +132,12 @@ namespace love { std::vector batchedDrawBuffers; void updatedBatchedDrawBuffers(); + + void createVulkanVertexFormat( + VertexAttributes vertexAttributes, + bool& useConstantVertexColor, + GraphicsPipelineConfiguration& configuration); + // vulkan specific member functions and variables struct QueueFamilyIndices { @@ -163,7 +176,7 @@ namespace love { void createUniformBuffers(); void createDescriptorPool(); std::vector createDescriptorSets(graphics::Texture* texture); - void createGraphicsPipeline(); + VkPipeline createGraphicsPipeline(GraphicsPipelineConfiguration); void createFramebuffers(); void createCommandPool(); void createCommandBuffers(); @@ -175,6 +188,7 @@ namespace love { void startRecordingGraphicsCommands(); void endRecordingGraphicsCommands(); + void ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration); void prepareDraw(uint32_t currentImage); @@ -192,7 +206,8 @@ namespace love { VkDescriptorSetLayout descriptorSetLayout; VkPipelineLayout pipelineLayout; VkRenderPass renderPass; - VkPipeline graphicsPipeline; + VkPipeline currentGraphicsPipeline = VK_NULL_HANDLE; + std::vector> graphicsPipelines; // FIXME improve performance by using a hash map std::vector swapChainFramBuffers; VkCommandPool commandPool; std::vector commandBuffers; diff --git a/src/modules/graphics/vulkan/Vulkan.cpp b/src/modules/graphics/vulkan/Vulkan.cpp new file mode 100644 index 000000000..83763a563 --- /dev/null +++ b/src/modules/graphics/vulkan/Vulkan.cpp @@ -0,0 +1,88 @@ +#include "Vulkan.h" + +namespace love { + namespace graphics { + namespace vulkan { + VkFormat Vulkan::getVulkanVertexFormat(DataFormat format) { + switch (format) { + case DATAFORMAT_FLOAT: + return VK_FORMAT_R32_SFLOAT; + case DATAFORMAT_FLOAT_VEC2: + return VK_FORMAT_R32G32_SFLOAT; + case DATAFORMAT_FLOAT_VEC3: + return VK_FORMAT_R32G32B32_SFLOAT; + case DATAFORMAT_FLOAT_VEC4: + return VK_FORMAT_R32G32B32A32_SFLOAT; + + case DATAFORMAT_FLOAT_MAT2X2: + case DATAFORMAT_FLOAT_MAT2X3: + case DATAFORMAT_FLOAT_MAT2X4: + case DATAFORMAT_FLOAT_MAT3X2: + case DATAFORMAT_FLOAT_MAT3X3: + case DATAFORMAT_FLOAT_MAT3X4: + case DATAFORMAT_FLOAT_MAT4X2: + case DATAFORMAT_FLOAT_MAT4X3: + case DATAFORMAT_FLOAT_MAT4X4: + throw love::Exception("unimplemented data format (matnxm)"); + + case DATAFORMAT_INT32: + return VK_FORMAT_R32_SINT; + case DATAFORMAT_INT32_VEC2: + return VK_FORMAT_R32G32_SINT; + case DATAFORMAT_INT32_VEC3: + return VK_FORMAT_R32G32B32_SINT; + case DATAFORMAT_INT32_VEC4: + return VK_FORMAT_R32G32B32A32_SINT; + + case DATAFORMAT_UINT32: + return VK_FORMAT_R32_UINT; + case DATAFORMAT_UINT32_VEC2: + return VK_FORMAT_R32G32_UINT; + case DATAFORMAT_UINT32_VEC3: + return VK_FORMAT_R32G32B32_UINT; + case DATAFORMAT_UINT32_VEC4: + return VK_FORMAT_R32G32B32A32_UINT; + + case DATAFORMAT_SNORM8_VEC4: + return VK_FORMAT_R8G8B8A8_SNORM; + case DATAFORMAT_UNORM8_VEC4: + return VK_FORMAT_R8G8B8A8_UNORM; + case DATAFORMAT_INT8_VEC4: + return VK_FORMAT_R8G8B8A8_SINT; + case DATAFORMAT_UINT8_VEC4: + return VK_FORMAT_R8G8B8A8_UINT; + + case DATAFORMAT_SNORM16_VEC2: + return VK_FORMAT_R16G16_SNORM; + case DATAFORMAT_SNORM16_VEC4: + return VK_FORMAT_R16G16B16A16_SNORM; + case DATAFORMAT_UNORM16_VEC2: + return VK_FORMAT_R16G16_UNORM; + case DATAFORMAT_UNORM16_VEC4: + return VK_FORMAT_R16G16B16A16_UNORM; + + case DATAFORMAT_INT16_VEC2: + return VK_FORMAT_R16G16_SINT; + case DATAFORMAT_INT16_VEC4: + return VK_FORMAT_R16G16B16A16_SINT; + + case DATAFORMAT_UINT16: + return VK_FORMAT_R16_UINT; + case DATAFORMAT_UINT16_VEC2: + return VK_FORMAT_R16G16_UINT; + case DATAFORMAT_UINT16_VEC4: + return VK_FORMAT_R16G16B16A16_UINT; + + case DATAFORMAT_BOOL: + case DATAFORMAT_BOOL_VEC2: + case DATAFORMAT_BOOL_VEC3: + case DATAFORMAT_BOOL_VEC4: + throw love::Exception("unimplemented data format (bool)"); + + default: + throw love::Exception("unknown data format"); + } + } + } + } +} diff --git a/src/modules/graphics/vulkan/Vulkan.h b/src/modules/graphics/vulkan/Vulkan.h new file mode 100644 index 000000000..8dcb1de9c --- /dev/null +++ b/src/modules/graphics/vulkan/Vulkan.h @@ -0,0 +1,18 @@ +#ifndef LOVE_GRAPHICS_VULKAN_VULKAN_H +#define LOVE_GRAPHICS_VULKAN_VULKAN_H + +#include "Vulkan.h" +#include "Graphics.h" + +namespace love { + namespace graphics { + namespace vulkan { + class Vulkan { + public: + static VkFormat getVulkanVertexFormat(DataFormat format); + }; + } + } +} + +#endif