diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 0ada82ba..9277bdbb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -10,10 +10,15 @@ #include "MG_State/GLState/Core.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" +#include "MG_State/GLState/TextureState/TextureObject2D.h" #include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" #include namespace MobileGL::MG_Backend::DirectVulkan { + namespace { + constexpr Uint kFallbackTexture2DExternalIndex = 0xFFFFFF00u; + } + static Bool FindFramebufferAttachmentForTexture(const MG_State::GLState::FramebufferObject& framebuffer, const MG_State::GLState::ITextureObject& texture, FramebufferAttachmentType& outAttachment, Int& outLevel) { @@ -145,6 +150,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_peakDescriptorSetsObserved = 0; m_textureManager = nullptr; m_samplerManager = nullptr; + m_fallbackTexture2D.reset(); } void UniformManager::BeginFrame(Uint32 frameIndex) { @@ -190,12 +196,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Int unit = ResolveSamplerUnitIndex(program, location, binding); auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); const auto samplerOverride = textureUnit.GetSamplerObject(); + const auto preferredTarget = programObj.samplerTextureTargetByBinding[binding]; if (texture == nullptr) { - MGLOG_E( - "ResolveSamplerDescriptor: sampler binding %u ('%s') resolved null texture (location=%d unit=%d target=%d)", + texture = GetFallbackTexture(preferredTarget); + MOBILEGL_ASSERT(texture != nullptr, + "ResolveSamplerDescriptor: no fallback texture available for binding=%u location=%d unit=%d target=%d", + binding, location, unit, static_cast(preferredTarget)); + MGLOG_W( + "ResolveSamplerDescriptor: using fallback texture for unbound sampler binding=%u ('%s') location=%d unit=%d target=%d", binding, programObj.samplerNameByBinding[binding].c_str(), location, unit, - static_cast(programObj.samplerTextureTargetByBinding[binding])); - return false; + static_cast(preferredTarget)); } const MG_State::GLState::SamplerObject* samplerToUse = @@ -291,11 +301,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding]; outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject(); - MOBILEGL_ASSERT(outTexture, - "ResolveSamplerTexture: no texture bound for active sampler binding=%u location=%d unit=%d target=%d", - binding, location, unit, static_cast(preferredTarget)); + return true; + } - return outTexture != nullptr; + SharedPtr UniformManager::GetFallbackTexture(TextureTarget target) const { + MOBILEGL_ASSERT(target == TextureTarget::Texture2D || target == TextureTarget::TextureRectangle, + "UniformManager::GetFallbackTexture: unsupported fallback target=%d", + static_cast(target)); + + if (m_fallbackTexture2D == nullptr) { + auto fallbackTexture = MakeShared(kFallbackTexture2DExternalIndex); + fallbackTexture->SetInternalFormat(TextureInternalFormat::RGBA8); + fallbackTexture->AllocateStorage(TextureUploadTarget::Texture2D, 0, + {.texelSize = {1, 1, 1}, .byteSize = 4}); + fallbackTexture->MarkStorageDirty(TextureUploadTarget::Texture2D, 0, true); + m_fallbackTexture2D = fallbackTexture; + } + + return m_fallbackTexture2D; } Bool UniformManager::CollectSampledTextures(const MG_State::GLState::ProgramObject& program, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 18dfc249..3070ce76 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -65,6 +65,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, SharedPtr& outTexture); + SharedPtr GetFallbackTexture(TextureTarget target) const; Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, VkDescriptorImageInfo& outImageInfo) const; @@ -90,6 +91,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 m_peakDescriptorSetsObserved = 0; VkTextureManager* m_textureManager = nullptr; VkSamplerManager* m_samplerManager = nullptr; + mutable SharedPtr m_fallbackTexture2D; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 42c2ca12..b53f0bba 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -267,6 +267,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, .texture = texture, + .textureMipLevel = attachmentMipLevel, .finalLayout = desc.finalLayout, }); attachmentViews.emplace_back(m_textureManager.GetOrCreateViewAtMipLevel(*texture, attachmentMipLevel)); @@ -369,6 +370,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, .texture = &texture, + .textureMipLevel = attachmentMipLevel, .finalLayout = depthAttachmentDescription.finalLayout, }); textureResources.emplace_back(depthTextureResource); @@ -512,7 +514,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { switch (trackedAttachment.target) { case TrackedAttachmentTarget::Texture: MOBILEGL_ASSERT(s_textureManager != nullptr, "EndRenderPass: texture manager is null"); - s_textureManager->UpdateTrackedImageLayout(trackedAttachment.texture, trackedAttachment.finalLayout); + s_textureManager->UpdateTrackedImageLayoutAfterAttachmentWrite( + commandBuffer, + trackedAttachment.texture, + trackedAttachment.textureMipLevel, + trackedAttachment.finalLayout); break; case TrackedAttachmentTarget::SwapchainColor: MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null"); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 4ac091ed..1a100b1b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -32,6 +32,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct TrackedAttachmentLayoutInfo { TrackedAttachmentTarget target = TrackedAttachmentTarget::Texture; MG_State::GLState::ITextureObject* texture = nullptr; + Uint32 textureMipLevel = 0; Uint32 swapchainImageIndex = 0; VkImageLayout finalLayout = VK_IMAGE_LAYOUT_UNDEFINED; }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index f72fe885..f43b4424 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -17,6 +17,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { static constexpr VkPipelineStageFlags kGraphicsSampledReadStages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) { + Int maxDimension = std::max(baseTexelSize.x(), + std::max(baseTexelSize.y(), std::max(baseTexelSize.z(), 1))); + Uint32 mipLevelCount = 1; + while (maxDimension > 1) { + maxDimension = std::max(maxDimension / 2, 1); + ++mipLevelCount; + } + return mipLevelCount; + } + struct TextureFormatInfo { VkFormat format = VK_FORMAT_UNDEFINED; Bool expandRgbToRgba = false; @@ -290,16 +301,34 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - static VkComponentMapping ResolveSampledViewComponents(const MG_State::GLState::ITextureObject& texture) { - VkComponentMapping components{ - VK_COMPONENT_SWIZZLE_R, - VK_COMPONENT_SWIZZLE_G, - VK_COMPONENT_SWIZZLE_B, - VK_COMPONENT_SWIZZLE_A, - }; - if (ResolveTextureFormatInfo(texture.GetFormat()).expandRgbToRgba) { - components.a = VK_COMPONENT_SWIZZLE_ONE; + static VkComponentSwizzle ToVkComponentSwizzle(TextureSwizzleParam swizzle) { + switch (swizzle) { + case TextureSwizzleParam::Red: + return VK_COMPONENT_SWIZZLE_R; + case TextureSwizzleParam::Green: + return VK_COMPONENT_SWIZZLE_G; + case TextureSwizzleParam::Blue: + return VK_COMPONENT_SWIZZLE_B; + case TextureSwizzleParam::Alpha: + return VK_COMPONENT_SWIZZLE_A; + case TextureSwizzleParam::Zero: + return VK_COMPONENT_SWIZZLE_ZERO; + case TextureSwizzleParam::One: + return VK_COMPONENT_SWIZZLE_ONE; + default: + MOBILEGL_ASSERT(false, "ToVkComponentSwizzle: unsupported swizzle=%d", static_cast(swizzle)); + return VK_COMPONENT_SWIZZLE_IDENTITY; } + } + + static VkComponentMapping ResolveSampledViewComponents(const MG_State::GLState::ITextureObject& texture) { + const auto& swizzles = texture.GetAllSwizzleParams(); + VkComponentMapping components{ + ToVkComponentSwizzle(swizzles.x()), + ToVkComponentSwizzle(swizzles.y()), + ToVkComponentSwizzle(swizzles.z()), + ToVkComponentSwizzle(swizzles.w()), + }; return components; } @@ -335,10 +364,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_allocator = initInfo.allocator; m_commandPool = initInfo.commandPool; m_graphicsQueue = initInfo.graphicsQueue; + m_currentFrameIndex = 0; + m_deferredReleases.clear(); + m_deferredReleases.resize(initInfo.frameCount); + m_deferredViewReleases.clear(); + m_deferredViewReleases.resize(initInfo.frameCount); MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE && m_allocator != nullptr && m_commandPool != VK_NULL_HANDLE && m_graphicsQueue != VK_NULL_HANDLE, "VkTextureManager::Initialize failed: invalid initialization info"); + MOBILEGL_ASSERT(initInfo.frameCount > 0, + "VkTextureManager::Initialize failed: frameCount must be > 0"); TextureResource::s_device = m_device; TextureResource::s_allocator = m_allocator; @@ -347,6 +383,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkTextureManager::Shutdown() { + DestroyDeferredReleases(); m_textureResources.clear(); m_device = VK_NULL_HANDLE; @@ -354,6 +391,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_allocator = nullptr; m_commandPool = VK_NULL_HANDLE; m_graphicsQueue = VK_NULL_HANDLE; + m_currentFrameIndex = 0; + } + + void VkTextureManager::BeginFrame(Uint32 frameIndex) { + MOBILEGL_ASSERT(frameIndex < m_deferredReleases.size(), + "VkTextureManager::BeginFrame invalid frame index %u (size=%zu)", + frameIndex, m_deferredReleases.size()); + MOBILEGL_ASSERT(frameIndex < m_deferredViewReleases.size(), + "VkTextureManager::BeginFrame invalid deferred-view frame index %u (size=%zu)", + frameIndex, m_deferredViewReleases.size()); + m_currentFrameIndex = frameIndex; + CollectDeferredReleases(frameIndex); } VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& texture) { @@ -437,6 +486,60 @@ namespace MobileGL::MG_Backend::DirectVulkan { it->second.layout = newLayout; } + void VkTextureManager::UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer, + MG_State::GLState::ITextureObject* texture, + Uint32 writtenMipLevel, + VkImageLayout newLayout) { + MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayoutAfterAttachmentWrite: texture is null"); + auto it = m_textureResources.find(texture); + MOBILEGL_ASSERT(it != m_textureResources.end(), + "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d has no tracked resource", + texture->GetExternalIndex()); + + auto& resource = it->second; + MOBILEGL_ASSERT(resource.image != VK_NULL_HANDLE, + "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d has null image", + texture->GetExternalIndex()); + MOBILEGL_ASSERT(writtenMipLevel < resource.mipLevels, + "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d mipLevel=%u out of range %u", + texture->GetExternalIndex(), writtenMipLevel, resource.mipLevels); + + if (resource.layout != newLayout && resource.mipLevels > 1) { + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcAccessMask = 0; + GetImageTransitionSourceState(resource.layout, srcStageMask, srcAccessMask); + + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstAccessMask = 0; + GetImageTransitionDestinationState(newLayout, dstStageMask, dstAccessMask); + + if (writtenMipLevel > 0) { + VkImageLayout lowerMipLayout = resource.layout; + const Bool lowerTransitioned = TransitionImageLayout( + commandBuffer, resource.image, lowerMipLayout, newLayout, + srcStageMask, dstStageMask, srcAccessMask, dstAccessMask, + resource.aspect, 0, writtenMipLevel); + MOBILEGL_ASSERT(lowerTransitioned, + "UpdateTrackedImageLayoutAfterAttachmentWrite: failed to transition lower mip levels for textureId=%d", + texture->GetExternalIndex()); + } + + const Uint32 upperBaseMipLevel = writtenMipLevel + 1; + if (upperBaseMipLevel < resource.mipLevels) { + VkImageLayout upperMipLayout = resource.layout; + const Bool upperTransitioned = TransitionImageLayout( + commandBuffer, resource.image, upperMipLayout, newLayout, + srcStageMask, dstStageMask, srcAccessMask, dstAccessMask, + resource.aspect, upperBaseMipLevel, resource.mipLevels - upperBaseMipLevel); + MOBILEGL_ASSERT(upperTransitioned, + "UpdateTrackedImageLayoutAfterAttachmentWrite: failed to transition upper mip levels for textureId=%d", + texture->GetExternalIndex()); + } + } + + resource.layout = newLayout; + } + Bool VkTextureManager::TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture) { TextureResource* resource = SyncTextureAndGetDescriptor(texture); if (resource == nullptr) { @@ -599,6 +702,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_D("%s: no mip levels", __func__); return false; } + const Uint32 backingMipLevels = std::max(mipLevels, ComputeFullMipLevelCount(texelSize)); TextureShapeInfo shapeInfo{}; const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo); MOBILEGL_ASSERT(supportedShape, @@ -619,13 +723,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && resource.viewType == shapeInfo.viewType && - resource.mipLevels == mipLevels; + resource.mipLevels == backingMipLevels; if (compatible) { - if (resource.perMipViews.size() != mipLevels) { - resource.perMipViews.resize(mipLevels, VK_NULL_HANDLE); + if (resource.perMipViews.size() != backingMipLevels) { + resource.perMipViews.resize(backingMipLevels, VK_NULL_HANDLE); } - if (resource.perMipSampledViews.size() != mipLevels) { - resource.perMipSampledViews.resize(mipLevels, VK_NULL_HANDLE); + if (resource.perMipSampledViews.size() != backingMipLevels) { + resource.perMipSampledViews.resize(backingMipLevels, VK_NULL_HANDLE); } return true; } @@ -638,14 +742,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && resource.viewType == shapeInfo.viewType && - resource.mipLevels < mipLevels && + resource.mipLevels < backingMipLevels && resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; std::unique_ptr preservedResource; if (preserveExistingContent) { preservedResource = std::make_unique(Move(resource)); } else { - resource.Reset(); + DeferResourceRelease(Move(resource)); } auto aspect = GetAspectMaskForFormat(format); @@ -657,7 +761,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo.extent.width = static_cast(texelSize.x()); imageInfo.extent.height = static_cast(texelSize.y()); imageInfo.extent.depth = shapeInfo.depth; - imageInfo.mipLevels = mipLevels; + imageInfo.mipLevels = backingMipLevels; imageInfo.arrayLayers = shapeInfo.arrayLayers; imageInfo.format = format; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; @@ -680,9 +784,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.extent = {static_cast(texelSize.x()), static_cast(texelSize.y())}; resource.depth = shapeInfo.depth; resource.arrayLayers = shapeInfo.arrayLayers; - resource.mipLevels = mipLevels; - resource.perMipViews.assign(mipLevels, VK_NULL_HANDLE); - resource.perMipSampledViews.assign(mipLevels, VK_NULL_HANDLE); + resource.mipLevels = backingMipLevels; + resource.perMipViews.assign(backingMipLevels, VK_NULL_HANDLE); + resource.perMipSampledViews.assign(backingMipLevels, VK_NULL_HANDLE); resource.sampledBaseMipLevel = 0; resource.sampledLevelCount = mipLevels; resource.format = format; @@ -695,10 +799,78 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_device, m_commandPool, m_graphicsQueue, *preservedResource, resource); MOBILEGL_ASSERT(preserved, "SyncTextureResource: failed to preserve texture contents while growing mip chain"); + DeferResourceRelease(Move(*preservedResource)); } return true; } + void VkTextureManager::DeferResourceRelease(TextureResource&& resource) { + if (resource.image == VK_NULL_HANDLE && resource.fullView == VK_NULL_HANDLE && + resource.perMipViews.empty() && resource.perMipSampledViews.empty()) { + return; + } + + if (m_deferredReleases.empty()) { + resource.Reset(); + return; + } + + MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredReleases.size(), + "VkTextureManager::DeferResourceRelease invalid current frame index %u (size=%zu)", + m_currentFrameIndex, m_deferredReleases.size()); + m_deferredReleases[m_currentFrameIndex].push_back(Move(resource)); + } + + void VkTextureManager::CollectDeferredReleases(Uint32 frameIndex) { + MOBILEGL_ASSERT(frameIndex < m_deferredReleases.size(), + "VkTextureManager::CollectDeferredReleases invalid frame index %u (size=%zu)", + frameIndex, m_deferredReleases.size()); + m_deferredReleases[frameIndex].clear(); + + MOBILEGL_ASSERT(frameIndex < m_deferredViewReleases.size(), + "VkTextureManager::CollectDeferredReleases invalid deferred-view frame index %u (size=%zu)", + frameIndex, m_deferredViewReleases.size()); + for (const VkImageView view : m_deferredViewReleases[frameIndex]) { + if (view != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, view, nullptr); + } + } + m_deferredViewReleases[frameIndex].clear(); + } + + void VkTextureManager::DestroyDeferredReleases() { + for (auto& deferredReleases : m_deferredReleases) { + deferredReleases.clear(); + } + m_deferredReleases.clear(); + + for (auto& deferredViews : m_deferredViewReleases) { + for (const VkImageView view : deferredViews) { + if (view != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, view, nullptr); + } + } + deferredViews.clear(); + } + m_deferredViewReleases.clear(); + } + + void VkTextureManager::DeferViewRelease(VkImageView view) { + if (view == VK_NULL_HANDLE) { + return; + } + + if (m_deferredViewReleases.empty()) { + vkDestroyImageView(m_device, view, nullptr); + return; + } + + MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredViewReleases.size(), + "VkTextureManager::DeferViewRelease invalid current frame index %u (size=%zu)", + m_currentFrameIndex, m_deferredViewReleases.size()); + m_deferredViewReleases[m_currentFrameIndex].push_back(view); + } + Bool VkTextureManager::SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource) { MOBILEGL_ASSERT(resource.image != VK_NULL_HANDLE, "SyncTextureViews: image == VK_NULL_HANDLE"); @@ -716,7 +888,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (resource.fullView != VK_NULL_HANDLE) { - vkDestroyImageView(m_device, resource.fullView, nullptr); + DeferViewRelease(resource.fullView); resource.fullView = VK_NULL_HANDLE; } @@ -770,11 +942,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; Vector uploadItems; - uploadItems.reserve(outResource.mipLevels); + const Uint32 definedMipLevels = GetUploadMipLevelCount(mipmapTexture, uploadTarget); + MOBILEGL_ASSERT(definedMipLevels <= outResource.mipLevels, + "UploadDirtyMipLevels: defined mip level count %u exceeds backing mip level count %u for textureId=%d", + definedMipLevels, outResource.mipLevels, mipmapTexture.GetExternalIndex()); + + uploadItems.reserve(definedMipLevels); const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(mipmapTexture.GetFormat()); VkDeviceSize stagingSize = 0; - for (Uint32 level = 0; level < outResource.mipLevels; ++level) { + for (Uint32 level = 0; level < definedMipLevels; ++level) { if (!mipmapTexture.IsStorageDirty(uploadTarget, level)) { continue; } @@ -987,8 +1164,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32& outBaseMipLevel, Uint32& outLevelCount) { MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0"); + Uint32 definedMipLevels = mipLevels; + if (const auto* mipTexture = dynamic_cast(&texture)) { + const auto& targets = texture.GetUploadTargets(); + for (const auto target : targets) { + const Uint32 uploadMipLevels = GetUploadMipLevelCount(*mipTexture, target); + if (uploadMipLevels == 0) { + continue; + } + definedMipLevels = std::min(mipLevels, uploadMipLevels); + break; + } + } + MOBILEGL_ASSERT(definedMipLevels > 0, "ResolveViewMipRange: texture has no defined mip levels"); + const auto& levelRange = texture.GetLevelRange(); - const Uint32 maxAvailableMipLevel = mipLevels - 1; + const Uint32 maxAvailableMipLevel = definedMipLevels - 1; const Uint32 requestedBaseMipLevel = std::min(static_cast(levelRange.x()), maxAvailableMipLevel); Uint32 requestedMaxMipLevel = std::min(static_cast(levelRange.y()), maxAvailableMipLevel); if (requestedMaxMipLevel < requestedBaseMipLevel) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 99299551..37d9a16f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -26,6 +26,7 @@ public: VmaAllocator allocator = nullptr; VkCommandPool commandPool = VK_NULL_HANDLE; VkQueue graphicsQueue = VK_NULL_HANDLE; + Uint32 frameCount = 0; }; struct TextureResource { @@ -112,12 +113,17 @@ public: Bool Initialize(const InitInfo& initInfo); void Shutdown(); + void BeginFrame(Uint32 frameIndex); TextureResource* SyncTextureAndGetDescriptor( MG_State::GLState::ITextureObject& texture); VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel); VkImageView GetOrCreateSampledViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel); void UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout); + void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer, + MG_State::GLState::ITextureObject* texture, + Uint32 writtenMipLevel, + VkImageLayout newLayout); Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, @@ -152,15 +158,22 @@ private: static void ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels, Uint32& outBaseMipLevel, Uint32& outLevelCount); static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format); + void DeferResourceRelease(TextureResource&& resource); + void DeferViewRelease(VkImageView view); + void CollectDeferredReleases(Uint32 frameIndex); + void DestroyDeferredReleases(); VkDevice m_device = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; VmaAllocator m_allocator = nullptr; VkCommandPool m_commandPool = VK_NULL_HANDLE; VkQueue m_graphicsQueue = VK_NULL_HANDLE; + Uint32 m_currentFrameIndex = 0; Uint8 m_gcCounter = 0; UnorderedMap> m_aliveObjects; UnorderedMap m_textureResources; + Vector> m_deferredReleases; + Vector> m_deferredViewReleases; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 9e59da90..e8a8c1f2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -133,6 +133,69 @@ namespace MobileGL::MG_Backend::DirectVulkan { static constexpr Uint kHiddenBlitFragmentShaderId = 0xFFFFFFF2u; static constexpr Uint kHiddenBlitNearestSamplerId = 0xFFFFFFF3u; static constexpr Uint kHiddenBlitLinearSamplerId = 0xFFFFFFF4u; + static constexpr Uint kHiddenDepthMipmapProgramId = 0xFFFFFFF5u; + static constexpr Uint kHiddenDepthMipmapVertexShaderId = 0xFFFFFFF6u; + static constexpr Uint kHiddenDepthMipmapFragmentShaderId = 0xFFFFFFF7u; + + static constexpr const char* kFullscreenTriangleVertexShaderSource = R"(#version 460 core +uniform vec4 uSrcRect; +uniform vec4 uDstRect; +uniform int uSurfaceTransform; +layout(location = 0) out vec2 vTexCoord; + +vec2 ApplySurfaceTransform(vec2 position, int transform) { + vec2 p = position; + p.y = -p.y; + if (transform == 1) { + p = vec2(-p.y, p.x); + } else if (transform == 2) { + p = -p; + } else if (transform == 3) { + p = vec2(p.y, -p.x); + } + return p; +} + +void main() { + const vec2 uvTri[3] = vec2[]( + vec2(0.0, 0.0), + vec2(2.0, 0.0), + vec2(0.0, 2.0) + ); + vec2 uv = uvTri[gl_VertexID]; + vec2 dst = uDstRect.xy + uv * uDstRect.zw; + vec2 clip = dst * 2.0 - 1.0; + clip = ApplySurfaceTransform(clip, uSurfaceTransform); + gl_Position = vec4(clip, 0.0, 1.0); + vTexCoord = uSrcRect.xy + uv * uSrcRect.zw; +} +)"; + + static constexpr const char* kBlitFragmentShaderSource = R"(#version 460 core +layout(binding = 0) uniform sampler2D uSource; +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(uSource, vTexCoord); +} +)"; + + static constexpr const char* kDepthMipmapFragmentShaderSource = R"(#version 460 core +layout(binding = 0) uniform sampler2D uSource; +layout(location = 0) in vec2 vTexCoord; +uniform ivec2 uSrcTexelSize; + +void main() { + ivec2 srcBase = ivec2(gl_FragCoord.xy) * 2; + ivec2 srcMax = max(uSrcTexelSize - ivec2(1), ivec2(0)); + float depth0 = texelFetch(uSource, clamp(srcBase + ivec2(0, 0), ivec2(0), srcMax), 0).r; + float depth1 = texelFetch(uSource, clamp(srcBase + ivec2(1, 0), ivec2(0), srcMax), 0).r; + float depth2 = texelFetch(uSource, clamp(srcBase + ivec2(0, 1), ivec2(0), srcMax), 0).r; + float depth3 = texelFetch(uSource, clamp(srcBase + ivec2(1, 1), ivec2(0), srcMax), 0).r; + gl_FragDepth = 0.25 * (depth0 + depth1 + depth2 + depth3); +} +)"; static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) { Int maxDimension = std::max( @@ -411,6 +474,83 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + static Bool ResolveFramebufferBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, + Uint32 swapchainImageIndex, SwapchainObject& swapchainObject, + VkTextureManager& textureManager, + VkImageAspectFlags requiredAspectMask, + BlitImageBinding& outBinding) { + const Bool isDefaultFbo = + (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); + const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, isReadFramebuffer, requiredAspectMask); + if (attachmentType == FramebufferAttachmentType::None) { + MGLOG_E("BlitFramebuffer skipped: unsupported aspect mask=0x%x", + static_cast(requiredAspectMask)); + return false; + } + + outBinding.label = isReadFramebuffer ? "read" : "draw"; + if (isDefaultFbo) { + const auto extent = swapchainObject.GetExtent(); + outBinding.extent = {static_cast(extent.width), static_cast(extent.height)}; + outBinding.mipLevel = 0; + outBinding.mipLevelCount = 1; + outBinding.trackedLayout = nullptr; + if ((requiredAspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { + outBinding.image = swapchainObject.GetImage(swapchainImageIndex); + outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + return true; + } + + const VkImageAspectFlags swapchainAspectMask = GetSwapchainDepthStencilAspectMask(swapchainObject); + if ((swapchainAspectMask & requiredAspectMask) != requiredAspectMask) { + MGLOG_E("BlitFramebuffer skipped: swapchain depth image missing required aspect mask=0x%x", + static_cast(requiredAspectMask)); + return false; + } + + outBinding.image = swapchainObject.GetDepthStencilImage(swapchainImageIndex); + outBinding.aspectMask = requiredAspectMask; + return true; + } + + const auto& attachment = fbo.GetAttachment(attachmentType); + if (!attachment.IsComplete()) { + MGLOG_E("BlitFramebuffer skipped: %s framebuffer attachment is incomplete", outBinding.label); + return false; + } + if (attachment.IsRenderbuffer()) { + MGLOG_E("BlitFramebuffer skipped: renderbuffer attachments are not supported yet"); + return false; + } + if (!attachment.IsTexture()) { + MGLOG_E("BlitFramebuffer skipped: unsupported framebuffer attachment type"); + return false; + } + + auto* texture = attachment.GetTexture().get(); + MOBILEGL_ASSERT(texture != nullptr, "ResolveFramebufferBlitBinding: texture attachment is null"); + auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); + if (resource == nullptr) { + MGLOG_E("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d", + outBinding.label, texture->GetExternalIndex()); + return false; + } + if ((resource->aspect & requiredAspectMask) != requiredAspectMask) { + MGLOG_E("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is missing aspect mask=0x%x", + outBinding.label, texture->GetExternalIndex(), static_cast(requiredAspectMask)); + return false; + } + + outBinding.image = resource->image; + outBinding.trackedLayout = &resource->layout; + outBinding.aspectMask = requiredAspectMask; + const auto attachmentExtent = attachment.GetSize(); + outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()}; + outBinding.mipLevel = static_cast(std::max(attachment.GetTextureLevel(), 0)); + outBinding.mipLevelCount = resource->mipLevels; + return true; + } + static Bool ResolveTextureCopyDestinationBinding(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, VkTextureManager& textureManager, BlitImageBinding& outBinding) { auto* resource = textureManager.SyncTextureAndGetDescriptor(texture); @@ -617,6 +757,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight), "CreateFrameContexts"); MGLOG_I("CreateFrameContexts completed"); + m_deferredDepthMipmapCleanup.assign(m_frameContext.GetFrameCount(), {}); auto succeeded = false; succeeded = m_bufferManager.Initialize({ .allocator = m_allocator, @@ -630,7 +771,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_textureManager = MakeUnique(); MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed."); succeeded = m_textureManager->Initialize( - {m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue}); + {m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue, + m_frameContext.GetFrameCount()}); MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed."); m_clearManager = MakeUnique(); MOBILEGL_ASSERT(m_clearManager != nullptr, "VkClearManager creation failed."); @@ -658,6 +800,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed."); succeeded = InitializeBlitResources(); MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed."); + succeeded = InitializeDepthMipmapResources(); + MOBILEGL_ASSERT(succeeded, "Depth mipmap pipeline resource initialization failed."); m_uniformManager = MakeUnique(); MOBILEGL_ASSERT(m_uniformManager != nullptr, "UniformDescriptorBinder creation failed."); @@ -672,6 +816,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Prime the first frame so Render() always targets an acquired swapchain image. VK_VERIFY(m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired), "Initialize, WaitAndAcquireNextImage"); + m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_transientVertexIndexBuffersThisFrame.clear(); @@ -681,8 +826,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::Shutdown() { VK_VERIFY(vkDeviceWaitIdle(m_device)); + DestroyDeferredDepthMipmapCleanup(); + m_pipelineFactory.reset(); ShutdownBlitResources(); + ShutdownDepthMipmapResources(); if (m_samplerManager) { m_samplerManager->Shutdown(); m_samplerManager.reset(); @@ -865,52 +1013,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool VulkanRenderer::InitializeBlitResources() { ShutdownBlitResources(); - static constexpr const char* kVertexShaderSource = R"(#version 460 core -uniform vec4 uSrcRect; -uniform vec4 uDstRect; -uniform int uSurfaceTransform; - -layout(location = 0) out vec2 vTexCoord; - -vec2 ApplySurfaceTransform(vec2 position, int transform) { - vec2 p = position; - p.y = -p.y; - if (transform == 1) { - p = vec2(-p.y, p.x); - } else if (transform == 2) { - p = -p; - } else if (transform == 3) { - p = vec2(p.y, -p.x); - } - return p; -} - -void main() { - const vec2 uvTri[3] = vec2[]( - vec2(0.0, 0.0), - vec2(2.0, 0.0), - vec2(0.0, 2.0) - ); - vec2 uv = uvTri[gl_VertexID]; - vec2 dst = uDstRect.xy + uv * uDstRect.zw; - vec2 clip = dst * 2.0 - 1.0; - clip = ApplySurfaceTransform(clip, uSurfaceTransform); - gl_Position = vec4(clip, 0.0, 1.0); - vTexCoord = uSrcRect.xy + uv * uSrcRect.zw; -} -)"; - static constexpr const char* kFragmentShaderSource = R"(#version 460 core -layout(binding = 0) uniform sampler2D uSource; -layout(location = 0) in vec2 vTexCoord; -layout(location = 0) out vec4 outColor; - -void main() { - outColor = texture(uSource, vTexCoord); -} -)"; - auto vertexShader = MakeShared(ShaderStage::Vertex, kHiddenBlitVertexShaderId); - vertexShader->SetShaderSource(kVertexShaderSource); + vertexShader->SetShaderSource(kFullscreenTriangleVertexShaderSource); vertexShader->Compile(); if (!vertexShader->GetCompileStatus()) { MGLOG_E("InitializeBlitResources failed: vertex shader compile error: %s", vertexShader->GetInfoLog().c_str()); @@ -918,7 +1022,7 @@ void main() { } auto fragmentShader = MakeShared(ShaderStage::Fragment, kHiddenBlitFragmentShaderId); - fragmentShader->SetShaderSource(kFragmentShaderSource); + fragmentShader->SetShaderSource(kBlitFragmentShaderSource); fragmentShader->Compile(); if (!fragmentShader->GetCompileStatus()) { MGLOG_E("InitializeBlitResources failed: fragment shader compile error: %s", fragmentShader->GetInfoLog().c_str()); @@ -982,6 +1086,123 @@ void main() { m_blitResources = {}; } + Bool VulkanRenderer::InitializeDepthMipmapResources() { + ShutdownDepthMipmapResources(); + + auto vertexShader = MakeShared(ShaderStage::Vertex, + kHiddenDepthMipmapVertexShaderId); + vertexShader->SetShaderSource(kFullscreenTriangleVertexShaderSource); + vertexShader->Compile(); + if (!vertexShader->GetCompileStatus()) { + MGLOG_E("InitializeDepthMipmapResources failed: vertex shader compile error: %s", + vertexShader->GetInfoLog().c_str()); + return false; + } + + auto fragmentShader = MakeShared(ShaderStage::Fragment, + kHiddenDepthMipmapFragmentShaderId); + fragmentShader->SetShaderSource(kDepthMipmapFragmentShaderSource); + fragmentShader->Compile(); + if (!fragmentShader->GetCompileStatus()) { + MGLOG_E("InitializeDepthMipmapResources failed: fragment shader compile error: %s", + fragmentShader->GetInfoLog().c_str()); + return false; + } + + m_depthMipmapResources.program = MakeShared(kHiddenDepthMipmapProgramId); + m_depthMipmapResources.program->AttachShader(vertexShader); + m_depthMipmapResources.program->AttachShader(fragmentShader); + m_depthMipmapResources.program->Link(false); + if (!m_depthMipmapResources.program->GetLinkStatus()) { + MGLOG_E("InitializeDepthMipmapResources failed: program link error: %s", + m_depthMipmapResources.program->GetInfoLog().c_str()); + return false; + } + + m_depthMipmapResources.srcRectLocation = m_depthMipmapResources.program->GetUniformLocation("uSrcRect"); + m_depthMipmapResources.dstRectLocation = m_depthMipmapResources.program->GetUniformLocation("uDstRect"); + m_depthMipmapResources.surfaceTransformLocation = + m_depthMipmapResources.program->GetUniformLocation("uSurfaceTransform"); + m_depthMipmapResources.srcTexelSizeLocation = + m_depthMipmapResources.program->GetUniformLocation("uSrcTexelSize"); + MOBILEGL_ASSERT(m_depthMipmapResources.srcRectLocation >= 0, + "InitializeDepthMipmapResources: missing uSrcRect"); + MOBILEGL_ASSERT(m_depthMipmapResources.dstRectLocation >= 0, + "InitializeDepthMipmapResources: missing uDstRect"); + MOBILEGL_ASSERT(m_depthMipmapResources.surfaceTransformLocation >= 0, + "InitializeDepthMipmapResources: missing uSurfaceTransform"); + MOBILEGL_ASSERT(m_depthMipmapResources.srcTexelSizeLocation >= 0, + "InitializeDepthMipmapResources: missing uSrcTexelSize"); + MOBILEGL_ASSERT(m_depthMipmapResources.program->GetUBOSize() > 0, + "InitializeDepthMipmapResources: depth mipmap program global UBO is empty"); + MOBILEGL_ASSERT(m_programFactory != nullptr, "InitializeDepthMipmapResources: program factory is null"); + + ProgramFactory::CompileOptionFlags depthMipmapTransformFlags = 0; + const auto& depthMipmapProgramObj = + m_programFactory->GetOrCreateProgram(*m_depthMipmapResources.program, depthMipmapTransformFlags); + Bool foundDepthMipmapSamplerBinding = false; + for (Uint32 binding = 0; binding < depthMipmapProgramObj.samplerNameByBinding.size(); ++binding) { + if (depthMipmapProgramObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { + continue; + } + if (depthMipmapProgramObj.samplerNameByBinding[binding] == "uSource") { + m_depthMipmapResources.samplerBinding = binding; + foundDepthMipmapSamplerBinding = true; + break; + } + } + MOBILEGL_ASSERT(foundDepthMipmapSamplerBinding, + "InitializeDepthMipmapResources: failed to resolve reflected binding for uSource"); + return true; + } + + void VulkanRenderer::ShutdownDepthMipmapResources() { + m_depthMipmapResources = {}; + } + + void VulkanRenderer::CollectDeferredDepthMipmapCleanup(Uint32 frameIndex) { + MOBILEGL_ASSERT(frameIndex < m_deferredDepthMipmapCleanup.size(), + "CollectDeferredDepthMipmapCleanup: frame index %u out of range (size=%zu)", + frameIndex, m_deferredDepthMipmapCleanup.size()); + if (m_device == VK_NULL_HANDLE) { + return; + } + + auto& cleanup = m_deferredDepthMipmapCleanup[frameIndex]; + for (auto framebuffer : cleanup.framebuffers) { + if (framebuffer != VK_NULL_HANDLE) { + vkDestroyFramebuffer(m_device, framebuffer, nullptr); + } + } + for (auto pipeline : cleanup.pipelines) { + if (pipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(m_device, pipeline, nullptr); + } + } + for (auto renderPass : cleanup.renderPasses) { + if (renderPass != VK_NULL_HANDLE) { + vkDestroyRenderPass(m_device, renderPass, nullptr); + } + } + for (auto imageView : cleanup.imageViews) { + if (imageView != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, imageView, nullptr); + } + } + + cleanup.framebuffers.clear(); + cleanup.pipelines.clear(); + cleanup.renderPasses.clear(); + cleanup.imageViews.clear(); + } + + void VulkanRenderer::DestroyDeferredDepthMipmapCleanup() { + for (Uint32 frameIndex = 0; frameIndex < m_deferredDepthMipmapCleanup.size(); ++frameIndex) { + CollectDeferredDepthMipmapCleanup(frameIndex); + } + m_deferredDepthMipmapCleanup.clear(); + } + VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) { MOBILEGL_ASSERT(m_blitResources.program != nullptr, "GetOrCreateBlitPipeline: blit program is null"); MOBILEGL_ASSERT(m_programFactory != nullptr, "GetOrCreateBlitPipeline: program factory is null"); @@ -1026,6 +1247,304 @@ void main() { return m_pipelineFactory->GetOrCreatePipeline(payload); } + Bool VulkanRenderer::GenerateDepthMipmapWithShader(FrameContext::FrameData& frame, + MG_State::GLState::ITextureObject& texture, + VkTextureManager::TextureResource& resource, + Uint32 baseMipLevel, + Uint32 generateMipLevelCount, + const IntVec3& storageBaseTexelSize, + VkImageLayout originalLayout, + VkImageLayout finalLayout) { + MOBILEGL_ASSERT(m_depthMipmapResources.program != nullptr, + "GenerateDepthMipmapWithShader: depth mipmap program is null"); + MOBILEGL_ASSERT(m_blitResources.nearestSampler != nullptr, + "GenerateDepthMipmapWithShader: helper sampler is null"); + MOBILEGL_ASSERT(m_programFactory != nullptr, "GenerateDepthMipmapWithShader: program factory is null"); + MOBILEGL_ASSERT(m_uniformManager != nullptr, "GenerateDepthMipmapWithShader: uniform manager is null"); + MOBILEGL_ASSERT(texture.GetTarget() == TextureTarget::Texture2D, + "GenerateDepthMipmapWithShader only supports GL_TEXTURE_2D depth textures"); + MOBILEGL_ASSERT((resource.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0, + "GenerateDepthMipmapWithShader requires a depth aspect"); + MOBILEGL_ASSERT(resource.depth == 1 && resource.arrayLayers == 1, + "GenerateDepthMipmapWithShader only supports single-layer depth textures"); + MOBILEGL_ASSERT(m_frameContext.GetCurrentFrameIndex() < m_deferredDepthMipmapCleanup.size(), + "GenerateDepthMipmapWithShader: frame index %u out of range (cleanup slots=%zu)", + m_frameContext.GetCurrentFrameIndex(), m_deferredDepthMipmapCleanup.size()); + auto& deferredCleanup = m_deferredDepthMipmapCleanup[m_frameContext.GetCurrentFrameIndex()]; + + static const VkPipelineVertexInputStateCreateInfo kEmptyVertexInputState { + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO + }; + + ProgramFactory::CompileOptionFlags depthMipmapTransformFlags = 0; + const auto& programObj = m_programFactory->GetOrCreateProgram(*m_depthMipmapResources.program, + depthMipmapTransformFlags); + + VkAttachmentDescription depthAttachment{}; + depthAttachment.format = resource.format; + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef{}; + depthAttachmentRef.attachment = 0; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpassDesc{}; + subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDesc.pDepthStencilAttachment = &depthAttachmentRef; + + VkRenderPassCreateInfo renderPassCreateInfo{}; + renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassCreateInfo.attachmentCount = 1; + renderPassCreateInfo.pAttachments = &depthAttachment; + renderPassCreateInfo.subpassCount = 1; + renderPassCreateInfo.pSubpasses = &subpassDesc; + + VkRenderPass renderPass = VK_NULL_HANDLE; + VK_VERIFY(vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &renderPass), + "GenerateDepthMipmapWithShader: vkCreateRenderPass"); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizationState{}; + rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; + rasterizationState.cullMode = VK_CULL_MODE_NONE; + rasterizationState.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizationState.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampleState{}; + multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depthStencilState{}; + depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencilState.depthTestEnable = VK_TRUE; + depthStencilState.depthWriteEnable = VK_TRUE; + depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS; + depthStencilState.minDepthBounds = 0.0f; + depthStencilState.maxDepthBounds = 1.0f; + + VkPipelineColorBlendStateCreateInfo colorBlendState{}; + colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + + const VkDynamicState dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(std::size(dynamicStates)); + dynamicState.pDynamicStates = dynamicStates; + + VkGraphicsPipelineCreateInfo pipelineCreateInfo{}; + pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineCreateInfo.stageCount = static_cast(programObj.stages.size()); + pipelineCreateInfo.pStages = programObj.stages.data(); + pipelineCreateInfo.pVertexInputState = &kEmptyVertexInputState; + pipelineCreateInfo.pInputAssemblyState = &inputAssembly; + pipelineCreateInfo.pViewportState = &viewportState; + pipelineCreateInfo.pRasterizationState = &rasterizationState; + pipelineCreateInfo.pMultisampleState = &multisampleState; + pipelineCreateInfo.pDepthStencilState = &depthStencilState; + pipelineCreateInfo.pColorBlendState = &colorBlendState; + pipelineCreateInfo.pDynamicState = &dynamicState; + pipelineCreateInfo.layout = programObj.pipelineLayout; + pipelineCreateInfo.renderPass = renderPass; + pipelineCreateInfo.subpass = 0; + + VkPipeline pipeline = VK_NULL_HANDLE; + VK_VERIFY(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline), + "GenerateDepthMipmapWithShader: vkCreateGraphicsPipelines"); + deferredCleanup.renderPasses.push_back(renderPass); + deferredCleanup.pipelines.push_back(pipeline); + + auto createMipView = [&](Uint32 mipLevel, VkImageAspectFlags aspectMask) { + VkImageViewCreateInfo viewCreateInfo{}; + viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewCreateInfo.image = resource.image; + viewCreateInfo.viewType = resource.viewType; + viewCreateInfo.format = resource.format; + viewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.subresourceRange.aspectMask = aspectMask; + viewCreateInfo.subresourceRange.baseMipLevel = mipLevel; + viewCreateInfo.subresourceRange.levelCount = 1; + viewCreateInfo.subresourceRange.baseArrayLayer = 0; + viewCreateInfo.subresourceRange.layerCount = resource.arrayLayers; + + VkImageView view = VK_NULL_HANDLE; + VK_VERIFY(vkCreateImageView(m_device, &viewCreateInfo, nullptr, &view), + "GenerateDepthMipmapWithShader: vkCreateImageView"); + return view; + }; + + VkPipelineStageFlags originalSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags originalSrcAccessMask = 0; + GetImageTransitionSourceState(originalLayout, originalSrcStageMask, originalSrcAccessMask); + + VkPipelineStageFlags finalDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags finalDstAccessMask = 0; + GetImageTransitionDestinationState(finalLayout, finalDstStageMask, finalDstAccessMask); + + VkPipelineStageFlags depthAttachmentStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags depthAttachmentAccessMask = 0; + GetImageTransitionDestinationState(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + depthAttachmentStageMask, depthAttachmentAccessMask); + + if (originalLayout != finalLayout) { + if (baseMipLevel > 0) { + VkImageLayout lowerMipLayout = originalLayout; + const Bool lowerReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource.image, lowerMipLayout, finalLayout, + originalSrcStageMask, finalDstStageMask, + originalSrcAccessMask, finalDstAccessMask, + resource.aspect, 0, baseMipLevel); + MOBILEGL_ASSERT(lowerReady, "%s: failed to transition lower untouched mip levels", __func__); + } + + if (generateMipLevelCount < resource.mipLevels) { + VkImageLayout upperMipLayout = originalLayout; + const Bool upperReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource.image, upperMipLayout, finalLayout, + originalSrcStageMask, finalDstStageMask, + originalSrcAccessMask, finalDstAccessMask, + resource.aspect, generateMipLevelCount, resource.mipLevels - generateMipLevelCount); + MOBILEGL_ASSERT(upperReady, "%s: failed to transition upper untouched mip levels", __func__); + } + + VkImageLayout srcMipLayout = originalLayout; + const Bool srcReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource.image, srcMipLayout, finalLayout, + originalSrcStageMask, finalDstStageMask, + originalSrcAccessMask, finalDstAccessMask, + resource.aspect, baseMipLevel, 1); + MOBILEGL_ASSERT(srcReady, "%s: failed to transition base mip level to sampled layout", __func__); + } + + resource.layout = finalLayout; + + auto* depthProgramData = static_cast(m_depthMipmapResources.program->MapUBO()); + MOBILEGL_ASSERT(depthProgramData != nullptr, "GenerateDepthMipmapWithShader: depth mipmap UBO is null"); + auto writeUniform = [&](Int location, const void* data, SizeT size) { + MOBILEGL_ASSERT(location >= 0, "GenerateDepthMipmapWithShader: invalid uniform location"); + const Uint offset = m_depthMipmapResources.program->GetUniformOffset(static_cast(location)); + MOBILEGL_ASSERT(offset + size <= m_depthMipmapResources.program->GetUBOSize(), + "GenerateDepthMipmapWithShader: uniform write out of bounds"); + memcpy(depthProgramData + offset, data, size); + }; + + for (Uint32 level = baseMipLevel + 1; level < generateMipLevelCount; ++level) { + VkImageLayout dstMipLayout = originalLayout; + const Bool dstReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource.image, dstMipLayout, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + originalSrcStageMask, depthAttachmentStageMask, + originalSrcAccessMask, depthAttachmentAccessMask, + resource.aspect, level, 1); + MOBILEGL_ASSERT(dstReady, "%s: failed to transition mip level %u to depth attachment layout", __func__, level); + + const IntVec3 srcTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level - 1); + const IntVec3 dstTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level); + const Int srcTexelSizeUniform[2] = {srcTexelSize.x(), srcTexelSize.y()}; + + const VkImageView sourceImageView = createMipView(level - 1, VK_IMAGE_ASPECT_DEPTH_BIT); + const VkImageView depthAttachmentView = createMipView(level, resource.aspect); + deferredCleanup.imageViews.push_back(sourceImageView); + deferredCleanup.imageViews.push_back(depthAttachmentView); + + VkFramebufferCreateInfo framebufferCreateInfo{}; + framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferCreateInfo.renderPass = renderPass; + framebufferCreateInfo.attachmentCount = 1; + framebufferCreateInfo.pAttachments = &depthAttachmentView; + framebufferCreateInfo.width = static_cast(dstTexelSize.x()); + framebufferCreateInfo.height = static_cast(dstTexelSize.y()); + framebufferCreateInfo.layers = 1; + + VkFramebuffer framebuffer = VK_NULL_HANDLE; + VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferCreateInfo, nullptr, &framebuffer), + "GenerateDepthMipmapWithShader: vkCreateFramebuffer"); + deferredCleanup.framebuffers.push_back(framebuffer); + + VkRenderPassBeginInfo renderPassBeginInfo{}; + renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.framebuffer = framebuffer; + renderPassBeginInfo.renderArea.offset = {0, 0}; + renderPassBeginInfo.renderArea.extent = { + static_cast(dstTexelSize.x()), static_cast(dstTexelSize.y()) + }; + + vkCmdBeginRenderPass(frame.commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(dstTexelSize.x()); + viewport.height = static_cast(dstTexelSize.y()); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = {static_cast(dstTexelSize.x()), static_cast(dstTexelSize.y())}; + vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); + + vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + std::fill(depthProgramData, + depthProgramData + m_depthMipmapResources.program->GetUBOSize(), + Uint8{0}); + BlitUniformData blitUniformData{}; + writeUniform(m_depthMipmapResources.srcRectLocation, + blitUniformData.srcRect, + sizeof(blitUniformData.srcRect)); + writeUniform(m_depthMipmapResources.dstRectLocation, + blitUniformData.dstRect, + sizeof(blitUniformData.dstRect)); + writeUniform(m_depthMipmapResources.surfaceTransformLocation, + &blitUniformData.surfaceTransform, + sizeof(blitUniformData.surfaceTransform)); + writeUniform(m_depthMipmapResources.srcTexelSizeLocation, + srcTexelSizeUniform, + sizeof(srcTexelSizeUniform)); + + const auto samplerBindingOverride = UniformManager::SamplerBindingOverride{ + .binding = m_depthMipmapResources.samplerBinding, + .texture = &texture, + .sampler = m_blitResources.nearestSampler.get(), + .imageView = sourceImageView, + }; + const Bool bound = m_uniformManager->BindProgramUniformBuffers( + frame.commandBuffer, *m_depthMipmapResources.program, programObj, + m_frameContext.GetCurrentFrameIndex(), &samplerBindingOverride); + MOBILEGL_ASSERT(bound, "GenerateDepthMipmapWithShader: BindProgramUniformBuffers failed"); + vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); + vkCmdEndRenderPass(frame.commandBuffer); + + VkImageLayout finishedMipLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + const Bool finishedReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource.image, finishedMipLayout, finalLayout, + depthAttachmentStageMask, finalDstStageMask, + depthAttachmentAccessMask, finalDstAccessMask, + resource.aspect, level, 1); + MOBILEGL_ASSERT(finishedReady, "%s: failed to transition mip level %u to sampled layout", __func__, level); + } + return true; + } + VkPipeline VulkanRenderer::GetOrCreatePipeline( GLenum mode, const MG_State::GLState::ProgramObject& program, @@ -1179,19 +1698,23 @@ void main() { static_cast(transitionedResource->layout)); } - auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired); - auto pipeline = GetOrCreatePipeline(mode, program, vao, renderPassEntry); + auto* renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired); + if (activeRenderPass && !activeRenderPass->CompatibleWith(*renderPassEntry)) { + VkRenderPassManager::EndRenderPass(frame.commandBuffer); + activeRenderPass = nullptr; + renderPassEntry = &m_renderPassManager->GetOrCreateRenderPass(*drawFbo, m_imageIndexAcquired); + } + + auto pipeline = GetOrCreatePipeline(mode, program, vao, *renderPassEntry); activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); // Begin render pass, and handle clear - if (activeRenderPass && activeRenderPass->CompatibleWith(renderPassEntry)) { - ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, renderPassEntry); + if (activeRenderPass && activeRenderPass->CompatibleWith(*renderPassEntry)) { + ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, *renderPassEntry); } else { // No active render pass or active one not compatible. // Restart a new render pass - if (activeRenderPass) - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry); + Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, *renderPassEntry); MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__); } @@ -1215,8 +1738,8 @@ void main() { VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; - viewport.width = static_cast(renderPassEntry.extent.x()); - viewport.height = static_cast(renderPassEntry.extent.y()); + viewport.width = static_cast(renderPassEntry->extent.x()); + viewport.height = static_cast(renderPassEntry->extent.y()); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport); @@ -1229,7 +1752,7 @@ void main() { scissor.extent = { (Uint)scissorBox[2], (Uint)scissorBox[3] }; } else { scissor.offset = {0, 0}; - scissor.extent = { (Uint)renderPassEntry.extent.x(), (Uint)renderPassEntry.extent.y() }; + scissor.extent = { (Uint)renderPassEntry->extent.x(), (Uint)renderPassEntry->extent.y() }; } vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); return true; @@ -1592,18 +2115,29 @@ void main() { void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { - if ((mask & ~GL_COLOR_BUFFER_BIT) != 0) { - MGLOG_E("BlitFramebuffer skipped: only GL_COLOR_BUFFER_BIT is supported right now (mask=0x%x)", - static_cast(mask)); + static constexpr GLbitfield kSupportedBlitMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + if ((mask & ~kSupportedBlitMask) != 0) { + MGLOG_E("BlitFramebuffer skipped: unsupported mask bits=0x%x", static_cast(mask)); return; } - if ((mask & GL_COLOR_BUFFER_BIT) == 0) { + const Bool isColorBlit = (mask & GL_COLOR_BUFFER_BIT) != 0; + const Bool isDepthBlit = (mask & GL_DEPTH_BUFFER_BIT) != 0; + if (!isColorBlit && !isDepthBlit) { + return; + } + if (isColorBlit && isDepthBlit) { + MGLOG_E("BlitFramebuffer skipped: combined color+depth blits are not supported yet (mask=0x%x)", + static_cast(mask)); return; } if (filter != GL_NEAREST && filter != GL_LINEAR) { MGLOG_E("BlitFramebuffer skipped: unsupported filter=0x%x", static_cast(filter)); return; } + if (isDepthBlit && filter != GL_NEAREST) { + MGLOG_E("BlitFramebuffer skipped: depth blits currently require GL_NEAREST"); + return; + } auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); @@ -1635,6 +2169,160 @@ void main() { return; } + if (isDepthBlit) { + BlitImageBinding srcBinding{}; + BlitImageBinding dstBinding{}; + if (!ResolveFramebufferBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, + *m_textureManager, VK_IMAGE_ASPECT_DEPTH_BIT, srcBinding) || + !ResolveFramebufferBlitBinding(*drawFbo, false, m_imageIndexAcquired, m_swapchainObject, + *m_textureManager, VK_IMAGE_ASPECT_DEPTH_BIT, dstBinding)) { + return; + } + + if (srcX1 < srcX0 || srcY1 < srcY0 || dstX1 < dstX0 || dstY1 < dstY0) { + MGLOG_E("BlitFramebuffer skipped: depth blits with flipped rectangles are not supported yet"); + return; + } + + const Int srcWidth = srcX1 - srcX0; + const Int srcHeight = srcY1 - srcY0; + const Int dstWidth = dstX1 - dstX0; + const Int dstHeight = dstY1 - dstY0; + if (srcWidth <= 0 || srcHeight <= 0 || srcWidth != dstWidth || srcHeight != dstHeight) { + MGLOG_E("BlitFramebuffer skipped: depth blits currently require matching source and destination extents"); + return; + } + + if (!readIsDefaultFbo) { + const auto sourceAttachmentType = ResolveFramebufferCopyAttachmentType(*readFbo, true, srcBinding.aspectMask); + const auto& sourceAttachment = readFbo->GetAttachment(sourceAttachmentType); + auto sourceTexture = sourceAttachment.GetTexture(); + MOBILEGL_ASSERT(sourceTexture != nullptr, "BlitFramebuffer: depth source texture attachment is null"); + const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); + MOBILEGL_ASSERT(clearReady, + "BlitFramebuffer: failed to materialize pending clear for depth source textureId=%d", + sourceTexture->GetExternalIndex()); + } + + const VkImageLayout srcOriginalLayout = readIsDefaultFbo + ? m_swapchainObject.GetDepthStencilImageLayout(m_imageIndexAcquired) + : *srcBinding.trackedLayout; + if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { + MGLOG_E("BlitFramebuffer skipped: depth source image layout is undefined"); + return; + } + + const VkImageLayout dstOriginalLayout = drawIsDefaultFbo + ? m_swapchainObject.GetDepthStencilImageLayout(m_imageIndexAcquired) + : *dstBinding.trackedLayout; + const VkImageLayout dstRestoreLayout = dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED + ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL + : dstOriginalLayout; + + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcAccessMask = 0; + GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask); + if (readIsDefaultFbo) { + VkImageLayout srcTrackedLayout = srcOriginalLayout; + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcBinding.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, + srcBinding.mipLevel, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain depth source image", __func__); + m_swapchainObject.SetDepthStencilImageLayout(m_imageIndexAcquired, srcTrackedLayout); + } else { + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, + srcBinding.mipLevel, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to transition depth source image", __func__); + } + + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstAccessMask = 0; + GetImageTransitionSourceState(dstOriginalLayout, dstStageMask, dstAccessMask); + if (drawIsDefaultFbo) { + VkImageLayout dstTrackedLayout = dstOriginalLayout; + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstBinding.image, dstTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask, + dstBinding.mipLevel, dstBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain depth destination image", __func__); + m_swapchainObject.SetDepthStencilImageLayout(m_imageIndexAcquired, dstTrackedLayout); + } else { + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask, + dstBinding.mipLevel, dstBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to transition depth destination image", __func__); + } + + VkImageCopy copyRegion{}; + copyRegion.srcSubresource.aspectMask = srcBinding.aspectMask; + copyRegion.srcSubresource.mipLevel = srcBinding.mipLevel; + copyRegion.srcSubresource.baseArrayLayer = 0; + copyRegion.srcSubresource.layerCount = 1; + copyRegion.srcOffset = {srcX0, srcY0, 0}; + copyRegion.dstSubresource.aspectMask = dstBinding.aspectMask; + copyRegion.dstSubresource.mipLevel = dstBinding.mipLevel; + copyRegion.dstSubresource.baseArrayLayer = 0; + copyRegion.dstSubresource.layerCount = 1; + copyRegion.dstOffset = {dstX0, dstY0, 0}; + copyRegion.extent = {static_cast(srcWidth), static_cast(srcHeight), 1}; + + vkCmdCopyImage(frame.commandBuffer, + srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ©Region); + + VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcRestoreAccessMask = 0; + GetImageTransitionDestinationState(srcOriginalLayout, srcRestoreStageMask, srcRestoreAccessMask); + if (readIsDefaultFbo) { + VkImageLayout srcTrackedLayout = m_swapchainObject.GetDepthStencilImageLayout(m_imageIndexAcquired); + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcBinding.image, srcTrackedLayout, srcOriginalLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, + VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask, + srcBinding.mipLevel, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain depth source image layout", __func__); + m_swapchainObject.SetDepthStencilImageLayout(m_imageIndexAcquired, srcTrackedLayout); + } else { + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, srcOriginalLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, + VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask, + srcBinding.mipLevel, srcBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to restore depth source image layout", __func__); + } + + VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstRestoreAccessMask = 0; + GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask); + if (drawIsDefaultFbo) { + VkImageLayout dstTrackedLayout = m_swapchainObject.GetDepthStencilImageLayout(m_imageIndexAcquired); + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstBinding.image, dstTrackedLayout, dstRestoreLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask, + dstBinding.mipLevel, dstBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain depth destination image layout", __func__); + m_swapchainObject.SetDepthStencilImageLayout(m_imageIndexAcquired, dstTrackedLayout); + } else { + Bool ok = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, dstRestoreLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask, + dstBinding.mipLevel, dstBinding.mipLevelCount); + MOBILEGL_ASSERT(ok, "%s: failed to restore depth destination image layout", __func__); + } + return; + } + BlitImageBinding srcBinding{}; BlitImageBinding dstBinding{}; if (!ResolveColorBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, srcBinding) || @@ -1947,8 +2635,11 @@ void main() { VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, resource->format, &formatProperties); const VkFormatFeatureFlags optimalTilingFeatures = formatProperties.optimalTilingFeatures; - if ((optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0 || - (optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0) { + const Bool isDepthOrStencilTexture = + (resource->aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0; + if (!isDepthOrStencilTexture && + ((optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0 || + (optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0)) { MGLOG_W("GenerateMipmap skipped for textureId=%d because Vulkan format %d does not support blit-based mip generation", texture->GetExternalIndex(), static_cast(resource->format)); return; @@ -1978,6 +2669,17 @@ void main() { const VkImageLayout originalLayout = resource->layout; const VkImageLayout finalLayout = ResolveGenerateMipmapFinalLayout(resource->aspect); + if (isDepthOrStencilTexture) { + const Bool depthReady = GenerateDepthMipmapWithShader(frame, *texture, *resource, + baseMipLevel, generateMipLevelCount, + storageBaseTexelSize, originalLayout, finalLayout); + MOBILEGL_ASSERT(depthReady, + "GenerateMipmap: depth/stencil fallback failed for textureId=%d target=%d internalFormat=%d vkFormat=%d", + texture->GetExternalIndex(), static_cast(texture->GetTarget()), + static_cast(texture->GetFormat()), static_cast(resource->format)); + return; + } + const VkFilter blitFilter = (optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; @@ -2186,6 +2888,8 @@ void main() { result = VK_SUCCESS; } VK_VERIFY(result, "Present, vkAcquireNextImageKHR"); + CollectDeferredDepthMipmapCleanup(m_frameContext.GetCurrentFrameIndex()); + m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_transientVertexIndexBuffersThisFrame.clear(); } @@ -2459,8 +3163,14 @@ void main() { presentQueueCreateInfo.pQueuePriorities = &queuePriority; } + VkPhysicalDeviceFeatures supportedDeviceFeatures{}; + vkGetPhysicalDeviceFeatures(m_physicalDevice.handle, &supportedDeviceFeatures); + VkPhysicalDeviceFeatures deviceFeatures{}; - // TODO: query and make use of device features + deviceFeatures.geometryShader = supportedDeviceFeatures.geometryShader; + deviceFeatures.independentBlend = supportedDeviceFeatures.independentBlend; + deviceFeatures.shaderClipDistance = supportedDeviceFeatures.shaderClipDistance; + deviceFeatures.shaderCullDistance = supportedDeviceFeatures.shaderCullDistance; VkDeviceCreateInfo deviceCreateInfo{}; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -2525,6 +3235,16 @@ void main() { deviceCreateInfo.enabledExtensionCount = static_cast(enabledDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data(); + MGLOG_I("Device feature support: geometryShader=%s independentBlend=%s shaderClipDistance=%s shaderCullDistance=%s", + supportedDeviceFeatures.geometryShader ? "true" : "false", + supportedDeviceFeatures.independentBlend ? "true" : "false", + supportedDeviceFeatures.shaderClipDistance ? "true" : "false", + supportedDeviceFeatures.shaderCullDistance ? "true" : "false"); + MGLOG_I("Device feature enabled: geometryShader=%s independentBlend=%s shaderClipDistance=%s shaderCullDistance=%s", + deviceFeatures.geometryShader ? "true" : "false", + deviceFeatures.independentBlend ? "true" : "false", + deviceFeatures.shaderClipDistance ? "true" : "false", + deviceFeatures.shaderCullDistance ? "true" : "false"); VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice"); s_vkCmdDrawIndexedIndirectCount = reinterpret_cast( @@ -2747,6 +3467,9 @@ void main() { vkDeviceWaitIdle(m_device); + DestroyDeferredDepthMipmapCleanup(); + m_deferredDepthMipmapCleanup.assign(m_frameContext.GetFrameCount(), {}); + ShutdownSwapchain(); CreateSwapchain(); @@ -2766,6 +3489,9 @@ void main() { const Bool okArena = m_bufferManager.RecreateTransientArenas(m_frameContext.GetFrameCount()); MOBILEGL_ASSERT(okArena, "RecreateSwapchain: buffer manager transient arena initialization failed"); if (m_frameContext.GetFrameCount() > 0) { + if (m_textureManager) { + m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_transientVertexIndexBuffersThisFrame.clear(); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 81d60773..9c4d39d3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -153,6 +153,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 samplerBinding = 0; }; + struct DepthMipmapResources { + SharedPtr program; + Int srcRectLocation = -1; + Int dstRectLocation = -1; + Int surfaceTransformLocation = -1; + Int srcTexelSizeLocation = -1; + Uint32 samplerBinding = 0; + }; + + struct DeferredDepthMipmapCleanup { + Vector imageViews; + Vector framebuffers; + Vector renderPasses; + Vector pipelines; + }; + void QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload); NativeWindowType m_window = 0; @@ -196,6 +212,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr m_textureManager; UniquePtr m_samplerManager; BlitResources m_blitResources; + DepthMipmapResources m_depthMipmapResources; + Vector m_deferredDepthMipmapCleanup; void CreateInstance(); VkResult SetupDebugMessenger(); @@ -220,7 +238,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::VertexArrayObject& vao, const IndexBufferView* pIndexBufferView = nullptr); Bool InitializeBlitResources(); + Bool InitializeDepthMipmapResources(); void ShutdownBlitResources(); + void ShutdownDepthMipmapResources(); + void CollectDeferredDepthMipmapCleanup(Uint32 frameIndex); + void DestroyDeferredDepthMipmapCleanup(); Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, MG_State::GLState::FramebufferObject& readFbo, MG_State::GLState::FramebufferObject& drawFbo, @@ -230,6 +252,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry); + Bool GenerateDepthMipmapWithShader(FrameContext::FrameData& frame, + MG_State::GLState::ITextureObject& texture, + VkTextureManager::TextureResource& resource, + Uint32 baseMipLevel, + Uint32 generateMipLevelCount, + const IntVec3& storageBaseTexelSize, + VkImageLayout originalLayout, + VkImageLayout finalLayout); void ShutdownSwapchain();