diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 8687823fe..59905d4ed 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -83,7 +83,6 @@ love::graphics::Buffer* Graphics::newBuffer(const love::graphics::Buffer::Settin return new Buffer(this, settings, format, data, size, arraylength); } -// FIXME: clear stencil and depth missing. void Graphics::clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) { VkClearAttachment attachment{}; @@ -95,12 +94,31 @@ void Graphics::clear(OptionalColorD color, OptionalInt stencil, OptionalDouble d attachment.clearValue.color.float32[3] = static_cast(color.value.a); } + VkClearAttachment depthStencilAttachment{}; + + if (stencil.hasValue) { + depthStencilAttachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + depthStencilAttachment.clearValue.depthStencil.stencil = static_cast(stencil.value); + } + if (depth.hasValue) { + depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + depthStencilAttachment.clearValue.depthStencil.depth = static_cast(depth.value); + } + + std::array attachments = { + attachment, + depthStencilAttachment + }; + VkClearRect rect{}; rect.layerCount = 1; rect.rect.extent.width = static_cast(currentViewportWidth); rect.rect.extent.height = static_cast(currentViewportHeight); - vkCmdClearAttachments(commandBuffers[currentFrame], 1, &attachment, 1, &rect); + vkCmdClearAttachments( + commandBuffers[currentFrame], + static_cast(attachments.size()), attachments.data(), + 1, &rect); } void Graphics::clear(const std::vector& colors, OptionalInt stencil, OptionalDouble depth) { @@ -214,6 +232,7 @@ bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int createSwapChain(); createImageViews(); createSyncObjects(); + createDepthResources(); createCommandPool(); createCommandBuffers(); startRecordingGraphicsCommands(); @@ -422,6 +441,23 @@ void Graphics::setScissor() { states.back().scissor = false; } +void Graphics::setStencilMode(StencilAction action, CompareMode compare, int value, love::uint32 readmask, love::uint32 writemask) { + flushBatchedDraws(); + + states.back().stencil.action = action; + states.back().stencil.compare = compare; + states.back().stencil.value = value; + states.back().stencil.readMask = readmask; + states.back().stencil.writeMask = writemask; +} + +void Graphics::setDepthMode(CompareMode compare, bool write) { + flushBatchedDraws(); + + states.back().depthTest = compare; + states.back().depthWrite = write; +} + void Graphics::setWireframe(bool enable) { flushBatchedDraws(); @@ -1048,18 +1084,27 @@ VkPresentModeKHR Graphics::chooseSwapPresentMode(const std::vector attachments = { + configuration.imageView, + depthImageView }; VkFramebufferCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; createInfo.renderPass = configuration.renderPass; - createInfo.attachmentCount = 1; - createInfo.pAttachments = attachments; + createInfo.attachmentCount = static_cast(attachments.size()); + createInfo.pAttachments = attachments.data(); createInfo.width = configuration.width; createInfo.height = configuration.height; - createInfo.layers = 1; // is this correct? + createInfo.layers = 1; VkFramebuffer frameBuffer; if (vkCreateFramebuffer(device, &createInfo, nullptr, &frameBuffer) != VK_SUCCESS) { @@ -1186,21 +1232,48 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration configuration) { colorDescription.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorDescription.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentDescription depthStencilAttachment{}; + depthStencilAttachment.format = findDepthFormat(); + depthStencilAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthStencilAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthStencilAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthStencilAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkAttachmentReference depthStencilAttachmentRef{}; + depthStencilAttachmentRef.attachment = 1; + depthStencilAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkSubpassDescription subPass{}; subPass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subPass.colorAttachmentCount = 1; subPass.pColorAttachments = &colorAttachmentRef; + subPass.pDepthStencilAttachment = &depthStencilAttachmentRef; + + std::array attachments = { colorDescription, depthStencilAttachment }; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - createInfo.attachmentCount = 1; - createInfo.pAttachments = &colorDescription; + createInfo.attachmentCount = static_cast(attachments.size()); + createInfo.pAttachments = attachments.data(); createInfo.subpassCount = 1; createInfo.pSubpasses = &subPass; + createInfo.dependencyCount = 1; + createInfo.pDependencies = &dependency; VkRenderPass renderPass; if (vkCreateRenderPass(device, &createInfo, nullptr, &renderPass) != VK_SUCCESS) { @@ -1284,6 +1357,8 @@ void Graphics::createVulkanVertexFormat( } void Graphics::prepareDraw(const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture, PrimitiveType primitiveType, CullMode cullmode) { + states.back().stencil; + GraphicsPipelineConfiguration configuration; configuration.renderPass = currentRenderPass; configuration.vertexAttributes = attributes; @@ -1293,6 +1368,9 @@ void Graphics::prepareDraw(const VertexAttributes& attributes, const BufferBindi configuration.blendState = states.back().blend; configuration.colorChannelMask = states.back().colorMask; configuration.winding = states.back().winding; + configuration.stencil = states.back().stencil; + configuration.depthState.compare = states.back().depthTest; + configuration.depthState.write = states.back().depthWrite; configuration.cullmode = cullmode; configuration.viewportWidth = currentViewportWidth; configuration.viewportHeight = currentViewportHeight; @@ -1509,6 +1587,32 @@ VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration config multisampling.alphaToCoverageEnable = VK_FALSE; // Optional multisampling.alphaToOneEnable = VK_FALSE; // Optional + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = Vulkan::getBool(configuration.depthState.write); + depthStencil.depthCompareOp = Vulkan::getCompareOp(configuration.depthState.compare); + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.minDepthBounds = 0.0f; + depthStencil.maxDepthBounds = 1.0f; + depthStencil.stencilTestEnable = VK_TRUE; + + depthStencil.front.failOp = VK_STENCIL_OP_KEEP; + depthStencil.front.passOp = Vulkan::getStencilOp(configuration.stencil.action); + depthStencil.front.depthFailOp = VK_STENCIL_OP_KEEP; + depthStencil.front.compareOp = Vulkan::getCompareOp(configuration.stencil.compare); + depthStencil.front.compareMask = configuration.stencil.readMask; + depthStencil.front.writeMask = configuration.stencil.writeMask; + depthStencil.front.reference = configuration.stencil.value; + + depthStencil.back.failOp = VK_STENCIL_OP_KEEP; + depthStencil.back.passOp = Vulkan::getStencilOp(configuration.stencil.action); + depthStencil.back.depthFailOp = VK_STENCIL_OP_KEEP; + depthStencil.back.compareOp = Vulkan::getCompareOp(configuration.stencil.compare); + depthStencil.back.compareMask = configuration.stencil.readMask; + depthStencil.back.writeMask = configuration.stencil.writeMask; + depthStencil.back.reference = static_cast(configuration.stencil.value); + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; colorBlendAttachment.colorWriteMask = Vulkan::getColorMask(configuration.colorChannelMask); colorBlendAttachment.blendEnable = Vulkan::getBool(configuration.blendState.enable); @@ -1539,7 +1643,7 @@ VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration config pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pDepthStencilState = nullptr; + pipelineInfo.pDepthStencilState = &depthStencil; pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.pDynamicState = nullptr; pipelineInfo.layout = configuration.shader->getGraphicsPipelineLayout(); @@ -1570,6 +1674,72 @@ void Graphics::ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration } } +VkFormat Graphics::findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (auto format : candidates) { + VkFormatProperties properties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &properties); + if (tiling == VK_IMAGE_TILING_LINEAR && (properties.linearTilingFeatures & features) == features) { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (properties.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw love::Exception("failed to find supported format"); +} + + +VkFormat Graphics::findDepthFormat() { + return findSupportedFormat( + { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); +} + +void Graphics::createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = depthFormat; + imageInfo.extent.width = swapChainExtent.width; + imageInfo.extent.height = swapChainExtent.height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo allocationInfo{}; + allocationInfo.usage = VMA_MEMORY_USAGE_AUTO; + allocationInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + vmaCreateImage(vmaAllocator, &imageInfo, &allocationInfo, &depthImage, &depthImageAllocation, nullptr); + + VkImageViewCreateInfo imageViewInfo{}; + imageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewInfo.image = depthImage; + imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewInfo.format = depthFormat; + imageViewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + imageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + imageViewInfo.subresourceRange.baseMipLevel = 0; + imageViewInfo.subresourceRange.levelCount = 1; + imageViewInfo.subresourceRange.baseArrayLayer = 0; + imageViewInfo.subresourceRange.layerCount = 1; + + vkCreateImageView(device, &imageViewInfo, nullptr, &depthImageView); +} + void Graphics::createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); @@ -1683,6 +1853,8 @@ void Graphics::cleanup() { } void Graphics::cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vmaDestroyImage(vmaAllocator, depthImage, depthImageAllocation); for (const auto& [key, val] : framebuffers) { vkDestroyFramebuffer(device, val, nullptr); } @@ -1701,6 +1873,7 @@ void Graphics::recreateSwapChain() { createSwapChain(); createImageViews(); + createDepthResources(); } love::graphics::Graphics* createInstance() { diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 955503769..aee2c7d98 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -68,6 +68,8 @@ struct GraphicsPipelineConfiguration { float viewportWidth; float viewportHeight; std::optional scissorRect; + StencilState stencil; + DepthState depthState; GraphicsPipelineConfiguration() { memset(this, 0, sizeof(GraphicsPipelineConfiguration)); @@ -154,8 +156,8 @@ public: 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 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; @@ -216,6 +218,9 @@ private: void createDefaultShaders(); VkRenderPass createRenderPass(RenderPassConfiguration); VkPipeline createGraphicsPipeline(GraphicsPipelineConfiguration); + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features); + VkFormat findDepthFormat(); + void createDepthResources(); void createCommandPool(); void createCommandBuffers(); void createSyncObjects(); @@ -252,6 +257,9 @@ private: std::vector swapChainImageViews; VkPipeline currentGraphicsPipeline = VK_NULL_HANDLE; VkRenderPass currentRenderPass = VK_NULL_HANDLE; + VkImage depthImage; + VkImageView depthImageView; + VmaAllocation depthImageAllocation; std::unordered_map renderPasses; std::unordered_map framebuffers; std::unordered_map graphicsPipelines; diff --git a/src/modules/graphics/vulkan/Vulkan.cpp b/src/modules/graphics/vulkan/Vulkan.cpp index bced12665..38351e947 100644 --- a/src/modules/graphics/vulkan/Vulkan.cpp +++ b/src/modules/graphics/vulkan/Vulkan.cpp @@ -583,6 +583,29 @@ VkDescriptorType Vulkan::getDescriptorType(graphics::Shader::UniformType type) { } } +VkStencilOp Vulkan::getStencilOp(StencilAction action) { + switch (action) { + case STENCIL_KEEP: + return VK_STENCIL_OP_KEEP; + case STENCIL_ZERO: + return VK_STENCIL_OP_ZERO; + case STENCIL_REPLACE: + return VK_STENCIL_OP_REPLACE; + case STENCIL_INCREMENT: + return VK_STENCIL_OP_INCREMENT_AND_CLAMP; + case STENCIL_DECREMENT: + return VK_STENCIL_OP_DECREMENT_AND_CLAMP; + case STENCIL_INCREMENT_WRAP: + return VK_STENCIL_OP_INCREMENT_AND_WRAP; + case STENCIL_DECREMENT_WRAP: + return VK_STENCIL_OP_DECREMENT_AND_WRAP; + case STENCIL_INVERT: + return VK_STENCIL_OP_INVERT; + default: + throw love::Exception("unknown stencil action"); + } +} + void Vulkan::cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t baseLevel, uint32_t levelCount, uint32_t baseLayer, uint32_t layerCount) { VkImageMemoryBarrier barrier{}; diff --git a/src/modules/graphics/vulkan/Vulkan.h b/src/modules/graphics/vulkan/Vulkan.h index fdaf97934..be1316544 100644 --- a/src/modules/graphics/vulkan/Vulkan.h +++ b/src/modules/graphics/vulkan/Vulkan.h @@ -52,6 +52,7 @@ public: static VkCompareOp getCompareOp(CompareMode); static VkSamplerMipmapMode getMipMapMode(SamplerState::MipmapFilterMode); static VkDescriptorType getDescriptorType(graphics::Shader::UniformType); + static VkStencilOp getStencilOp(StencilAction); static void cmdTransitionImageLayout( VkCommandBuffer, VkImage, VkImageLayout oldLayout, VkImageLayout newLayout,