diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp index 052642bb..967e64aa 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp @@ -33,6 +33,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount))); + XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode))); @@ -134,7 +135,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { raster.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; - ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + ms.rasterizationSamples = payload.rasterizationSamples; VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO}; depthStencil.depthTestEnable = payload.depthTestEnable ? VK_TRUE : VK_FALSE; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h index 474cc938..b76925be 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h @@ -26,6 +26,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; VkRenderPass renderPass = VK_NULL_HANDLE; Uint32 colorAttachmentCount = 1; + VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; Uint32 subpass = 0; VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 9bba8023..c53e4c63 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -172,6 +172,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture); if (resource != nullptr) { imageIdentity = reinterpret_cast(resource->image); + XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount))); + } else { + const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT; + XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount))); } XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity))); } @@ -289,6 +293,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { textureResources.reserve(colorAttachmentSlotCount + 1); Vector attachmentViews; attachmentViews.reserve(colorAttachmentSlotCount + 1); + VkSampleCountFlagBits renderPassSampleCount = VK_SAMPLE_COUNT_1_BIT; + Bool hasRenderPassSampleCount = false; + const auto adoptRenderPassSampleCount = [&](VkSampleCountFlagBits sampleCount, + const char* attachmentKind, + Int attachmentId) { + if (!hasRenderPassSampleCount) { + renderPassSampleCount = sampleCount; + hasRenderPassSampleCount = true; + return; + } + MOBILEGL_ASSERT(renderPassSampleCount == sampleCount, + "GetOrCreateRenderPass: mismatched sample count %d on %s attachment %d (expected %d)", + static_cast(sampleCount), attachmentKind, attachmentId, + static_cast(renderPassSampleCount)); + }; // This should automatically work on default & offscreen FBO // assuming default FBO has the right param for (Uint32 i = 0; i < colorAttachmentSlotCount; ++i) { @@ -306,15 +325,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Color attachment description VkAttachmentDescription& desc = attachmentDescriptions.back(); switch (textureTarget) { - case TextureTarget::Texture2D: { - auto* texture2d = - static_cast(texture); + case TextureTarget::Texture2D: + case TextureTarget::Texture2DMultisample: { desc.flags = 0; desc.format = isDefaultFbo ? m_swapchainObject.GetSurfaceFormat().format : MG_Util::ConvertTextureInternalFormatToVkEnum( - texture2d->GetFormat()); - desc.samples = VK_SAMPLE_COUNT_1_BIT; + texture->GetFormat()); + VkSampleCountFlagBits attachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; ClearAttachmentPayload clearPayload{}; Bool hasClear = m_clearManager.GetPendingClear(att, clearPayload); VkImageLayout trackedColorLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -348,6 +366,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { .swapchainImageIndex = swapchainImageIndex, .finalLayout = desc.finalLayout, }); + attachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; textureResources.emplace_back(nullptr); attachmentViews.emplace_back(swapchainViews[swapchainImageIndex]); } else { @@ -356,6 +375,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i); textureResources.emplace_back(textureResource); desc.format = textureResource->format; + attachmentSampleCount = textureResource->sampleCount; trackedColorLayout = textureResource->layout; trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, @@ -372,6 +392,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(attachmentViews.back() != VK_NULL_HANDLE, "GetOrCreateRenderPass: GetOrCreateAttachmentView failed at color attachment %d", i); } + desc.samples = attachmentSampleCount; + adoptRenderPassSampleCount(attachmentSampleCount, "color", texture->GetExternalIndex()); if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { MGLOG_W("GetOrCreateRenderPass: color attachment textureId=%d starts with undefined layout and no clear; " @@ -439,10 +461,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { depthAttachmentDescription.format = isDefaultFbo ? m_swapchainObject.GetDepthStencilFormat() : MG_Util::ConvertTextureInternalFormatToVkEnum(texture.GetFormat()); + VkSampleCountFlagBits depthAttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; if (!isDefaultFbo) { depthAttachmentDescription.format = depthTextureResource->format; + depthAttachmentSampleCount = depthTextureResource->sampleCount; } - depthAttachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachmentDescription.samples = depthAttachmentSampleCount; + adoptRenderPassSampleCount(depthAttachmentSampleCount, "depth/stencil", texture.GetExternalIndex()); const auto loadInfo = ResolveDepthStencilAttachmentLoadInfo(trackedDepthLayout, clearDepth, clearStencil); depthAttachmentDescription.loadOp = loadInfo.depthLoadOp; @@ -556,13 +581,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { static_cast(attachmentViews.size()), static_cast(colorAttachmentRefs.size()), hasDepthStencilAttachment, + renderPassSampleCount, extent, 1 }; - MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u extent=%dx%d", + MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u samples=%d extent=%dx%d", static_cast(hash), static_cast(compatibilityHash), renderPassEntry.attachmentCount, renderPassEntry.colorAttachmentCount, + static_cast(renderPassEntry.sampleCount), extent.x(), extent.y()); auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry)); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 57523990..2ae81943 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -58,6 +58,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 attachmentCount = 0; Uint32 colorAttachmentCount = 0; Bool hasDepthStencilAttachment = false; + VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; IntVec2 extent = {0, 0}; Uint32 subpass = 0; @@ -73,6 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { std::swap(attachmentCount, that.attachmentCount); std::swap(colorAttachmentCount, that.colorAttachmentCount); std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment); + std::swap(sampleCount, that.sampleCount); std::swap(extent, that.extent); std::swap(subpass, that.subpass); } @@ -86,6 +88,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 attachmentCount, Uint32 colorAttachmentCount, Bool hasDepthStencilAttachment, + VkSampleCountFlagBits sampleCount, IntVec2 extent, int subpass): hash(hash), renderPass(renderpass), @@ -96,6 +99,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { attachmentCount(attachmentCount), colorAttachmentCount(colorAttachmentCount), hasDepthStencilAttachment(hasDepthStencilAttachment), + sampleCount(sampleCount), extent(extent), subpass(subpass) {} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 3c8830ac..7e33a1af 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -46,6 +46,41 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 arrayLayers = 1; }; + static Bool IsMultisampleTextureUploadTarget(TextureUploadTarget target) { + return target == TextureUploadTarget::Texture2DMultisample || + target == TextureUploadTarget::ProxyTexture2DMultisample || + target == TextureUploadTarget::Texture2DMultisampleArray || + target == TextureUploadTarget::ProxyTexture2DMultisampleArray; + } + + static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) { + switch (requestedSamples) { + case 1: + outSampleCount = VK_SAMPLE_COUNT_1_BIT; + return true; + case 2: + outSampleCount = VK_SAMPLE_COUNT_2_BIT; + return true; + case 4: + outSampleCount = VK_SAMPLE_COUNT_4_BIT; + return true; + case 8: + outSampleCount = VK_SAMPLE_COUNT_8_BIT; + return true; + case 16: + outSampleCount = VK_SAMPLE_COUNT_16_BIT; + return true; + case 32: + outSampleCount = VK_SAMPLE_COUNT_32_BIT; + return true; + case 64: + outSampleCount = VK_SAMPLE_COUNT_64_BIT; + return true; + default: + return false; + } + } + static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) { return target >= TextureUploadTarget::CubeMapPositiveX && target <= TextureUploadTarget::CubeMapNegativeZ; @@ -468,6 +503,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { case TextureUploadTarget::ProxyTextureRectangle: outShape = {}; return true; + case TextureUploadTarget::Texture2DMultisample: + case TextureUploadTarget::ProxyTexture2DMultisample: + outShape = {}; + return true; + case TextureUploadTarget::Texture2DMultisampleArray: + case TextureUploadTarget::ProxyTexture2DMultisampleArray: + MOBILEGL_ASSERT(texelSize.z() > 0, + "TryResolveTextureShapeInfo: invalid 2D multisample array depth=%d for textureId=%d", + texelSize.z(), texture.GetExternalIndex()); + outShape.imageType = VK_IMAGE_TYPE_2D; + outShape.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + outShape.depth = 1; + outShape.arrayLayers = static_cast(texelSize.z()); + return true; case TextureUploadTarget::Texture3D: case TextureUploadTarget::ProxyTexture3D: MOBILEGL_ASSERT(texelSize.z() > 0, @@ -806,6 +855,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (resource == nullptr) { return false; } + if (resource->sampleCount != VK_SAMPLE_COUNT_1_BIT) { + MGLOG_D("TransitionTextureForStorageImage: multisample textureId=%d is not exposed as a storage image", + texture.GetExternalIndex()); + return false; + } if (resource->layout == VK_IMAGE_LAYOUT_GENERAL) { return true; } @@ -958,7 +1012,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_D("%s: no mip levels", __func__); return false; } - const Uint32 backingMipLevels = std::max(mipLevels, ComputeFullMipLevelCount(texelSize)); + const Bool isMultisampleTexture = IsMultisampleTextureUploadTarget(uploadTarget); + const Uint32 backingMipLevels = + isMultisampleTexture ? 1u : std::max(mipLevels, ComputeFullMipLevelCount(texelSize)); TextureShapeInfo shapeInfo{}; const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo); MOBILEGL_ASSERT(supportedShape, @@ -972,6 +1028,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_D("%s: not Texture2D, unsupported", __func__); return false; } + VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT; + if (isMultisampleTexture && + !TryResolveSampleCountFlagBits(texture.GetSamples(), resolvedSampleCount)) { + MGLOG_D("%s: unsupported multisample count=%d for textureId=%d target=%s", __func__, + texture.GetSamples(), texture.GetExternalIndex(), + MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str()); + return false; + } const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format && resource.extent.width == static_cast(texelSize.x()) && @@ -979,6 +1043,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && resource.viewType == shapeInfo.viewType && + resource.sampleCount == resolvedSampleCount && resource.mipLevels == backingMipLevels; if (compatible) { if (resource.perMipViews.size() != backingMipLevels) { @@ -998,6 +1063,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && resource.viewType == shapeInfo.viewType && + resource.sampleCount == resolvedSampleCount && + resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT && resource.mipLevels < backingMipLevels && resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; @@ -1025,16 +1092,33 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); const Bool supportsStorageImage = + !isMultisampleTexture && (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; - imageInfo.usage = - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - (supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) | - ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) | - ((aspect & VK_IMAGE_ASPECT_DEPTH_BIT || aspect & VK_IMAGE_ASPECT_STENCIL_BIT) ? - VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : 0); - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | + (supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) | + ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) | + (((aspect & VK_IMAGE_ASPECT_DEPTH_BIT) || (aspect & VK_IMAGE_ASPECT_STENCIL_BIT)) ? + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : + 0); + if (!isMultisampleTexture) { + imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + imageInfo.samples = resolvedSampleCount; + if (isMultisampleTexture) { + VkImageFormatProperties imageFormatProperties{}; + const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( + m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, + imageInfo.flags, &imageFormatProperties); + if (imageFormatResult != VK_SUCCESS || + (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0) { + MGLOG_D("%s: sampleCount=%d is unsupported for textureId=%d target=%s format=%d usage=0x%x", + __func__, texture.GetSamples(), texture.GetExternalIndex(), + MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), + static_cast(format), static_cast(imageInfo.usage)); + return false; + } + } imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VmaAllocationCreateInfo allocationInfo{}; allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; @@ -1054,6 +1138,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.format = format; resource.aspect = aspect; resource.viewType = shapeInfo.viewType; + resource.sampleCount = resolvedSampleCount; resource.syncedTextureParamsVersion = 0; if (preservedResource) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 276a4ec4..394a8ead 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -72,6 +72,7 @@ public: VkFormat format = VK_FORMAT_UNDEFINED; VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE; VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; + VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; Uint16 syncedTextureParamsVersion = 0; TextureResource() = default; @@ -94,6 +95,7 @@ public: std::swap(this->format, that.format); std::swap(this->aspect, that.aspect); std::swap(this->viewType, that.viewType); + std::swap(this->sampleCount, that.sampleCount); std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion); } @@ -139,6 +141,7 @@ public: format = VK_FORMAT_UNDEFINED; aspect = VK_IMAGE_ASPECT_NONE; viewType = VK_IMAGE_VIEW_TYPE_2D; + sampleCount = VK_SAMPLE_COUNT_1_BIT; syncedTextureParamsVersion = 0; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 98c072d8..c207a874 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -2167,6 +2167,7 @@ void main() { .pipelineLayout = programObj.pipelineLayout, .renderPass = renderPassEntry.renderPass, .colorAttachmentCount = renderPassEntry.colorAttachmentCount, + .rasterizationSamples = renderPassEntry.sampleCount, .subpass = 0, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .cullMode = VK_CULL_MODE_NONE, @@ -2633,6 +2634,7 @@ void main() { .pipelineLayout = programObj.pipelineLayout, .renderPass = renderPassEntry.renderPass, .colorAttachmentCount = renderPassEntry.colorAttachmentCount, + .rasterizationSamples = renderPassEntry.sampleCount, .subpass = 0, .topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode), .cullMode = cullFaceEnabled