diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 684ff3ac..7b8e11fa 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -66,7 +66,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::CopyTexSubImage2D called with null GL context"); pVulkanRenderer->CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); } - void GenerateMipmap(GLenum target) {} + void GenerateMipmap(GLenum target) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GenerateMipmap called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::GenerateMipmap called with null GL context"); + pVulkanRenderer->GenerateMipmap(target); + } void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {} void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 83fd44d4..42c2ca12 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -69,6 +69,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (att.IsTexture()) { const Int textureLevel = att.GetTextureLevel(); XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel))); + + Uint64 imageIdentity = 0; + auto* texture = att.GetTexture().get(); + auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture); + if (resource != nullptr) { + imageIdentity = reinterpret_cast(resource->image); + } + XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity))); } if (includePendingClear && att.IsTexture()) { @@ -94,9 +102,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { currentLayout = m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex); } } else { - auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture); - if (resource != nullptr) { - currentLayout = resource->layout; + auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture); + if (textureResource != nullptr) { + currentLayout = textureResource->layout; } } XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout))); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 0b398784..f72fe885 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -12,6 +12,8 @@ #include "MG_Util/Converters/MGToStr/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" +#include + namespace MobileGL::MG_Backend::DirectVulkan { static constexpr VkPipelineStageFlags kGraphicsSampledReadStages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; @@ -43,6 +45,188 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + static void GetImageTransitionSourceState(VkImageLayout oldLayout, + VkPipelineStageFlags& outSrcStageMask, + VkAccessFlags& outSrcAccessMask) { + switch (oldLayout) { + case VK_IMAGE_LAYOUT_UNDEFINED: + outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + outSrcAccessMask = 0; + return; + case VK_IMAGE_LAYOUT_GENERAL: + outSrcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + outSrcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + return; + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + outSrcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + outSrcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + return; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + outSrcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + outSrcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + return; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + outSrcStageMask = kGraphicsSampledReadStages; + outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT; + return; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + outSrcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + return; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + outSrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + return; + default: + MOBILEGL_ASSERT(false, "GetImageTransitionSourceState: unsupported layout=%d", static_cast(oldLayout)); + outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + outSrcAccessMask = 0; + return; + } + } + + static void GetImageTransitionDestinationState(VkImageLayout newLayout, + VkPipelineStageFlags& outDstStageMask, + VkAccessFlags& outDstAccessMask) { + switch (newLayout) { + case VK_IMAGE_LAYOUT_GENERAL: + outDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + return; + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + outDstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + return; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + outDstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + return; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + outDstStageMask = kGraphicsSampledReadStages; + outDstAccessMask = VK_ACCESS_SHADER_READ_BIT; + return; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + outDstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + return; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + outDstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + return; + default: + MOBILEGL_ASSERT(false, "GetImageTransitionDestinationState: unsupported layout=%d", static_cast(newLayout)); + outDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + outDstAccessMask = 0; + return; + } + } + + static Bool PreserveTextureContentsOnRecreate(VkDevice device, + VkCommandPool commandPool, + VkQueue graphicsQueue, + const VkTextureManager::TextureResource& oldResource, + VkTextureManager::TextureResource& newResource) { + MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: device is null"); + MOBILEGL_ASSERT(commandPool != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: commandPool is null"); + MOBILEGL_ASSERT(graphicsQueue != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: graphicsQueue is null"); + MOBILEGL_ASSERT(oldResource.image != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: old image is null"); + MOBILEGL_ASSERT(newResource.image != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: new image is null"); + + const Uint32 preservedMipLevels = std::min(oldResource.mipLevels, newResource.mipLevels); + if (preservedMipLevels == 0 || oldResource.layout == VK_IMAGE_LAYOUT_UNDEFINED) { + return true; + } + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + VK_VERIFY(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer), + "vkAllocateCommandBuffers(texture preserve)"); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture preserve)"); + + Bool ok = VkTextureManager::TransitionImageLayout( + commandBuffer, newResource.image, newResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, newResource.aspect, 0, newResource.mipLevels); + MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare destination image"); + + VkImageLayout srcTrackedLayout = oldResource.layout; + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcAccessMask = 0; + GetImageTransitionSourceState(srcTrackedLayout, srcStageMask, srcAccessMask); + ok = VkTextureManager::TransitionImageLayout( + commandBuffer, oldResource.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, oldResource.aspect, 0, preservedMipLevels); + MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare source image"); + + Vector copyRegions; + copyRegions.reserve(preservedMipLevels); + for (Uint32 level = 0; level < preservedMipLevels; ++level) { + VkImageCopy copy{}; + copy.srcSubresource.aspectMask = oldResource.aspect; + copy.srcSubresource.mipLevel = level; + copy.srcSubresource.baseArrayLayer = 0; + copy.srcSubresource.layerCount = oldResource.arrayLayers; + copy.dstSubresource.aspectMask = newResource.aspect; + copy.dstSubresource.mipLevel = level; + copy.dstSubresource.baseArrayLayer = 0; + copy.dstSubresource.layerCount = newResource.arrayLayers; + copy.extent.width = std::max(oldResource.extent.width >> level, 1u); + copy.extent.height = std::max(oldResource.extent.height >> level, 1u); + copy.extent.depth = std::max(oldResource.depth >> level, 1u); + copyRegions.push_back(copy); + } + + vkCmdCopyImage(commandBuffer, + oldResource.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + newResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + static_cast(copyRegions.size()), copyRegions.data()); + + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags dstAccessMask = 0; + GetImageTransitionDestinationState(oldResource.layout, dstStageMask, dstAccessMask); + ok = VkTextureManager::TransitionImageLayout( + commandBuffer, newResource.image, newResource.layout, oldResource.layout, + VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, newResource.aspect, 0, newResource.mipLevels); + MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to restore destination layout"); + + VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture preserve)"); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + VkFence fence = VK_NULL_HANDLE; + VK_VERIFY(vkCreateFence(device, &fenceInfo, nullptr, &fence), "vkCreateFence(texture preserve)"); + VK_VERIFY(vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence), "vkQueueSubmit(texture preserve)"); + VK_VERIFY(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX), "vkWaitForFences(texture preserve)"); + + vkDestroyFence(device, fence, nullptr); + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + return true; + } + static TextureFormatInfo ResolveTextureFormatInfo(TextureInternalFormat format) { switch (format) { case TextureInternalFormat::RGB: @@ -446,7 +630,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - resource.Reset(); + const Bool preserveExistingContent = + resource.image != VK_NULL_HANDLE && + resource.format == format && + resource.extent.width == static_cast(texelSize.x()) && + resource.extent.height == static_cast(texelSize.y()) && + resource.depth == shapeInfo.depth && + resource.arrayLayers == shapeInfo.arrayLayers && + resource.viewType == shapeInfo.viewType && + resource.mipLevels < mipLevels && + resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; + + std::unique_ptr preservedResource; + if (preserveExistingContent) { + preservedResource = std::make_unique(Move(resource)); + } else { + resource.Reset(); + } auto aspect = GetAspectMaskForFormat(format); @@ -489,6 +689,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.aspect = aspect; resource.viewType = shapeInfo.viewType; resource.syncedTextureParamsVersion = 0; + + if (preservedResource) { + const Bool preserved = PreserveTextureContentsOnRecreate( + m_device, m_commandPool, m_graphicsQueue, *preservedResource, resource); + MOBILEGL_ASSERT(preserved, + "SyncTextureResource: failed to preserve texture contents while growing mip chain"); + } return true; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 3fa5f7ba..9e59da90 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -14,6 +14,7 @@ #include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/ProgramState/ShaderObject.h" #include "MG_State/GLState/SamplerState/SamplerObject.h" +#include "MG_State/GLState/TextureState/TextureObject.h" #include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" #include "MG_Util/Converters/GLToMG/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h" @@ -133,6 +134,68 @@ namespace MobileGL::MG_Backend::DirectVulkan { static constexpr Uint kHiddenBlitNearestSamplerId = 0xFFFFFFF3u; static constexpr Uint kHiddenBlitLinearSamplerId = 0xFFFFFFF4u; + 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; + } + + static IntVec3 ComputeMipTexelSize(const IntVec3& baseTexelSize, Uint32 relativeMipLevel) { + const Int width = std::max(baseTexelSize.x() >> static_cast(relativeMipLevel), 1); + const Int height = std::max(baseTexelSize.y() >> static_cast(relativeMipLevel), 1); + const Int depth = std::max(baseTexelSize.z() >> static_cast(relativeMipLevel), 1); + return {width, height, depth}; + } + + static Bool EnsureGenerateMipmapStorageAllocated(::MobileGL::MG_State::GLState::TextureObjectMipmap& texture, + ::MobileGL::TextureUploadTarget uploadTarget, + Uint32 baseMipLevel) { + const Uint32 existingMipLevelCount = static_cast(texture.GetMipmapLevelCount()); + if (existingMipLevelCount <= baseMipLevel) { + return false; + } + + const IntVec3 baseTexelSize = texture.GetMipmapTexelSize(uploadTarget, baseMipLevel); + const SizeT baseByteSize = texture.GetMipmapByteSize(uploadTarget, baseMipLevel); + if (baseTexelSize.x() <= 0 || baseTexelSize.y() <= 0 || baseTexelSize.z() <= 0 || baseByteSize == 0) { + return false; + } + + const SizeT baseTexelCount = static_cast(baseTexelSize.x()) * static_cast(baseTexelSize.y()) * + static_cast(baseTexelSize.z()); + if (baseTexelCount == 0 || (baseByteSize % baseTexelCount) != 0) { + return false; + } + + const SizeT bytesPerTexel = baseByteSize / baseTexelCount; + const Uint32 requiredMipLevelCount = baseMipLevel + ComputeFullMipLevelCount(baseTexelSize); + if (existingMipLevelCount >= requiredMipLevelCount) { + return true; + } + + for (Uint32 level = existingMipLevelCount; level < requiredMipLevelCount; ++level) { + const IntVec3 levelTexelSize = ComputeMipTexelSize(baseTexelSize, level - baseMipLevel); + const SizeT levelByteSize = bytesPerTexel * static_cast(levelTexelSize.x()) * + static_cast(levelTexelSize.y()) * + static_cast(levelTexelSize.z()); + texture.AllocateStorage(uploadTarget, level, {levelTexelSize, levelByteSize}); + texture.MarkStorageDirty(uploadTarget, level, false); + } + return true; + } + + static VkImageLayout ResolveGenerateMipmapFinalLayout(VkImageAspectFlags aspectMask) { + return (aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0 + ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL + : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + enum class BlitSurfaceTransform : Uint32 { Identity = 0, Rotate90 = 1, @@ -1843,6 +1906,181 @@ void main() { MOBILEGL_ASSERT(dstRestored, "%s: failed to restore destination image layout", __func__); } + void VulkanRenderer::GenerateMipmap(GLenum target) { + const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); + const auto uploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); + MOBILEGL_ASSERT(textureTarget == TextureTarget::Texture2D || textureTarget == TextureTarget::Texture3D, + "GenerateMipmap currently only supports GL_TEXTURE_2D and GL_TEXTURE_3D."); + + auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); + auto texture = textureUnit.GetBindingSlot(textureTarget).GetBoundObject(); + MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture."); + MOBILEGL_ASSERT(texture->IsComplete(), "GenerateMipmap requires a complete texture."); + + auto* mipmapTexture = dynamic_cast(texture.get()); + MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires a mipmapped texture object."); + + const Uint32 currentMipLevelCount = static_cast(mipmapTexture->GetMipmapLevelCount()); + MOBILEGL_ASSERT(currentMipLevelCount > 0, "GenerateMipmap requires level 0 storage."); + + const Uint32 baseMipLevel = std::min(static_cast(texture->GetLevelRange().x()), currentMipLevelCount - 1); + + auto& frame = m_frameContext.GetCurrent(); + if (!frame.isCommandRecording) { + m_frameContext.BeginCommandRecording(); + m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } + + if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { + VkRenderPassManager::EndRenderPass(frame.commandBuffer); + } + + const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *texture); + MOBILEGL_ASSERT(clearReady, + "GenerateMipmap: failed to materialize pending clear for textureId=%d", + texture->GetExternalIndex()); + + auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); + MOBILEGL_ASSERT(resource != nullptr && resource->image != VK_NULL_HANDLE, + "GenerateMipmap failed to sync the backend texture."); + + 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) { + 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; + } + + MOBILEGL_ASSERT(EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, baseMipLevel), + "GenerateMipmap could not allocate a full mip chain for this texture."); + + resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); + MOBILEGL_ASSERT(resource != nullptr && resource->image != VK_NULL_HANDLE, + "GenerateMipmap failed to resync the backend texture after allocating mip storage."); + MOBILEGL_ASSERT(resource->layout != VK_IMAGE_LAYOUT_UNDEFINED, + "GenerateMipmap requires initialized base-level image data."); + + const IntVec3 storageBaseTexelSize = { + static_cast(resource->extent.width), + static_cast(resource->extent.height), + static_cast(resource->depth), + }; + const IntVec3 baseTexelSize = ComputeMipTexelSize(storageBaseTexelSize, baseMipLevel); + const Uint32 requiredMipLevelCount = baseMipLevel + ComputeFullMipLevelCount(baseTexelSize); + const Uint32 generateMipLevelCount = std::min(requiredMipLevelCount, resource->mipLevels); + if (generateMipLevelCount <= baseMipLevel + 1) { + resource->layout = ResolveGenerateMipmapFinalLayout(resource->aspect); + return; + } + + const VkImageLayout originalLayout = resource->layout; + const VkImageLayout finalLayout = ResolveGenerateMipmapFinalLayout(resource->aspect); + const VkFilter blitFilter = (optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 + ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST; + + 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); + + 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; + Bool srcReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, srcMipLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + originalSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + originalSrcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, + resource->aspect, baseMipLevel, 1); + MOBILEGL_ASSERT(srcReady, "%s: failed to transition base mip level to transfer source", __func__); + + for (Uint32 level = baseMipLevel + 1; level < generateMipLevelCount; ++level) { + VkImageLayout dstMipLayout = originalLayout; + Bool dstReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, dstMipLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + originalSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, + originalSrcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, + resource->aspect, level, 1); + MOBILEGL_ASSERT(dstReady, "%s: failed to transition mip level %u to transfer destination", __func__, level); + + const IntVec3 srcTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level - 1); + const IntVec3 dstTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level); + + VkImageBlit blitRegion{}; + blitRegion.srcSubresource.aspectMask = resource->aspect; + blitRegion.srcSubresource.mipLevel = level - 1; + blitRegion.srcSubresource.baseArrayLayer = 0; + blitRegion.srcSubresource.layerCount = 1; + blitRegion.srcOffsets[0] = {0, 0, 0}; + blitRegion.srcOffsets[1] = {srcTexelSize.x(), srcTexelSize.y(), srcTexelSize.z()}; + blitRegion.dstSubresource.aspectMask = resource->aspect; + blitRegion.dstSubresource.mipLevel = level; + blitRegion.dstSubresource.baseArrayLayer = 0; + blitRegion.dstSubresource.layerCount = 1; + blitRegion.dstOffsets[0] = {0, 0, 0}; + blitRegion.dstOffsets[1] = {dstTexelSize.x(), dstTexelSize.y(), dstTexelSize.z()}; + + vkCmdBlitImage(frame.commandBuffer, + resource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blitRegion, blitFilter); + + VkImageLayout finishedSrcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + Bool srcRestored = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, finishedSrcLayout, finalLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, finalDstStageMask, + VK_ACCESS_TRANSFER_READ_BIT, finalDstAccessMask, + resource->aspect, level - 1, 1); + MOBILEGL_ASSERT(srcRestored, "%s: failed to transition mip level %u to final layout", __func__, level - 1); + + if (level + 1 < generateMipLevelCount) { + VkImageLayout nextSrcLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + Bool nextSrcReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, nextSrcLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, + resource->aspect, level, 1); + MOBILEGL_ASSERT(nextSrcReady, "%s: failed to prepare mip level %u as next transfer source", __func__, level); + } else { + VkImageLayout lastMipLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + Bool lastMipReady = VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, lastMipLayout, finalLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, finalDstStageMask, + VK_ACCESS_TRANSFER_WRITE_BIT, finalDstAccessMask, + resource->aspect, level, 1); + MOBILEGL_ASSERT(lastMipReady, "%s: failed to transition last mip level to final layout", __func__); + } + } + + resource->layout = finalLayout; + } + void VulkanRenderer::DrawArrays(const DrawCmd& payload) { auto& frame = m_frameContext.GetCurrent(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 29102504..81d60773 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -124,6 +124,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLbitfield mask, GLenum filter); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + void GenerateMipmap(GLenum target); void DrawArrays(const DrawCmd& payload); void DrawElements(const DrawIndexedCmd& payload); void MultiDrawElements(const MultiDrawIndexedCmd& payloads); diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp index daaf5929..c5f343d7 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp @@ -30,6 +30,7 @@ namespace MobileGL { case TextureInternalFormat::RG8Snorm: case TextureInternalFormat::RG8I: case TextureInternalFormat::RG8UI: + case TextureInternalFormat::DepthComponent16: return 2; case TextureInternalFormat::RGB4: @@ -39,6 +40,7 @@ namespace MobileGL { case TextureInternalFormat::SRGB8: case TextureInternalFormat::RGB8I: case TextureInternalFormat::RGB8UI: + case TextureInternalFormat::DepthComponent24: return 3; case TextureInternalFormat::RGBA2: @@ -52,6 +54,11 @@ namespace MobileGL { case TextureInternalFormat::RGB10A2: case TextureInternalFormat::RGB10A2UI: case TextureInternalFormat::R32F: + case TextureInternalFormat::DepthComponent: + case TextureInternalFormat::DepthComponent32: + case TextureInternalFormat::DepthComponent32F: + case TextureInternalFormat::DepthStencil: + case TextureInternalFormat::Depth24Stencil8: case TextureInternalFormat::RG16: case TextureInternalFormat::RG16Snorm: @@ -83,6 +90,7 @@ namespace MobileGL { case TextureInternalFormat::RGBA32F: case TextureInternalFormat::RGBA32I: case TextureInternalFormat::RGBA32UI: + case TextureInternalFormat::Depth32FStencil8: return 16; case TextureInternalFormat::R11FG11FB10F: @@ -125,7 +133,13 @@ namespace MobileGL { SizeT GetBaseInternalFormatComponentCount(TextureInternalFormat format) { switch (format) { case TextureInternalFormat::DepthComponent: + case TextureInternalFormat::DepthComponent16: + case TextureInternalFormat::DepthComponent24: + case TextureInternalFormat::DepthComponent32: + case TextureInternalFormat::DepthComponent32F: case TextureInternalFormat::DepthStencil: + case TextureInternalFormat::Depth24Stencil8: + case TextureInternalFormat::Depth32FStencil8: // Depth stencil is actually 2 components // tho real formats always gives byte size in whole // so we count this as one here