From 922507f8f8646dbb05787efdcee951d0efb2d868 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 6 Oct 2026 19:31:02 -0400 Subject: [PATCH] [Refactor] (P13 W6f): Magma's pipeline build reads the draw framebuffer record only, and the frontend texture sync behind it goes; link ratchet 58 -> 49. --- .../Renderer/VkTextureManager.cpp | 1561 ----------------- .../DirectVulkan/Renderer/VkTextureManager.h | 37 - .../DirectVulkan/Renderer/VulkanRenderer.cpp | 185 +- scripts/data/link_ratchet_baseline.txt | 11 +- 4 files changed, 14 insertions(+), 1780 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index e11198d1..fd4c37cf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -95,13 +95,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return MG_Config::Features.MagmaR11G11B10FFallback; } - static Bool IsMultisampleTextureUploadTarget(TextureUploadTarget target) { - return target == TextureUploadTarget::Texture2DMultisample || - target == TextureUploadTarget::ProxyTexture2DMultisample || - target == TextureUploadTarget::Texture2DMultisampleArray || - target == TextureUploadTarget::ProxyTexture2DMultisampleArray; - } - static Bool IsMutableStorageImageFormat(VkFormat format) { if (!vkuFormatIsColor(format) || vkuFormatIsCompressed(format)) { return false; @@ -502,151 +495,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - 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 VkComponentSwizzle ToVkSampledComponentSwizzle(TextureSwizzleParam swizzle, Bool alphaIsImplicitOne) { - if (alphaIsImplicitOne && swizzle == TextureSwizzleParam::Alpha) { - return VK_COMPONENT_SWIZZLE_ONE; - } - return ToVkComponentSwizzle(swizzle); - } - - static VkComponentMapping ResolveSampledViewComponents(const MG_State::GLState::ITextureObject& texture, - const TextureFormatInfo& formatInfo) { - const auto& swizzles = texture.GetAllSwizzleParams(); - const Bool alphaIsImplicitOne = formatInfo.expandRgbToRgba; - VkComponentMapping components{ - ToVkSampledComponentSwizzle(swizzles.x(), alphaIsImplicitOne), - ToVkSampledComponentSwizzle(swizzles.y(), alphaIsImplicitOne), - ToVkSampledComponentSwizzle(swizzles.z(), alphaIsImplicitOne), - ToVkSampledComponentSwizzle(swizzles.w(), alphaIsImplicitOne), - }; - return components; - } - - static Bool TryResolveTextureShapeInfo(const MG_State::GLState::ITextureObject& texture, - TextureUploadTarget uploadTarget, const IntVec3& texelSize, - TextureShapeInfo& outShape) { - switch (uploadTarget) { - case TextureUploadTarget::Texture1D: - case TextureUploadTarget::ProxyTexture1D: - outShape = {}; - outShape.imageType = VK_IMAGE_TYPE_1D; - outShape.viewType = VK_IMAGE_VIEW_TYPE_1D; - return true; - case TextureUploadTarget::Texture1DArray: - case TextureUploadTarget::ProxyTexture1DArray: - MOBILEGL_ASSERT(texelSize.z() > 0, - "TryResolveTextureShapeInfo: invalid 1D array depth=%d for textureId=%d", - texelSize.z(), texture.GetExternalIndex()); - outShape.imageType = VK_IMAGE_TYPE_1D; - outShape.viewType = VK_IMAGE_VIEW_TYPE_1D_ARRAY; - outShape.depth = 1; - outShape.arrayLayers = static_cast(texelSize.z()); - return true; - case TextureUploadTarget::Texture2D: - case TextureUploadTarget::ProxyTexture2D: - case TextureUploadTarget::TextureRectangle: - case TextureUploadTarget::ProxyTextureRectangle: - outShape = {}; - return true; - case TextureUploadTarget::Texture2DMultisample: - case TextureUploadTarget::ProxyTexture2DMultisample: - outShape = {}; - return true; - case TextureUploadTarget::Texture2DArray: - case TextureUploadTarget::ProxyTexture2DArray: - MOBILEGL_ASSERT(texelSize.z() > 0, - "TryResolveTextureShapeInfo: invalid 2D array depth=%d for textureId=%d", - texelSize.z(), texture.GetExternalIndex()); - outShape.imageType = VK_IMAGE_TYPE_2D; - outShape.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; - outShape.depth = 1; - outShape.arrayLayers = static_cast(texelSize.z()); - return true; - case TextureUploadTarget::Texture2DMultisampleArray: - case TextureUploadTarget::ProxyTexture2DMultisampleArray: - MOBILEGL_ASSERT(texelSize.z() > 0, - "TryResolveTextureShapeInfo: invalid 2D multisample array depth=%d for textureId=%d", - texelSize.z(), texture.GetExternalIndex()); - outShape.imageType = VK_IMAGE_TYPE_2D; - outShape.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; - outShape.depth = 1; - outShape.arrayLayers = static_cast(texelSize.z()); - return true; - case TextureUploadTarget::Texture3D: - case TextureUploadTarget::ProxyTexture3D: - MOBILEGL_ASSERT(texelSize.z() > 0, - "TryResolveTextureShapeInfo: invalid 3D texture depth=%d for textureId=%d", - texelSize.z(), texture.GetExternalIndex()); - outShape.imageType = VK_IMAGE_TYPE_3D; - outShape.viewType = VK_IMAGE_VIEW_TYPE_3D; - outShape.depth = static_cast(texelSize.z()); - return true; - case TextureUploadTarget::CubeMapPositiveX: - case TextureUploadTarget::CubeMapNegativeX: - case TextureUploadTarget::CubeMapPositiveY: - case TextureUploadTarget::CubeMapNegativeY: - case TextureUploadTarget::CubeMapPositiveZ: - case TextureUploadTarget::CubeMapNegativeZ: - case TextureUploadTarget::ProxyCubeMap: - MOBILEGL_ASSERT(texture.GetTarget() == TextureTarget::TextureCubeMap, - "TryResolveTextureShapeInfo: cube upload target on non-cube textureId=%d target=%s", - texture.GetExternalIndex(), - MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str()); - MOBILEGL_ASSERT(texelSize.x() == texelSize.y(), - "TryResolveTextureShapeInfo: cube map textureId=%d is not square (%d x %d)", - texture.GetExternalIndex(), texelSize.x(), texelSize.y()); - outShape.imageType = VK_IMAGE_TYPE_2D; - outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE; - outShape.imageFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; - outShape.depth = 1; - outShape.arrayLayers = 6; - return true; - case TextureUploadTarget::CubeMapArray: - case TextureUploadTarget::ProxyCubeMapArray: - // GL_TEXTURE_CUBE_MAP_ARRAY is an array texture whose layers happen to be cube faces: - // one 2D image with arrayLayers = 6 * cubeCount, CUBE_COMPATIBLE so the whole thing can - // be sampled as a samplerCubeArray. glTexStorage3D hands the 6*n through as the GL depth - // and the upload path's depthSelectsArrayLayer already lists VK_IMAGE_VIEW_TYPE_CUBE_ARRAY, - // so the copies address layers correctly. - // - // A depth that is not a whole number of cubes, or a non-square level, has no Vulkan shape - // - declined the way every other unrepresentable target is. This function's Bool return - // exists for exactly that; asserting here would abort the process on ordinary application - // input, GL_PROXY_TEXTURE_CUBE_MAP_ARRAY above all. - if (texelSize.z() <= 0 || (texelSize.z() % 6) != 0 || texelSize.x() != texelSize.y()) { - return false; - } - outShape.imageType = VK_IMAGE_TYPE_2D; - outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; - outShape.imageFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; - outShape.depth = 1; - outShape.arrayLayers = static_cast(texelSize.z()); - return true; - default: - return false; - } - } - Bool VkTextureManager::Initialize(const InitInfo& initInfo) { Shutdown(); @@ -787,85 +635,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& textureOrView) { - MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureAndGetDescriptor: m_device == VK_NULL_HANDLE"); - - // A GL texture view has no image of its own; it resolves to - and shares - the resource - // of the texture whose storage it views, so that there is exactly one VkImage, one - // tracked layout and one upload path per storage. Everything that makes the view a - // different texture (format, level/layer window, sampled aspect) is applied where the - // VkImageViews are built, keyed in alternateSampledViews / attachmentViews. - MG_State::GLState::ITextureObject& texture = StorageTextureOf(textureOrView); - if (&texture != &textureOrView) { - NoteTextureViewImageRequirements(textureOrView, texture); - } - - const TextureIdentity identity = MakeTextureIdentity(&texture); - - // Cross-draw memo probe (see SyncedTextureMemoEntry): skips both map - // lookups and the (re)registration path for repeat-bound textures. - TextureResource* resourcePtr = nullptr; - for (Uint32 i = 0; i < kSyncedTextureMemoSize; ++i) { - const SyncedTextureMemoEntry& memo = m_syncedTextureMemo[i]; - if (memo.texture == &texture && memo.lifetimeId == identity.lifetimeId && - memo.eraseEpoch == m_resourceEraseEpoch) { - resourcePtr = memo.resource; - break; - } - } - - if (resourcePtr == nullptr) { - auto aliveIt = m_aliveObjects.find(identity); - if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) { - EraseTrackedTexture(aliveIt->first); - aliveIt = m_aliveObjects.end(); - } - - // Only (re)register and prune when this (texture, lifetime) pair is new: stale - // aliases can only come into existence through an address reuse, which by - // construction introduces a new identity. Doing this unconditionally made every - // sampled-texture sync scan the entire alive-texture map per draw. - if (aliveIt == m_aliveObjects.end()) { - WeakPtr aliveTexture; - const auto& liveTexture = MG_Pipe::gPipeInputs.GetTextureObject(texture.GetExternalIndex()); - if (liveTexture && liveTexture.get() == &texture) { - aliveTexture = liveTexture; - } else { - // The name lookup legally fails while the object is alive: the name was - // deleted with the texture still attached to an FBO (the attachment's - // SharedPtr keeps it alive), or the name was reused by a new texture, or - // this is a default texture object (name 0 lives outside the name map). - // Register through the object's own control block so the resource created - // below still participates in weak-expiry GC instead of becoming an - // orphan no reclamation path can reach until Shutdown. - aliveTexture = texture.weak_from_this(); - } - if (!aliveTexture.expired()) { - m_aliveObjects[identity] = Move(aliveTexture); - PruneStaleTextureAliases(&texture); - } - } - - auto it = m_textureResources.find(identity); - if (it == m_textureResources.end()) { - TextureResource initial{}; - auto [insertIt, _] = m_textureResources.emplace(identity, Move(initial)); - it = insertIt; - } - resourcePtr = &(it->second); - m_syncedTextureMemo[m_syncedTextureMemoNext] = - SyncedTextureMemoEntry{&texture, identity.lifetimeId, m_resourceEraseEpoch, resourcePtr}; - m_syncedTextureMemoNext = (m_syncedTextureMemoNext + 1) % kSyncedTextureMemoSize; - } - - if (!SyncTexture(texture, *resourcePtr)) { - MGLOG_D("%s: Syncing texture %d failed", __func__, texture.GetExternalIndex()); - return nullptr; - } - - return resourcePtr; - } - void VkTextureManager::StampTextureRecordingUse(MG_State::GLState::ITextureObject* texture) { if (texture == nullptr) { return; @@ -2453,545 +2222,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture, - TextureResource &outResource) { - // This is the FRONTEND-object sync: it reads - and clears - the client's mip shadow (the - // dirty scan below, UploadDirtyMipLevels' texel / region reads and MarkStorageDirty(false) - // clears), so it belongs to the monolith arm only. Under an active transport every wire - // verb syncs through the handle-keyed arm (SyncTextureResourceByHandle, and the sampled - // wire descriptors in UniformManager) from the applier's record and the staged-texture - // store. P5c's named exemption for this function (MGPipeTextureLegacyArmScope) was retired - // in P7 (ratchet88) once a probe proved it never armed on the apply thread; a regression - // now meets the layer-1 guard (Fatal{RoleViolation, "texture-legacy-arm"}, - // MipmapStorage.cpp) by accessor name. This block is exactly as many lines as the one it - // replaced: __LINE__ reaches this file's pull .text, and G1 compares the bytes. - // Cross-draw fast path: if the resource is already built and neither the texture's - // pixel content (bumped in MarkStorageDirty), its SHAPE (bumped in BumpShapeVersion) - // nor its params changed since the last sync, there is nothing to re-check or - // re-upload - skip CheckMipmapCompleteness, SyncTextureResource, SyncTextureViews and - // the per-level dirty scan. Layout is maintained separately by the transition path, so - // the resource still reflects truth. The shape version is NOT redundant with the - // content one: glTexImage2D(..., nullptr) re-specifies a level's size or format - // without dirtying a texel, which is exactly how a re-specified image-unit texture used - // to keep reporting its old imageSize(). - const Uint64 syncingContentVersion = texture.GetContentVersion(); - const Uint64 syncingShapeVersion = texture.GetShapeVersion(); - const auto* syncingMipTexture = MG_State::GLState::AsMipmapTexture(&texture); - const Uint32 syncingMipLevelCount = - syncingMipTexture != nullptr ? syncingMipTexture->GetMipmapLevelCount() : 0u; - // A pending storage-usage upgrade also has to bust the skip: nothing about the texture's - // content or params changed, but the image itself must be recreated with STORAGE usage - // before it can back an image-unit descriptor. - const Bool storageUpgradePending = - !outResource.storageUsageResolved && - m_storageImageTextures.find(MakeTextureIdentity(&texture)) != m_storageImageTextures.end(); - // Same shape for a GL texture view's demands on the image (MUTABLE_FORMAT for a - // format-reinterpreting view, CUBE_COMPATIBLE for a cube view of an array texture): - // nothing about the texture itself changed, but the live image cannot carry the view. - // Masked by what this format can actually be given: MUTABLE_FORMAT is deliberately - // withheld from formats the driver already refused it for (see SyncTextureResource), and - // without this mask the "upgrade still pending" test below could never come true again - - // costing every later sync of that texture the whole slow path, forever. - VkImageCreateFlags requestedViewFlags = GetViewRequestedImageFlags(texture); - if (m_mutableFormatUnsupported.find(outResource.format) != m_mutableFormatUnsupported.end()) { - requestedViewFlags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - const Bool viewFlagUpgradePending = - (outResource.imageCreateFlags & requestedViewFlags) != requestedViewFlags; - if (outResource.image != VK_NULL_HANDLE && !storageUpgradePending && !viewFlagUpgradePending && - outResource.syncedContentVersion == syncingContentVersion && - outResource.syncedShapeVersion == syncingShapeVersion && - outResource.syncedTextureParamsVersion == texture.GetTextureParamsVersion() && - outResource.syncedMipLevelCount == syncingMipLevelCount) { - return true; - } - - TextureUploadTarget uploadTarget = TextureUploadTarget::Unknown; - IntVec3 texelSize{0, 0, 0}; - SizeT byteSize = 0; - Uint32 mipLevelCount = 0; - if (!CheckMipmapCompleteness(texture, uploadTarget, texelSize, byteSize, mipLevelCount)) { - MGLOG_D("%s: mipmap not complete", __func__); - return false; - } - - auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture); - if (!mipTexture) { - MGLOG_D("%s: not TextureObjectMipmap", __func__); - return false; - } - - // From here down the size is VULKAN geometry, not GL's: a 1D array's layer count moves - // out of the height it occupies GL-side and into z, which is the slot - // TryResolveTextureShapeInfo reads arrayLayers from and the only one that leaves - // extent.height at the 1 a VK_IMAGE_TYPE_1D image is required to have. - texelSize = ToVulkanLevelExtent(texture.GetTarget(), texelSize); - - if (!SyncTextureResource(texture, uploadTarget, texelSize, byteSize, mipLevelCount, outResource)) { - MGLOG_D("%s: SyncTextureResource failed", __func__); - return false; - } - if (!SyncTextureViews(texture, outResource)) { - MGLOG_D("%s: SyncTextureViews failed", __func__); - return false; - } - - Vector dirtyTargets; - if (outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) { - dirtyTargets = mipTexture->GetUploadTargets(); - } else { - dirtyTargets.push_back(uploadTarget); - } - Bool hasDirtyMipLevel = false; - for (const TextureUploadTarget target : dirtyTargets) { - const Uint32 targetMipLevelCount = std::min(mipLevelCount, GetUploadMipLevelCount(*mipTexture, target)); - for (Uint32 level = 0; level < targetMipLevelCount; ++level) { - if (mipTexture->IsStorageDirty(target, level)) { - hasDirtyMipLevel = true; - break; - } - } - if (hasDirtyMipLevel) { - break; - } - } - if (!hasDirtyMipLevel) { - outResource.syncedContentVersion = syncingContentVersion; - outResource.syncedMipLevelCount = syncingMipLevelCount; - outResource.syncedShapeVersion = syncingShapeVersion; - return true; - } - - if (!UploadDirtyMipLevels(*mipTexture, uploadTarget, outResource)) { - MGLOG_D("%s: UploadDirtyMipLevels failed", __func__); - return false; - } - outResource.syncedContentVersion = syncingContentVersion; - outResource.syncedMipLevelCount = syncingMipLevelCount; - outResource.syncedShapeVersion = syncingShapeVersion; - return true; - } - - Bool VkTextureManager::SyncTextureResource(const MG_State::GLState::ITextureObject &texture, - TextureUploadTarget uploadTarget, - const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels, - TextureResource &resource) { - const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); - VkFormat format = formatInfo.format; - if (format == VK_FORMAT_UNDEFINED) { - MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__); - return false; - } - // X8_D24 lacks optimal-tiling support on several drivers (lavapipe included); - // D32_SFLOAT holds every 24-bit depth value exactly, and the upload path - // converts the shadow words to float (see the pure-depth branch below). - if (format == VK_FORMAT_X8_D24_UNORM_PACK32) { - VkFormatProperties formatProperties{}; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); - constexpr VkFormatFeatureFlags kDepthAttachmentAndSample = - VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; - if ((formatProperties.optimalTilingFeatures & kDepthAttachmentAndSample) != kDepthAttachmentAndSample) { - format = VK_FORMAT_D32_SFLOAT; - } - } - if (texelSize.x() <= 0 || texelSize.y() <= 0 /*|| byteSize == 0*/) { - MGLOG_D("%s: texelSize or byteSize is zero", __func__); - return false; - } - if (mipLevels == 0) { - MGLOG_D("%s: no mip levels", __func__); - return false; - } - const Bool isMultisampleTexture = IsMultisampleTextureUploadTarget(uploadTarget); - // A texture that has only ever defined level 0 gets a single-level backing - // (ANGLE's model). Preallocating the full chain put every render target - // onto Adreno's multi-mip image layout and grew each texture by a third - // for levels most textures never define. Once a second level is defined - // the backing is recreated ONE time with the full chain (the - // preserve-copy path below carries the pixels over), so sequentially- - // defined atlas mips do not recreate per level, and glGenerateMipmap - - // which defines every level before syncing - works unchanged. - TextureShapeInfo shapeInfo{}; - const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo); - // ComputeFullMipLevelCount takes max(x, y, z), and for every ARRAY shape z is the layer - // count, not a mip-able axis: a 4x4 array with 192 layers asked for 6 levels on an image - // whose legal maximum is 3 (VUID-VkImageCreateInfo-mipLevels-00958). Only the image's own - // extent - width, height and shapeInfo.depth, which is 1 for every array - can bound it. - // lavapipe has been letting this through unvalidated; a strict driver would not. - const IntVec3 mipExtent{texelSize.x(), texelSize.y(), static_cast(shapeInfo.depth)}; - const Uint32 fullMipLevels = ComputeFullMipLevelCount(mipExtent); - const Uint32 backingMipLevels = - isMultisampleTexture ? 1u : (mipLevels > 1 ? std::min(std::max(mipLevels, fullMipLevels), fullMipLevels) : 1u); - if (!supportedShape) { - // A gap in this backend's coverage, not a broken invariant: the GL front end accepts - // targets this manager has no Vulkan image shape for yet (cube map arrays above all). - // Declining the sync leaves the texture unbacked - wrong, but recoverable - where an - // assertion would take the whole process down instead. - MGLOG_W_ONCE("SyncTextureResource: unsupported uploadTarget=%s textureTarget=%s textureId=%d size=(%d,%d,%d) " - "mipLevels=%u vkViewType=%d", - MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), - MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str(), texture.GetExternalIndex(), - texelSize.x(), texelSize.y(), texelSize.z(), mipLevels, - static_cast(MG_Util::ConvertTextureUploadTargetToVkEnum(uploadTarget))); - return false; - } - VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT; - if (isMultisampleTexture && - !TryResolveSampleCountFlagBits(texture.GetSamples(), resolvedSampleCount)) { - MGLOG_D("%s: unsupported multisample count=%d for textureId=%d target=%s", __func__, - texture.GetSamples(), texture.GetExternalIndex(), - MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str()); - return false; - } - // glTexStorage*Multisample(samples = 1) is legal GL, but a one-sample image cannot back a - // sampler2DMS: VUID-RuntimeSpirv-samples-08726 forbids an OpTypeImage with MS = 1 from - // reading an image created with VK_SAMPLE_COUNT_1_BIT, and the fetch returns undefined data - // rather than an error. GL only promises "at least the requested number of samples", so - // giving a multisample texture two is both legal and the only way to keep the shader's view - // of it honest. GL_TEXTURE_SAMPLES still reports what the application asked for - that is - // read off the texture object, not off the image. - if (isMultisampleTexture && resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT) { - resolvedSampleCount = VK_SAMPLE_COUNT_2_BIT; - } - - const VkImageAspectFlags aspect = GetAspectMaskForFormat(format); - VkFormatProperties formatProperties{}; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); - // Only textures that have actually been bound to a GL image unit get STORAGE usage (and - // the MUTABLE_FORMAT it drags in for format-reinterpreting image views). Requesting it - // for every storage-capable colour texture costs real bandwidth: Adreno cannot keep UBWC - // compression on an image that may be written through a storage descriptor, so the whole - // render target - MC's included - runs uncompressed. MarkStorageImageTexture upgrades a - // texture before its first image-unit draw, and the usage below feeds the compatibility - // check so the upgrade recreates the image. - const Bool markedAsStorageImage = - m_storageImageTextures.find(MakeTextureIdentity( - const_cast(&texture))) != m_storageImageTextures.end(); - // Storage-image CAPABILITY (does the format allow it at all) is deliberately separate from - // whether this texture actually needs the usage. MUTABLE_FORMAT keys off capability, as - // before: format-reinterpreting views are not a storage-only concern - the SAMPLED path - // needs them too (GetOrCreateSampledImageView bails out without it, see ~line 892), so - // tying MUTABLE_FORMAT to the image-unit mark would break sampled format reinterpretation - // for every texture that never becomes a storage image. - const Bool storageImageCapable = - !isMultisampleTexture && - (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && - (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; - const Bool supportsStorageImage = storageImageCapable && markedAsStorageImage; - VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags; - // One z slice of a 3D texture can only be attached to a framebuffer through a 2D view over - // it, which needs the image to be 2D-array-compatible (Vulkan 1.1 core, promoted from - // VK_KHR_maintenance1). Asked for optimistically and withdrawn per format below if the - // driver refuses - losing it only costs per-slice attachment, while failing creation would - // lose the texture entirely. - if (shapeInfo.imageType == VK_IMAGE_TYPE_3D && !isMultisampleTexture && - m_2dArrayCompatibleUnsupported.find(format) == m_2dArrayCompatibleUnsupported.end()) { - imageCreateFlags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; - } - if (storageImageCapable && IsMutableStorageImageFormat(format) && - m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) { - imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - // Flags a GL texture view over this storage asked for (see NoteTextureViewImageRequirements). - // MUTABLE_FORMAT is still withheld from formats the driver has already refused it for, so a - // reinterpreting view degrades to no view rather than to no texture. - const VkImageCreateFlags requestedViewFlags = GetViewRequestedImageFlags(texture); - if (requestedViewFlags != 0) { - imageCreateFlags |= requestedViewFlags; - if (m_mutableFormatUnsupported.find(format) != m_mutableFormatUnsupported.end()) { - imageCreateFlags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - } - // sRGB color images attach through their UNORM twin while GL_FRAMEBUFFER_SRGB is - // disabled (see ResolveSrgbAttachmentWriteFormat), which needs format-reinterpreting - // views - multisample sRGB render targets included. - if (ResolveSrgbAttachmentWriteFormat(format, false) != format && - (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && - m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) { - imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - - VkImageUsageFlags desiredUsage = - ResolveTextureUsage(aspect, formatProperties.optimalTilingFeatures, supportsStorageImage); - if (!isMultisampleTexture) { - desiredUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - } - - // Round a multisample request up to a count the device supports for this - // format (GL only promises "at least"), mirroring the renderbuffer path. - if (isMultisampleTexture && resolvedSampleCount != VK_SAMPLE_COUNT_1_BIT) { - auto supportedIt = m_multisampleCountsByFormat.find(format); - if (supportedIt == m_multisampleCountsByFormat.end()) { - VkImageFormatProperties imageFormatProperties{}; - VkSampleCountFlags supported = VK_SAMPLE_COUNT_1_BIT; - if (vkGetPhysicalDeviceImageFormatProperties(m_physicalDevice, format, shapeInfo.imageType, - VK_IMAGE_TILING_OPTIMAL, desiredUsage, imageCreateFlags, - &imageFormatProperties) == VK_SUCCESS) { - supported = imageFormatProperties.sampleCounts; - } - supportedIt = m_multisampleCountsByFormat.emplace(format, supported).first; - } - const VkSampleCountFlags supported = supportedIt->second; - if ((supported & resolvedSampleCount) == 0) { - Uint32 rounded = 0; - for (Uint32 bit = static_cast(resolvedSampleCount) << 1; bit <= VK_SAMPLE_COUNT_64_BIT; - bit <<= 1) { - if ((supported & bit) != 0) { - rounded = bit; - break; - } - } - if (rounded == 0) { - // Never land on one sample: that is the VUID-RuntimeSpirv-samples-08726 - // violation the floor above exists to avoid, and it would come back silently - // for any format whose only supported count is 1. - for (Uint32 bit = static_cast(resolvedSampleCount) >> 1; - bit > static_cast(VK_SAMPLE_COUNT_1_BIT); bit >>= 1) { - if ((supported & bit) != 0) { - rounded = bit; - break; - } - } - } - if (rounded == 0 && (supported & VK_SAMPLE_COUNT_1_BIT) != 0) { - // Nothing at two samples or above. Reachable because the frontend validates - // multisample allocations against the count MobileGL ADVERTISES (GL requires - // GL_MAX_SAMPLES >= 4) rather than against the device's per-format support, so - // a format this device cannot multisample at all now gets here instead of - // being refused up front. Keeping the unsupported count would hand - // vkCreateImage an invalid VkImageCreateInfo; one sample is at least a legal - // image, and the samples-08726 hazard above is the lesser of the two. - MGLOG_W_ONCE("Multisample texture format %d supports no count above one on this device; " - "backing it with a single sample", - static_cast(format)); - rounded = static_cast(VK_SAMPLE_COUNT_1_BIT); - } - if (rounded != 0) { - resolvedSampleCount = static_cast(rounded); - } - } - } - - const Bool compatible = 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.sampleCount == resolvedSampleCount && - resource.imageCreateFlags == imageCreateFlags && - resource.usageFlags == desiredUsage && - resource.mipLevels == backingMipLevels; - if (compatible) { - if (resource.perMipViews.size() != backingMipLevels) { - resource.perMipViews.resize(backingMipLevels, VK_NULL_HANDLE); - } - if (resource.perMipSampledViews.size() != backingMipLevels) { - resource.perMipSampledViews.resize(backingMipLevels, VK_NULL_HANDLE); - } - // Keeping the image is itself the answer to the mark: either it already carries - // STORAGE, or this format can never carry it. Either way there is nothing left to - // recreate, so stop reporting the texture as needing preparation. - resource.storageUsageResolved = markedAsStorageImage; - return true; - } - - 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.sampleCount == resolvedSampleCount && - resource.imageCreateFlags == imageCreateFlags && - resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT && - // '<=' rather than '<': a storage-usage upgrade recreates the image with an - // unchanged mip count, and its contents (a render target's pixels live only on the - // GPU) still have to survive. The vkCmdCopyImage below copies min(mipLevels). - resource.mipLevels <= backingMipLevels && - resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; - - std::unique_ptr preservedResource; - if (preserveExistingContent) { - preservedResource = std::make_unique(Move(resource)); - } else { - DeferResourceRelease(Move(resource)); - } - - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.flags = imageCreateFlags; - imageInfo.imageType = shapeInfo.imageType; - imageInfo.extent.width = static_cast(texelSize.x()); - imageInfo.extent.height = static_cast(texelSize.y()); - imageInfo.extent.depth = shapeInfo.depth; - imageInfo.mipLevels = backingMipLevels; - imageInfo.arrayLayers = shapeInfo.arrayLayers; - imageInfo.format = format; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = desiredUsage; - imageInfo.samples = resolvedSampleCount; - - // Bound the mutability. A blindly-mutable image has to be laid out so that ANY format in - // its compatibility class can be viewed, which costs bandwidth compression on tilers; - // naming the exact set instead lets the driver keep it. Only safe when that set really is - // exhaustive, so it is restricted to textures that are not image-unit bound: sampled views - // can only ever ask for ResolveSampledImageViewFormat's output, whereas glBindImageTexture - // may name any compatible format, which nothing here can enumerate ahead of time. - Vector viewFormats; - VkImageFormatListCreateInfo formatListInfo{}; - if (m_imageFormatListSupported && !supportsStorageImage && - (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) { - viewFormats.push_back(format); - for (const SamplerNumericDomain domain : {SamplerNumericDomain::Float, - SamplerNumericDomain::SignedInteger, - SamplerNumericDomain::UnsignedInteger}) { - const VkFormat viewFormat = ResolveSampledImageViewFormat(format, domain); - if (viewFormat == VK_FORMAT_UNDEFINED) { - continue; - } - if (std::find(viewFormats.begin(), viewFormats.end(), viewFormat) == viewFormats.end()) { - viewFormats.push_back(viewFormat); - } - } - // ...plus every format a glTextureView over this storage reinterprets it as. Those - // are NOT enumerable from ResolveSampledImageViewFormat - an application may name any - // member of the format's view class (GL 4.6 core table 8.21) - so without this the - // list would forbid the very view the MUTABLE_FORMAT bit was requested for. - AppendViewRequestedFormats(texture, viewFormats); - formatListInfo.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO; - formatListInfo.viewFormatCount = static_cast(viewFormats.size()); - formatListInfo.pViewFormats = viewFormats.data(); - imageInfo.pNext = &formatListInfo; - } - - if (isMultisampleTexture || (imageInfo.flags & (VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | - VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT)) != 0) { - VkImageFormatProperties imageFormatProperties{}; - VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( - m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, - imageInfo.flags, &imageFormatProperties); - if (imageFormatResult != VK_SUCCESS && !isMultisampleTexture && - (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) { - // Losing reinterpreted views only degrades the formatless-image feature for - // this texture; failing creation would lose the texture entirely, so retry - // as a plain immutable-format image. - MGLOG_W_ONCE("%s: mutable image format=%d is unsupported for textureId=%d; creating " - "without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT (format reinterpretation " - "will be unavailable for it)", - __func__, static_cast(format), texture.GetExternalIndex()); - // Remember the verdict so later syncs of same-format textures neither retry - // the probe nor flag-mismatch against this image and recreate it. - m_mutableFormatUnsupported.insert(format); - imageInfo.flags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - imageCreateFlags = imageInfo.flags; - imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( - m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, - imageInfo.flags, &imageFormatProperties); - } - if (imageFormatResult != VK_SUCCESS && !isMultisampleTexture && - (imageInfo.flags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0) { - // Losing 2D-array compatibility only costs framebuffer attachment of this format's - // 3D images - per-slice AND layered, since both are spelled as a 2D-family view over - // the z axis; failing creation would lose the texture entirely. Recorded here (the - // per-format set below) so later syncs neither reprobe nor flag-mismatch against this - // image and recreate it, and so GetOrCreateAttachmentViewAtMipLevel declines rather - // than handing back a view that cannot exist - the render-pass builder then turns - // that decline into a skipped draw instead of a null VkImageView in pAttachments. - MGLOG_W_ONCE("%s: VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT is unsupported for format=%d " - "textureId=%d; creating without it (per-slice and layered framebuffer " - "attachment of 3D textures in this format will be unavailable)", - __func__, static_cast(format), texture.GetExternalIndex()); - m_2dArrayCompatibleUnsupported.insert(format); - imageInfo.flags &= ~VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; - imageCreateFlags = imageInfo.flags; - imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( - m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, - imageInfo.flags, &imageFormatProperties); - } - if (imageFormatResult != VK_SUCCESS || - (isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) { - MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s " - "format=%d usage=0x%x", - __func__, static_cast(imageInfo.flags), texture.GetSamples(), - texture.GetExternalIndex(), - MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), - static_cast(format), static_cast(imageInfo.usage)); - // The preserved image was written by GPU work that may still be in flight - // (preserve requires layout != UNDEFINED); park it on the deferred ring - // like every other destruction path instead of letting the unique_ptr - // destroy it synchronously under the GPU. - if (preservedResource) { - DeferResourceRelease(Move(*preservedResource)); - } - return false; - } - } - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - VmaAllocationCreateInfo allocationInfo{}; - allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - // Soft failure like the unsupported-sample-count path above: a driver can pass the - // vkGetPhysicalDeviceImageFormatProperties pre-check yet still refuse the creation - // (e.g. multisampled depth on lavapipe); the texture simply stays unbacked. - const VkResult createImageResult = - vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr); - if (createImageResult != VK_SUCCESS) { - // E_ONCE, not F: the comment above says it - this is a soft failure the caller - // recovers from, and it re-fires on every sync of every texture the driver refuses. - MGLOG_E_ONCE("SyncTextureResource: vmaCreateImage failed (%d) textureId=%d extent=%ux%u depth=%u layers=%u " - "mips=%u samples=%d format=%d", - createImageResult, texture.GetExternalIndex(), imageInfo.extent.width, imageInfo.extent.height, - imageInfo.extent.depth, imageInfo.arrayLayers, imageInfo.mipLevels, - static_cast(imageInfo.samples), static_cast(imageInfo.format)); - resource.image = VK_NULL_HANDLE; - resource.allocation = nullptr; - // Same as the probe failure above: the preserved live image must go through - // the deferred ring, never a synchronous destructor while frames that - // reference it are still in flight. - if (preservedResource) { - DeferResourceRelease(Move(*preservedResource)); - } - return false; - } - ++m_textureImageEpoch; // a new attachment image invalidates cached render passes - - resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; - resource.extent = {static_cast(texelSize.x()), static_cast(texelSize.y())}; - resource.depth = shapeInfo.depth; - resource.arrayLayers = shapeInfo.arrayLayers; - 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; - resource.aspect = aspect; - resource.viewType = shapeInfo.viewType; - resource.sampleCount = resolvedSampleCount; - resource.imageCreateFlags = imageCreateFlags; - resource.usageFlags = imageInfo.usage; - resource.storageUsageResolved = markedAsStorageImage; - resource.syncedTextureParamsVersion = 0; - - if (preservedResource) { - // The preserve copy reads the OLD image on its own immediately- - // submitted-and-waited command buffer; a batched upload into that - // image still sitting in the open batch must reach the queue first - // or the copy carries pre-upload texels forward. - FlushPendingUploads(); - 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"); - DeferResourceRelease(Move(*preservedResource)); - } - return true; - } - void VkTextureManager::DeferResourceRelease(TextureResource&& resource) { // The deferred-release queues are drained under fence/queue-idle proofs // that only cover SUBMITTED work; a recorded-but-unsubmitted upload @@ -3285,803 +2515,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { 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); - } - MG_State::GLState::ITextureObject& VkTextureManager::StorageTextureOf( MG_State::GLState::ITextureObject& texture) { const auto& storageOwner = texture.GetViewStorageOwner(); return storageOwner ? *storageOwner : texture; } - // The extra VkImageCreateFlags a GL texture view needs on the image it views. Recorded - // BEFORE the storage texture is synced (see SyncTextureAndGetDescriptor) so the very first - // resolve of a view already creates - or recreates and copies forward - an image the view can - // legally be built over, instead of handing back VK_NULL_HANDLE for a frame. - void VkTextureManager::NoteTextureViewImageRequirements(MG_State::GLState::ITextureObject& viewTexture, - MG_State::GLState::ITextureObject& storageTexture) { - const TextureIdentity storageIdentity = MakeTextureIdentity(&storageTexture); - VkImageCreateFlags required = 0; - const VkFormat viewFormat = ResolveTextureFormatInfo(viewTexture.GetFormat()).format; - const VkFormat storageFormat = ResolveTextureFormatInfo(storageTexture.GetFormat()).format; - if (viewFormat != VK_FORMAT_UNDEFINED && storageFormat != VK_FORMAT_UNDEFINED && - viewFormat != storageFormat) { - required |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - // The image may be created with a NARROWED format list (see SyncTextureResource), and - // that list is a promise about every format the image will ever be viewed as. Record - // this one so the promise stays true. - m_viewRequestedFormats[storageIdentity].insert(viewFormat); - } - const TextureTarget viewTarget = viewTexture.GetTarget(); - if (viewTarget == TextureTarget::TextureCubeMap || viewTarget == TextureTarget::TextureCubeMapArray) { - // Only when the storage could legally carry the bit. VK_IMAGE_CREATE_CUBE_COMPATIBLE - // demands a 2D image with square levels and at least six array layers - // (VUID-VkImageCreateInfo-flags-00954), and asking for it on a storage that has fewer - // would fail vkCreateImage - which, because SyncTextureResource has already released - // the old resource by then, would leave the PARENT texture with no image at all. A - // degenerate view must not be able to destroy the texture it views; let its own view - // creation fail instead. - const IntVec3 storageSize = storageTexture.GetBaseSize(); - const Bool storageCanBeCube = storageSize.x() == storageSize.y() && - storageTexture.GetViewNumLayers() >= 6 && - storageTexture.GetTarget() != TextureTarget::Texture3D; - if (storageCanBeCube) { - required |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; - } else { - MGLOG_W_ONCE("Texture view %d wants a cube view of texture %d, whose storage is %dx%d with %u " - "layers and cannot be cube-compatible; the view will have no image view.", - viewTexture.GetExternalIndex(), storageTexture.GetExternalIndex(), storageSize.x(), - storageSize.y(), storageTexture.GetViewNumLayers()); - } - } - if (required == 0) { - return; - } - VkImageCreateFlags& stored = m_viewRequestedImageFlags[storageIdentity]; - stored |= required; - } - - VkImageCreateFlags VkTextureManager::GetViewRequestedImageFlags( - const MG_State::GLState::ITextureObject& storageTexture) const { - const auto it = m_viewRequestedImageFlags.find( - MakeTextureIdentity(const_cast(&storageTexture))); - return it == m_viewRequestedImageFlags.end() ? 0 : it->second; - } - - void VkTextureManager::AppendViewRequestedFormats(const MG_State::GLState::ITextureObject& storageTexture, - Vector& outFormats) const { - const auto it = m_viewRequestedFormats.find( - MakeTextureIdentity(const_cast(&storageTexture))); - if (it == m_viewRequestedFormats.end()) { - return; - } - for (const VkFormat viewFormat : it->second) { - if (std::find(outFormats.begin(), outFormats.end(), viewFormat) == outFormats.end()) { - outFormats.push_back(viewFormat); - } - } - } - - Bool VkTextureManager::SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource) { - MOBILEGL_ASSERT(resource.image != VK_NULL_HANDLE, "SyncTextureViews: image == VK_NULL_HANDLE"); - - Uint32 baseMipLevel = 0; - Uint32 levelCount = 1; - ResolveViewMipRange(texture, resource.mipLevels, baseMipLevel, levelCount); - - const Bool needsRecreate = - resource.fullView == VK_NULL_HANDLE || - resource.sampledView == VK_NULL_HANDLE || - resource.sampledBaseMipLevel != baseMipLevel || - resource.sampledLevelCount != levelCount || - resource.syncedTextureParamsVersion != texture.GetTextureParamsVersion(); - if (!needsRecreate) { - return true; - } - - if (resource.fullView != VK_NULL_HANDLE) { - DeferViewRelease(resource.fullView); - resource.fullView = VK_NULL_HANDLE; - } - if (resource.sampledView != VK_NULL_HANDLE) { - DeferViewRelease(resource.sampledView); - resource.sampledView = VK_NULL_HANDLE; - } - for (auto& sampledView : resource.perMipSampledViews) { - if (sampledView != VK_NULL_HANDLE) { - DeferViewRelease(sampledView); - sampledView = VK_NULL_HANDLE; - } - } - for (const auto& [_, sampledView] : resource.alternateSampledViews) { - DeferViewRelease(sampledView); - } - resource.alternateSampledViews.clear(); - - const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); - const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo); - resource.fullView = CreateImageView(resource.image, resource.format, resource.aspect, resource.viewType, - baseMipLevel, levelCount, 0, resource.arrayLayers, &sampledComponents); - if (resource.fullView == VK_NULL_HANDLE) { - return false; - } - const VkImageAspectFlags sampledAspect = - ResolveSampledImageViewAspectMask(resource.aspect, texture.GetDepthStencilTextureMode()); - resource.sampledView = CreateImageView(resource.image, resource.format, sampledAspect, resource.viewType, - baseMipLevel, levelCount, 0, resource.arrayLayers, &sampledComponents); - if (resource.sampledView == VK_NULL_HANDLE) { - return false; - } - - resource.sampledBaseMipLevel = baseMipLevel; - resource.sampledLevelCount = levelCount; - resource.syncedTextureParamsVersion = texture.GetTextureParamsVersion(); - return true; - } - - VkImageView VkTextureManager::CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, - VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount, - Uint32 baseArrayLayer, - Uint32 layerCount, - const VkComponentMapping* components, - VkImageUsageFlags viewUsage) const { - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = image; - viewInfo.viewType = viewType; - viewInfo.format = format; - viewInfo.components = components != nullptr ? - *components : - VkComponentMapping{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, - VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; - viewInfo.subresourceRange.aspectMask = aspect; - viewInfo.subresourceRange.baseMipLevel = baseMipLevel; - viewInfo.subresourceRange.levelCount = levelCount; - viewInfo.subresourceRange.baseArrayLayer = baseArrayLayer; - viewInfo.subresourceRange.layerCount = layerCount; - - VkImageViewUsageCreateInfo usageInfo{}; - if (viewUsage != 0) { - usageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO; - usageInfo.usage = viewUsage; - viewInfo.pNext = &usageInfo; - } - - VkImageView view = VK_NULL_HANDLE; - VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view), "vkCreateImageView(texture)"); - return view; - } - - Bool VkTextureManager::UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, - TextureUploadTarget uploadTarget, - TextureResource &outResource) { - struct UploadItem { - TextureUploadTarget target = TextureUploadTarget::Unknown; - Uint32 level = 0; - Uint32 baseArrayLayer = 0; - SizeT uploadByteSize = 0; - IntVec3 texelSize = {0, 0, 0}; - const void* source = nullptr; - Vector expandedData; - VkDeviceSize offset = 0; - // Sub-region upload (a small sprite in a big atlas): only the dirty box - // is staged and copied. texelSize keeps the LEVEL extent - the staging - // row copy needs it for the stride. Color formats only (an RGB-expanded - // level is cut from its expanded bytes); the depth(+stencil) conversion - // passes rewrite whole levels and stay full-size. - Bool subRegion = false; - IntVec3 regionLo = {0, 0, 0}; - IntVec3 regionSize = {0, 0, 0}; - SizeT texelBytes = 0; - // The writes' exact footprint when it is more than one box (~100 sprites - // whose union box spans the whole atlas): each rect is staged tightly and - // copied with its own VkBufferImageCopy in ONE vkCmdCopyBufferToImage. - // Empty means "stage the one box above". Only set while subRegion. - Vector rects; - }; - - Vector uploadItems; - Vector targets; - if (outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) { - targets = mipmapTexture.GetUploadTargets(); - } else { - targets.push_back(uploadTarget); - } - const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(mipmapTexture.GetFormat()); - Vector dirtyFootprint; - - VkDeviceSize stagingSize = 0; - for (const TextureUploadTarget target : targets) { - const Uint32 definedMipLevels = GetUploadMipLevelCount(mipmapTexture, target); - MOBILEGL_ASSERT(definedMipLevels <= outResource.mipLevels, - "UploadDirtyMipLevels: defined mip level count %u exceeds backing mip level count %u for textureId=%d target=%s", - definedMipLevels, outResource.mipLevels, mipmapTexture.GetExternalIndex(), - MG_Util::ConvertTextureUploadTargetToString(target).c_str()); - - for (Uint32 level = 0; level < definedMipLevels; ++level) { - if (!mipmapTexture.IsStorageDirty(target, level)) { - continue; - } - - const auto texelSize = mipmapTexture.GetMipmapTexelSize(target, level); - const auto byteSize = mipmapTexture.GetMipmapByteSize(target, level); - if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) { - mipmapTexture.MarkStorageDirty(target, level, false); - continue; - } - - const void* source = mipmapTexture.MapMipmapData(target, level); - if (source == nullptr) { - MGLOG_D("%s: MapmipmapData failed at target %s level %d", __func__, - MG_Util::ConvertTextureUploadTargetToString(target).c_str(), level); - return false; - } - - UploadItem uploadItem{}; - uploadItem.target = target; - uploadItem.level = level; - uploadItem.baseArrayLayer = ResolveUploadArrayLayer(target); - // Vulkan geometry, like the image this stages into (see SyncTexture): a 1D - // array's layers move from y to z, where the copy loop's depthSelectsArrayLayer - // branch turns them into layerCount. The shadow needs no repacking to follow - - // one layer of a 1D array IS one row of `width` texels, so the tight-packed - // per-layer copy the swapped size describes reads the same bytes in the same - // order as the row-major level it replaces. - uploadItem.texelSize = ToVulkanLevelExtent(mipmapTexture.GetTarget(), texelSize); - uploadItem.source = source; - uploadItem.offset = stagingSize; - uploadItem.uploadByteSize = byteSize; - // The RGB -> RGBA expansion rewrites the level texel for texel into a tight buffer - // of its own, so it runs first and the dirty region below is taken from whichever - // bytes are staged. Uploading an expanded level WHOLE put the shadow's stale texels - // over whatever the GPU had drawn into the rest of it. - if (formatInfo.expandRgbToRgba) { - const Bool expanded = ExpandRgbSourceToRgba(source, byteSize, texelSize, formatInfo, - uploadItem.expandedData); - MOBILEGL_ASSERT(expanded, - "UploadDirtyMipLevels: failed to expand RGB textureId=%d target=%s level=%u to RGBA staging data", - mipmapTexture.GetExternalIndex(), - MG_Util::ConvertTextureUploadTargetToString(target).c_str(), level); - uploadItem.uploadByteSize = uploadItem.expandedData.size(); - } - const SizeT levelBytes = uploadItem.uploadByteSize; - if (GetAspectMaskForFormat(outResource.format) == VK_IMAGE_ASPECT_COLOR_BIT) { - const auto region = mipmapTexture.GetStorageDirtyRegion(target, level); - const SizeT texelCount = static_cast(texelSize.x()) * - static_cast(texelSize.y()) * - static_cast(std::max(texelSize.z(), 1)); - // Exactly the texels the client wrote, never their union box or the storage's - // rect list (which merges touching writes into their bounding box, folds when - // full and is withheld past 3/4 of the box): the image may hold what the GPU - // drew or cleared anywhere else, and the shadow is stale there. Each box is one - // more VkBufferImageCopy in the same vkCmdCopyBufferToImage, which costs next - // to nothing, so there is no cheaper shape worth being wrong for. A union box - // that spans the level with gaps in it is not a whole-level write either. - mipmapTexture.GetStorageDirtyFootprint(target, level, dirtyFootprint); - if (!region.Empty() && (dirtyFootprint.size() >= 2 || !region.CoversWholeLevel(texelSize)) && - texelCount > 0 && levelBytes % texelCount == 0) { - uploadItem.subRegion = true; - uploadItem.regionLo = region.lo; - uploadItem.regionSize = {region.hi.x() - region.lo.x(), region.hi.y() - region.lo.y(), - region.hi.z() - region.lo.z()}; - uploadItem.texelBytes = levelBytes / texelCount; - uploadItem.uploadByteSize = static_cast(uploadItem.regionSize.x()) * - static_cast(uploadItem.regionSize.y()) * - static_cast(uploadItem.regionSize.z()) * - uploadItem.texelBytes; - // Several boxes: each is staged tightly and copied on its own. One box is - // the union box above. - if (dirtyFootprint.size() >= 2) { - uploadItem.rects.assign(dirtyFootprint.begin(), dirtyFootprint.end()); - SizeT rectTexels = 0; - for (const auto& rect : uploadItem.rects) { - rectTexels += rect.TexelCount(); - } - uploadItem.uploadByteSize = rectTexels * uploadItem.texelBytes; - } - // The boxes came out of the shadow in GL coordinates, where a 1D - // array's layer is the y. They have to follow texelSize across to z or - // they would address rows of an image that now has exactly one, and - // the staging walk would read the wrong bytes for them. Every byte - // count computed above is a product of the three extents, so moving - // the axes leaves all of them alone - and an OFFSET lands on a zero y, - // not on the extent's one, which is why this is spelled out rather than - // handed to ToVulkanLevelExtent. - if (mipmapTexture.GetTarget() == TextureTarget::Texture1DArray) { - uploadItem.regionLo = {uploadItem.regionLo.x(), 0, uploadItem.regionLo.y()}; - uploadItem.regionSize = {uploadItem.regionSize.x(), 1, - uploadItem.regionSize.y()}; - for (auto& rect : uploadItem.rects) { - rect.lo = {rect.lo.x(), 0, rect.lo.y()}; - rect.hi = {rect.hi.x(), 1, rect.hi.y()}; - } - } - } - } - uploadItems.push_back(Move(uploadItem)); - if (!uploadItems.back().expandedData.empty()) { - uploadItems.back().source = uploadItems.back().expandedData.data(); - } - stagingSize += static_cast(uploadItems.back().uploadByteSize); - } - } - - if (uploadItems.empty()) { - return true; - } - - // Combined depth-stencil images need per-aspect copies (VkBufferImageCopy aspectMask - // must have exactly one bit set), so de-interleave the shadow's GL wire format into - // a depth plane followed by a stencil plane per upload item. - const VkImageAspectFlags uploadAspectMask = GetAspectMaskForFormat(outResource.format); - const Bool isCombinedDepthStencil = - (uploadAspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) && (uploadAspectMask & VK_IMAGE_ASPECT_STENCIL_BIT); - if (isCombinedDepthStencil) { - const Bool srcIsD24S8 = outResource.format == VK_FORMAT_D24_UNORM_S8_UINT; - const Bool srcIsD32FS8 = outResource.format == VK_FORMAT_D32_SFLOAT_S8_UINT; - if (!srcIsD24S8 && !srcIsD32FS8) { - MGLOG_E_ONCE("UploadDirtyMipLevels: unsupported combined depth-stencil format %d for textureId=%d", - static_cast(outResource.format), mipmapTexture.GetExternalIndex()); - for (const auto& item : uploadItems) { - mipmapTexture.MarkStorageDirty(item.target, item.level, false); - } - return true; - } - stagingSize = 0; - for (auto& item : uploadItems) { - const SizeT texelCount = static_cast(item.texelSize.x()) * - static_cast(item.texelSize.y()) * - static_cast(std::max(item.texelSize.z(), 1)); - const SizeT shadowTexelSize = item.uploadByteSize / std::max(texelCount, 1); - MOBILEGL_ASSERT(shadowTexelSize == 4 || shadowTexelSize == 8, - "UploadDirtyMipLevels: unexpected depth-stencil shadow texel size %zu for textureId=%d", - shadowTexelSize, mipmapTexture.GetExternalIndex()); - // Depth plane as the aspect's buffer-copy format (32-bit word for - // D24: low 24 bits; float for D32F), then one stencil byte per texel. - Vector deinterleaved(texelCount * 4 + texelCount); - Uint8* depthPlane = deinterleaved.data(); - Uint8* stencilPlane = deinterleaved.data() + texelCount * 4; - const Uint8* shadow = static_cast(item.source); - for (SizeT t = 0; t < texelCount; ++t) { - if (shadowTexelSize == 8) { - // GL_FLOAT_32_UNSIGNED_INT_24_8_REV: float depth, then a word - // with stencil in its low 8 bits. - float depthValue; - Uint32 stencilWord; - std::memcpy(&depthValue, shadow + t * 8, sizeof(depthValue)); - std::memcpy(&stencilWord, shadow + t * 8 + 4, sizeof(stencilWord)); - if (srcIsD32FS8) { - std::memcpy(depthPlane + t * 4, &depthValue, sizeof(depthValue)); - } else { - const float clamped = std::min(std::max(depthValue, 0.0f), 1.0f); - const Uint32 depthWord = static_cast(clamped * 16777215.0f + 0.5f); - std::memcpy(depthPlane + t * 4, &depthWord, sizeof(depthWord)); - } - stencilPlane[t] = static_cast(stencilWord & 0xFFu); - } else { - // GL_UNSIGNED_INT_24_8: depth in the high 24 bits, stencil low 8. - Uint32 packed; - std::memcpy(&packed, shadow + t * 4, sizeof(packed)); - if (srcIsD24S8) { - const Uint32 depthWord = packed >> 8; - std::memcpy(depthPlane + t * 4, &depthWord, sizeof(depthWord)); - } else { - const float depthValue = static_cast(packed >> 8) / 16777215.0f; - std::memcpy(depthPlane + t * 4, &depthValue, sizeof(depthValue)); - } - stencilPlane[t] = static_cast(packed & 0xFFu); - } - } - item.expandedData = Move(deinterleaved); - item.source = item.expandedData.data(); - item.uploadByteSize = item.expandedData.size(); - item.offset = stagingSize; - stagingSize += static_cast(item.uploadByteSize); - } - } - - // Pure-depth images whose canonical shadow layout differs from the image texel - // layout (the shadow keeps a full-scale 16/32-bit unorm word or a float; the - // image may be X8_D24 or a D32_SFLOAT fallback) convert per texel here. - if (uploadAspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) { - const TextureInternalFormat depthInternal = mipmapTexture.GetFormat(); - const Bool shadowIsFloat = depthInternal == TextureInternalFormat::DepthComponent32F; - const Bool dstIsFloat = outResource.format == VK_FORMAT_D32_SFLOAT; - const Bool dstIsD24Word = outResource.format == VK_FORMAT_X8_D24_UNORM_PACK32; - stagingSize = 0; - for (auto& item : uploadItems) { - const SizeT texelCount = static_cast(item.texelSize.x()) * - static_cast(item.texelSize.y()) * - static_cast(std::max(item.texelSize.z(), 1)); - const SizeT shadowTexelSize = item.uploadByteSize / std::max(texelCount, 1); - const Bool needsConversion = - (dstIsFloat && !shadowIsFloat) || (dstIsD24Word && shadowTexelSize == 4 && !shadowIsFloat); - if (needsConversion) { - Vector converted(texelCount * 4); - const Uint8* shadow = static_cast(item.source); - for (SizeT t = 0; t < texelCount; ++t) { - Uint32 wide = 0; - if (shadowTexelSize == 2) { - Uint16 raw = 0; - std::memcpy(&raw, shadow + t * 2, sizeof(raw)); - wide = (static_cast(raw) << 16) | raw; - } else { - std::memcpy(&wide, shadow + t * 4, sizeof(wide)); - } - if (dstIsFloat) { - const float value = static_cast(static_cast(wide) / 4294967295.0); - std::memcpy(converted.data() + t * 4, &value, sizeof(value)); - } else { // X8_D24: depth in the low 24 bits of a 32-bit word - const Uint32 word = wide >> 8; - std::memcpy(converted.data() + t * 4, &word, sizeof(word)); - } - } - item.expandedData = Move(converted); - item.source = item.expandedData.data(); - item.uploadByteSize = item.expandedData.size(); - } - item.offset = stagingSize; - stagingSize += static_cast(item.uploadByteSize); - } - } - - // Rare mid-frame hazard, kept at parity with the old per-upload - // submits: this image already has an upload recorded in the OPEN batch - // and has since been referenced by the frame's open recording (drawn). - // Appending here would merge both uploads into the same pre-frame - // submission the old code split into two; flush first so the second - // upload lands in its own later submission, exactly like before. - if (m_uploadBatchOpen && WasTouchedThisRecording(outResource) && - std::find(m_uploadBatchImages.begin(), m_uploadBatchImages.end(), outResource.image) != - m_uploadBatchImages.end()) { - FlushPendingUploads(); - } - // Bound the staging bytes a single batch can pin before its fence can - // reclaim them. - constexpr VkDeviceSize kMaxBatchStagingBytes = 64u * 1024u * 1024u; - if (m_uploadBatchOpen && m_uploadBatchStagingBytes + stagingSize > kMaxBatchStagingBytes) { - FlushPendingUploads(); - } - - VkCommandBuffer commandBuffer = EnsureUploadBatchOpen(); - VkBuffer stagingBuffer = VK_NULL_HANDLE; - VkDeviceSize stagingBase = 0; - Uint8* mapped = AcquireUploadStagingSpace(stagingSize, stagingBuffer, stagingBase); - for (const auto& item : uploadItems) { - Uint8* dst = mapped + item.offset; - if (!item.subRegion) { - std::memcpy(dst, item.source, item.uploadByteSize); - continue; - } - // Tight-pack the dirty box(es): the shadow keeps whole-level rows, the - // staging slice holds only the region (bufferRowLength stays 0). Multi- - // rect items pack their rects back to back in list order; the copy loop - // below recomputes the same running offsets. - const SizeT levelRowBytes = static_cast(item.texelSize.x()) * item.texelBytes; - const SizeT levelSliceBytes = static_cast(item.texelSize.y()) * levelRowBytes; - const Uint8* src = static_cast(item.source); - const auto packBox = [&](Uint8* out, const IntVec3& lo, const IntVec3& boxSize) { - const SizeT boxRowBytes = static_cast(boxSize.x()) * item.texelBytes; - for (Int z = 0; z < boxSize.z(); ++z) { - for (Int y = 0; y < boxSize.y(); ++y) { - const Uint8* srcRow = src + static_cast(lo.z() + z) * levelSliceBytes + - static_cast(lo.y() + y) * levelRowBytes + - static_cast(lo.x()) * item.texelBytes; - std::memcpy(out + (static_cast(z) * static_cast(boxSize.y()) + y) * - boxRowBytes, - srcRow, boxRowBytes); - } - } - return static_cast(boxSize.x()) * static_cast(boxSize.y()) * - static_cast(boxSize.z()) * item.texelBytes; - }; - if (!item.rects.empty()) { - for (const auto& rect : item.rects) { - dst += packBox(dst, rect.lo, - IntVec3{rect.hi.x() - rect.lo.x(), rect.hi.y() - rect.lo.y(), - rect.hi.z() - rect.lo.z()}); - } - continue; - } - packBox(dst, item.regionLo, item.regionSize); - } - - if (MG_Util::PipeStats::Enabled()) { - // Same shape split as Espryt's: one union box per item, or one job per rect of - // a refined rect list. The box/rect decision is invisible to SSIM and is what - // the +6 ms/frame Mali cliff of section 7.3 was, so it is counted apart from - // the bytes. - Uint64 boxEmissions = 0; - Uint64 rectEmissions = 0; - Uint64 jobs = 0; - for (const auto& item : uploadItems) { - if (item.rects.empty()) { - ++boxEmissions; - jobs += isCombinedDepthStencil ? 2u : 1u; - } else { - ++rectEmissions; - jobs += static_cast(item.rects.size()); - } - } - MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageTexture, - static_cast(stagingSize)); - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureUploadEmissions, - static_cast(uploadItems.size())); - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureUploadBoxEmissions, boxEmissions); - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureUploadRectEmissions, rectEmissions); - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureUploadJobs, jobs); - } - - const VkImageAspectFlags aspectMask = GetAspectMaskForFormat(outResource.format); - VkPipelineStageFlags uploadSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags uploadSrcAccessMask = 0; - GetImageTransitionSourceState(outResource.layout, uploadSrcStageMask, uploadSrcAccessMask); - Bool ok = TransitionImageLayout(commandBuffer, outResource.image, - outResource.layout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - uploadSrcStageMask, - VK_PIPELINE_STAGE_TRANSFER_BIT, - uploadSrcAccessMask, - VK_ACCESS_TRANSFER_WRITE_BIT, - aspectMask, 0, outResource.mipLevels); - MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed"); - - // Array textures keep their GL "depth" in VkImage array layers, so the - // copy must address layerCount, not imageExtent.depth (which is invalid - // for 2D images and silently dropped every layer past the first). - const Bool depthSelectsArrayLayer = outResource.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY || - outResource.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY || - outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; - Vector rectCopies; - for (const auto& item : uploadItems) { - if (!item.rects.empty()) { - // Multi-rect item: one VkBufferImageCopy per rect, all submitted in a - // single vkCmdCopyBufferToImage. The rect list is pairwise disjoint by - // construction, so no two copies write the same texels. Multi-rect - // implies subRegion, which implies a plain color aspect - the combined - // depth-stencil split below can never see one of these. - rectCopies.resize(item.rects.size()); - Uint32 rectCopyCount = 0; - VkDeviceSize runningOffset = item.offset; - for (const auto& rect : item.rects) { - const IntVec3 rectSize = {rect.hi.x() - rect.lo.x(), rect.hi.y() - rect.lo.y(), - rect.hi.z() - rect.lo.z()}; - const Uint32 rectDepth = static_cast(std::max(rectSize.z(), 1)); - VkBufferImageCopy rectCopy{}; - rectCopy.bufferOffset = stagingBase + runningOffset; - rectCopy.bufferRowLength = 0; - rectCopy.bufferImageHeight = 0; - rectCopy.imageSubresource.aspectMask = aspectMask; - rectCopy.imageSubresource.mipLevel = item.level; - rectCopy.imageSubresource.baseArrayLayer = item.baseArrayLayer; - rectCopy.imageSubresource.layerCount = 1; - rectCopy.imageOffset = {rect.lo.x(), rect.lo.y(), - depthSelectsArrayLayer ? 0 : rect.lo.z()}; - rectCopy.imageExtent = {static_cast(rectSize.x()), - static_cast(rectSize.y()), - depthSelectsArrayLayer ? 1u : rectDepth}; - if (depthSelectsArrayLayer) { - // The GL "depth" axis addresses array layers here, so a partial - // z-range narrows the layer span rather than the extent. - rectCopy.imageSubresource.baseArrayLayer = - item.baseArrayLayer + static_cast(rect.lo.z()); - rectCopy.imageSubresource.layerCount = rectDepth; - } - rectCopies[rectCopyCount++] = rectCopy; - runningOffset += static_cast(rect.TexelCount() * item.texelBytes); - } - EndActiveRenderPassOn(commandBuffer); - vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, rectCopyCount, rectCopies.data()); - continue; - } - const Uint32 depthOrLayers = item.texelSize.z() > 0 ? static_cast(item.texelSize.z()) : 1u; - VkBufferImageCopy copy{}; - copy.bufferOffset = stagingBase + item.offset; - copy.bufferRowLength = 0; - copy.bufferImageHeight = 0; - copy.imageSubresource.aspectMask = aspectMask; - copy.imageSubresource.mipLevel = item.level; - copy.imageSubresource.baseArrayLayer = item.baseArrayLayer; - copy.imageSubresource.layerCount = depthSelectsArrayLayer ? depthOrLayers : 1; - copy.imageOffset = {0, 0, 0}; - copy.imageExtent = {static_cast(item.texelSize.x()), static_cast(item.texelSize.y()), - depthSelectsArrayLayer ? 1u : depthOrLayers}; - if (item.subRegion) { - const Uint32 regionDepth = static_cast(std::max(item.regionSize.z(), 1)); - copy.imageOffset = {item.regionLo.x(), item.regionLo.y(), - depthSelectsArrayLayer ? 0 : item.regionLo.z()}; - copy.imageExtent = {static_cast(item.regionSize.x()), - static_cast(item.regionSize.y()), - depthSelectsArrayLayer ? 1u : regionDepth}; - if (depthSelectsArrayLayer) { - // The GL "depth" axis addresses array layers here, so a partial - // z-range narrows the layer span rather than the extent. - copy.imageSubresource.baseArrayLayer = - item.baseArrayLayer + static_cast(item.regionLo.z()); - copy.imageSubresource.layerCount = regionDepth; - } - } - if (isCombinedDepthStencil) { - const SizeT texelCount = static_cast(item.texelSize.x()) * - static_cast(item.texelSize.y()) * - static_cast(std::max(item.texelSize.z(), 1)); - VkBufferImageCopy depthCopy = copy; - depthCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - VkBufferImageCopy stencilCopy = copy; - stencilCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; - stencilCopy.bufferOffset = stagingBase + item.offset + static_cast(texelCount) * 4; - const VkBufferImageCopy copies[2] = {depthCopy, stencilCopy}; - EndActiveRenderPassOn(commandBuffer); - vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, copies); - continue; - } - EndActiveRenderPassOn(commandBuffer); - vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ©); - } - - const VkImageLayout finalLayout = ResolveSampledReadOnlyLayout(aspectMask); - VkImageLayout uploadLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - ok = TransitionImageLayout(commandBuffer, outResource.image, - uploadLayout, - finalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, - s_sampledReadStagesSlot->value, - VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT, - aspectMask, 0, outResource.mipLevels); - MOBILEGL_ASSERT(ok, "TransitionImageLayout to sampled read-only layout failed"); - outResource.layout = finalLayout; - - // Ordering argument (replaces the old immediate per-texture submit): - // this upload is RECORDED into the shared batch command buffer, which - // FlushPendingUploads submits - with one vkQueueSubmit and one pooled - // fence for the whole batch - strictly BEFORE any other submission on - // the same queue whose commands could consume the image: the renderer - // flushes at every frame-command-buffer submit (mid-frame flush, - // readback, Present), and the texture manager flushes before the - // preserve-on-recreate copy and before deferring an image the batch - // references. The frame command buffer therefore still lands behind - // the uploads on the queue, so a texture uploaded and then immediately - // sampled in the same frame sees its data exactly as it did when each - // upload was its own submit. No fence is waited here, for the same - // reason as before: the batch queues behind the previous frame's - // rendering, and a synchronous wait would drain the GPU; the staging - // blocks/command buffer are parked on the reclaim list at flush time - // and recycled once the batch fence signals. - if (std::find(m_uploadBatchImages.begin(), m_uploadBatchImages.end(), outResource.image) == - m_uploadBatchImages.end()) { - m_uploadBatchImages.push_back(outResource.image); - } - m_uploadBatchStagingBytes += stagingSize; - - if (!ok) { - MGLOG_D("%s: texture upload cmd failed", __func__); - return false; - } - for (const auto& item : uploadItems) { - mipmapTexture.MarkStorageDirty(item.target, item.level, false); - } - outResource.layout = finalLayout; - // Large batches flush right away instead of riding until the frame - // submit: a big copy amortizes its own vkQueueSubmit, submitting it - // early lets the GPU overlap the copy with the rest of the frame's - // CPU recording (measurably faster than a frame-tail burst), and the - // frame-tail burst pattern was observed to leave the GPU in a - // latency state that taxes whatever runs next. Small uploads keep - // accumulating, so a lightmap+sprite frame still costs one submit. - constexpr VkDeviceSize kEagerUploadFlushBytes = 128u * 1024u; - if (m_uploadBatchStagingBytes >= kEagerUploadFlushBytes) { - FlushPendingUploads(); - } - return true; - } - - Bool VkTextureManager::CheckMipmapCompleteness(const MG_State::GLState::ITextureObject& texture, - TextureUploadTarget& outTarget, - IntVec3& outTexelSize, - SizeT& outByteSize, - Uint32& outMipLevelCount) { - const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture); - if (!mipTexture) { - MGLOG_D("%s: not TextureObjectMipmap", __func__); - return false; - } - const auto& targets = texture.GetUploadTargets(); - if (targets.empty()) { - MGLOG_D("%s: upload target empty", __func__); - return false; - } - - for (const auto target : targets) { - const Uint32 mipLevelCount = GetUploadMipLevelCount(*mipTexture, target); - if (mipLevelCount == 0) { - MGLOG_D("%s: mipLevelCount == 0", __func__); - continue; - } - - // Backing VkImage allocation still uses storage mip 0 as the physical image extent. - // GL_TEXTURE_BASE_LEVEL / MAX_LEVEL are applied later when building the sampled view. - const auto storageBaseTexelSize = mipTexture->GetMipmapTexelSize(target, 0); - const auto storageBaseByteSize = mipTexture->GetMipmapByteSize(target, 0); - if (storageBaseTexelSize.x() <= 0 || storageBaseTexelSize.y() <= 0 /*|| storageBaseByteSize == 0*/) { - continue; - } - - outTarget = target; - outTexelSize = storageBaseTexelSize; - outByteSize = storageBaseByteSize; - outMipLevelCount = mipLevelCount; - return true; - } - MGLOG_D("%s: no valid target or mipmap", __func__); - return false; - } - - Uint32 VkTextureManager::GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, - TextureUploadTarget target) { - const Uint totalLevelCount = texture.GetMipmapLevelCount(); - if (totalLevelCount == 0) { - return 0; - } - - Uint32 validLevelCount = 0; - for (Uint level = 0; level < totalLevelCount; ++level) { - const auto size = texture.GetMipmapTexelSize(target, level); - const auto byteSize = texture.GetMipmapByteSize(target, level); - if (size.x() <= 0 || size.y() <= 0 /*|| byteSize == 0*/) { - break; - } - ++validLevelCount; - } - return validLevelCount; - } - - void VkTextureManager::ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels, - Uint32& outBaseMipLevel, Uint32& outLevelCount) { - MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0"); - - Uint32 definedMipLevels = mipLevels; - if (const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&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 = definedMipLevels - 1; - const Uint32 requestedBaseMipLevel = std::min(static_cast(levelRange.x()), maxAvailableMipLevel); - Uint32 requestedMaxMipLevel = std::min(static_cast(levelRange.y()), maxAvailableMipLevel); - if (requestedMaxMipLevel < requestedBaseMipLevel) { - requestedMaxMipLevel = requestedBaseMipLevel; - } - - outBaseMipLevel = requestedBaseMipLevel; - outLevelCount = requestedMaxMipLevel - requestedBaseMipLevel + 1; - } - VkImageAspectFlags VkTextureManager::GetAspectMaskForFormat(VkFormat format) { switch (format) { case VK_FORMAT_D16_UNORM: diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 79e9553b..00cff614 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -565,18 +565,6 @@ public: Bool isTextureView = false; }; - // Records what a GL texture view needs of the image it views, so the next sync of the - // STORAGE texture creates (or recreates and copies forward) an image the view can be built - // over. See m_viewRequestedImageFlags for why this is lazy rather than unconditional. - void NoteTextureViewImageRequirements(MG_State::GLState::ITextureObject& viewTexture, - MG_State::GLState::ITextureObject& storageTexture); - VkImageCreateFlags GetViewRequestedImageFlags(const MG_State::GLState::ITextureObject& storageTexture) const; - // Appends every format a GL texture view reinterprets this storage as, for the narrowed - // VkImageFormatListCreateInfo the image is created with. - void AppendViewRequestedFormats(const MG_State::GLState::ITextureObject& storageTexture, - Vector& outFormats) const; - TextureResource* SyncTextureAndGetDescriptor( - MG_State::GLState::ITextureObject& texture); // P5f (fm): THE HANDLE-KEYED ARM of SyncTextureAndGetDescriptor. Under an active transport // the apply thread may not name the client's ITextureObject (rule E), so the clear / blit / // readback / mipmap verbs resolve their attachment textures from the wire handle the @@ -710,33 +698,8 @@ private: // stamp of 0 so a fresh resource counts as untouched. Uint64 m_recordingGeneration = 1; - Bool SyncTexture(MG_State::GLState::ITextureObject &texture, - TextureResource &outResource); - Bool SyncTextureResource(const MG_State::GLState::ITextureObject &texture, - TextureUploadTarget uploadTarget, - const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels, - TextureResource &resource); - Bool SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource); - VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, - VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount, - Uint32 baseArrayLayer, - Uint32 layerCount, - const VkComponentMapping* components = nullptr, - VkImageUsageFlags viewUsage = 0) const; - Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, - TextureUploadTarget uploadTarget, - TextureResource &outResource); - static Bool CheckMipmapCompleteness(const MG_State::GLState::ITextureObject& texture, - TextureUploadTarget& outTarget, - IntVec3& outTexelSize, - SizeT& outByteSize, - Uint32& outMipLevelCount); - static Uint32 GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget target); - 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(); // Frees the fence/command buffer/staging buffer of every in-flight texture diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 44b1711c..27e6fad9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -193,25 +193,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - static VkColorComponentFlags GetSupportedColorWriteMaskForComponentCount(SizeT componentCount) { - switch (componentCount) { - case 1: - return VK_COLOR_COMPONENT_R_BIT; - case 2: - return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT; - case 3: - return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT; - case 4: - return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - default: - MOBILEGL_ASSERT(false, - "GetSupportedColorWriteMaskForComponentCount: unsupported componentCount=%zu", - componentCount); - return 0; - } - } - static Bool IsQuarterTurnPreTransform(VkSurfaceTransformFlagBitsKHR preTransform) { return preTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || preTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; @@ -1128,11 +1109,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { static constexpr Uint32 kDescriptorSetsPerFrame = 64; - static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) { - return target >= TextureUploadTarget::CubeMapPositiveX && - target <= TextureUploadTarget::CubeMapNegativeZ; - } - enum class BlitSurfaceTransform : Uint32 { Identity = 0, Rotate90 = 1, @@ -3239,34 +3215,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineFactory::PipelineCreatePayload::kMaxColorAttachments, "GetOrCreatePipeline: colorAttachmentCount=%u exceeds payload capacity", payload.colorAttachmentCount); + // The draw framebuffer is the applier's record on every arm (P13 W6): a draw with none + // has nothing to build a pipeline for, and is declined by name rather than guessed at. const auto* wireFbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); - const auto drawFboBinding = wireFbo ? SharedPtr{} : - MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - MOBILEGL_ASSERT(wireFbo != nullptr || drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null"); - const Bool isDefaultDrawFbo = wireFbo ? wireFbo->IsDefault : drawFboBinding->IsDefaultFramebuffer(); - Array wireDrawBuffers{}; - if (wireFbo) for (SizeT i = 0; i < wireDrawBuffers.size(); ++i) - wireDrawBuffers[i] = wireFbo->DrawBuffers[i] < 0 ? FramebufferAttachmentType::None : + if (wireFbo == nullptr) { + MGLOG_E_ONCE("GetOrCreatePipeline skipped: no draw framebuffer record is bound"); + return VK_NULL_HANDLE; + } + const Bool isDefaultDrawFbo = wireFbo->IsDefault; + Array drawBuffers{}; + for (SizeT i = 0; i < drawBuffers.size(); ++i) + drawBuffers[i] = wireFbo->DrawBuffers[i] < 0 ? FramebufferAttachmentType::None : static_cast(static_cast(FramebufferAttachmentType::Color0) + wireFbo->DrawBuffers[i]); - const auto& drawBuffers = wireFbo ? wireDrawBuffers : drawFboBinding->GetDrawBuffers(); - auto resolveCompleteColorAttachmentTexture = [&](Uint32 drawBufferIndex) -> MG_State::GLState::ITextureObject* { - if (wireFbo) return nullptr; - if (isDefaultDrawFbo || drawBufferIndex >= drawBuffers.size()) { - return nullptr; - } - - const auto drawBuffer = drawBuffers[drawBufferIndex]; - if (drawBuffer == FramebufferAttachmentType::None) { - return nullptr; - } - - const auto& attachment = drawFboBinding->GetAttachment(drawBuffer); - if (!attachment.IsTexture() || !attachment.IsComplete()) { - return nullptr; - } - - return attachment.GetTexture().get(); - }; for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) { BlendFactor srcRGB = BlendFactor::One; BlendFactor dstRGB = BlendFactor::Zero; @@ -3288,8 +3248,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { (bufferMask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) | (bufferMask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u)); Bool effectiveBlendEnabled = blendEnabled; - MG_State::GLState::ITextureObject* colorAttachmentTexture = nullptr; - MG_State::GLState::RenderbufferObject* colorAttachmentRenderbuffer = nullptr; if (isDefaultDrawFbo && i < drawBuffers.size() && drawBuffers[i] == FramebufferAttachmentType::None) { // The default framebuffer spans the same MAX_DRAW_BUFFERS slots as an FBO @@ -3301,100 +3259,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { attachmentColorWriteMask = 0; effectiveBlendEnabled = false; } - if (wireFbo && !isDefaultDrawFbo) { + if (!isDefaultDrawFbo) { const Int32 slot = wireFbo->DrawBuffers[i]; if (slot < 0 || wireFbo->Color[slot].Kind == MG_Pipe::kMGPipeSurfaceKindNone) { attachmentColorWriteMask = 0; effectiveBlendEnabled = false; } - } else - if (!isDefaultDrawFbo && i < drawBuffers.size()) { - const auto drawBuffer = drawBuffers[i]; - colorAttachmentTexture = resolveCompleteColorAttachmentTexture(i); - if (colorAttachmentTexture == nullptr && drawBuffer != FramebufferAttachmentType::None) { - const auto& attachment = drawFboBinding->GetAttachment(drawBuffer); - if (attachment.IsRenderbuffer() && attachment.IsComplete()) { - colorAttachmentRenderbuffer = attachment.GetRenderbuffer().get(); - } - } - if (drawBuffer == FramebufferAttachmentType::None || - (colorAttachmentTexture == nullptr && colorAttachmentRenderbuffer == nullptr)) { - // GL ignores writes and per-target blend state for GL_NONE draw buffer slots. - // Depth-only or otherwise unattached draw buffers should also discard color writes. - attachmentColorWriteMask = 0; - effectiveBlendEnabled = false; - } - if (colorAttachmentRenderbuffer != nullptr) { - const SizeT componentCount = MG_Util::GetBaseInternalFormatComponentCount( - colorAttachmentRenderbuffer->GetInternalFormat()); - attachmentColorWriteMask &= GetSupportedColorWriteMaskForComponentCount(componentCount); - } - if (colorAttachmentTexture != nullptr) { - auto* texture = colorAttachmentTexture; -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG - const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture); - MOBILEGL_ASSERT(textureResource != nullptr, - "GetOrCreatePipeline: failed to sync color attachment textureId=%d", - texture->GetExternalIndex()); - VkFormatProperties attachmentFormatProperties{}; - vkGetPhysicalDeviceFormatProperties( - m_physicalDevice.handle, - textureResource->format, - &attachmentFormatProperties); - MOBILEGL_ASSERT( - (attachmentFormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0, - "GetOrCreatePipeline: color attachment %u format=%d textureId=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT (program=%u)", - i, - static_cast(textureResource->format), - texture->GetExternalIndex(), - program.GetExternalIndex()); -#endif - const SizeT componentCount = MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()); -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG - const NumericDomain attachmentNumericDomain = - GetNumericDomainForTextureInternalFormat(texture->GetFormat()); - for (Uint32 outputLocation = 0; - outputLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations; - ++outputLocation) { - if ((programObj.activeFragmentOutputLocationMask & (1u << outputLocation)) == 0 || - outputLocation != i) { - continue; - } - - const GLenum fragmentOutputType = programObj.fragmentOutputTypes[outputLocation]; - const NumericDomain fragmentOutputDomain = - GetNumericDomainForShaderValueType(fragmentOutputType); - // GL allows fragment outputs with more components than the bound color attachment; - // excess components are discarded during conversion to the attachment format. - MOBILEGL_ASSERT( - attachmentNumericDomain == NumericDomain::Unknown || - fragmentOutputDomain == NumericDomain::Unknown || - attachmentNumericDomain == fragmentOutputDomain, - "GetOrCreatePipeline: fragment output location=%d type=%u mismatches color attachment %u internalFormat=%d textureId=%d program=%u", - static_cast(outputLocation), - static_cast(fragmentOutputType), - i, - static_cast(texture->GetFormat()), - texture->GetExternalIndex(), - program.GetExternalIndex()); - } -#endif - const VkColorComponentFlags supportedColorWriteMask = - GetSupportedColorWriteMaskForComponentCount(componentCount); - if ((attachmentColorWriteMask & ~supportedColorWriteMask) != 0) { - MGLOG_W_ONCE( - "GetOrCreatePipeline: clamping colorWriteMask=0x%x to 0x%x on color attachment %u (componentCount=%zu textureId=%d internalFormat=%d program=%u blendEnabled=%d)", - static_cast(attachmentColorWriteMask), - static_cast(attachmentColorWriteMask & supportedColorWriteMask), - i, - componentCount, - texture->GetExternalIndex(), - static_cast(texture->GetFormat()), - program.GetExternalIndex(), - effectiveBlendEnabled ? 1 : 0); - attachmentColorWriteMask &= supportedColorWriteMask; - } - } } if (effectiveBlendEnabled) { MOBILEGL_ASSERT(i < drawBuffers.size(), @@ -3403,40 +3273,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFormat colorAttachmentFormat = VK_FORMAT_UNDEFINED; Int textureExternalIndex = -1; - if (wireFbo && !isDefaultDrawFbo) { + if (!isDefaultDrawFbo) { const Int32 slot = wireFbo->DrawBuffers[i]; colorAttachmentFormat = ResolveWireImage(*wireFbo, wireFbo->Color[slot], VK_IMAGE_ASPECT_COLOR_BIT).format; - } else - if (isDefaultDrawFbo) { - colorAttachmentFormat = m_swapchainObject.GetSurfaceFormat().format; - } else if (colorAttachmentRenderbuffer != nullptr) { - textureExternalIndex = static_cast(colorAttachmentRenderbuffer->GetExternalIndex()); - // The SAME resolver GetOrCreateRenderbufferResource backs the image with, so the - // probe cannot ask about a format the attachment does not have. The strict 1:1 - // converter is the wrong question here and answered VK_FORMAT_UNDEFINED for - // RGBA2/RGBA12/RGB10/RGB12/RGB16 and the packed 16-bit formats for RGBA4/RGB5_A1 - // - and VkFormatProperties for UNDEFINED are all zero, so blending was - // force-disabled forever on attachments that blend perfectly well. Every - // three-channel colour renderbuffer was in that set too (R8G8B8_UNORM is rarely - // supported), which is the more ordinary shape. - // - // Resolved rather than looked up: GetOrCreateRenderbufferResource creates images - // and bumps epochs, which a pipeline-state query must not do as a side effect. - // A renderbuffer has no device-fallback step after the resolver (unlike the - // texture path's D24 -> D32 substitution), so the resolver IS its live format. - colorAttachmentFormat = - ResolveTextureFormatInfo(colorAttachmentRenderbuffer->GetInternalFormat()).format; } else { - auto* texture = colorAttachmentTexture; - MOBILEGL_ASSERT(texture != nullptr, - "GetOrCreatePipeline: blend is enabled on draw buffer %u but no complete color attachment is bound", - i); - textureExternalIndex = texture->GetExternalIndex(); - auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture); - MOBILEGL_ASSERT(textureResource != nullptr, - "GetOrCreatePipeline: failed to sync blend color attachment textureId=%d", - texture->GetExternalIndex()); - colorAttachmentFormat = textureResource->format; + colorAttachmentFormat = m_swapchainObject.GetSurfaceFormat().format; } // Blending on an attachment whose format lacks diff --git a/scripts/data/link_ratchet_baseline.txt b/scripts/data/link_ratchet_baseline.txt index af5217ab..d5130613 100644 --- a/scripts/data/link_ratchet_baseline.txt +++ b/scripts/data/link_ratchet_baseline.txt @@ -5,7 +5,7 @@ # base commit : # configure : -DMOBILEGL_BUILD_DISAGGREGATED=ON -DMOBILEGL_BUILD_DISAGGREGATED_INPROC=ON -DCMAKE_BUILD_TYPE=Release # build dir : /home/swung/mgl-p13-split -# count : 58 +# count : 49 # # One demangled symbol per line. A trailing ` # note` is an annotation the parser # ignores; `# P13` marks a symbol that cannot fall before P13 establishes module @@ -21,7 +21,6 @@ MobileGL::MG_Pipe::MGPipeSlotAllocator::FindByLifetimeId(MobileGL::MG_Pipe::MGPi MobileGL::MG_Pipe::MGPipeSlotAllocator::Free(MobileGL::MG_Pipe::MGPipeKind, MobileGL::MG_Pipe::MGPipeHandle) # P13 MobileGL::MG_Pipe::MGPipeSlots() # P13 MobileGL::MG_Pipe::PipeInputs::GetBufferBindingPointCount(MobileGL::BufferTarget) const -MobileGL::MG_Pipe::PipeInputs::GetTextureObject(unsigned int) # P13 monolith arm: MGB_CTX accessor / GL readback packer (CONTRACT-P7 §12) MobileGL::MG_Pipe::PipeInputs::HasOpenTransformFeedbackSpan(unsigned long) const # P13 monolith arm: MGB_CTX accessor / GL readback packer (CONTRACT-P7 §12) MobileGL::MG_Pipe::PipeInputs::InvalidateCompileEnv() # P13 monolith arm: MGB_CTX accessor / GL readback packer (CONTRACT-P7 §12) MobileGL::MG_Pipe::PipeInputs::IsLive() const @@ -48,18 +47,10 @@ MobileGL::MG_State::GLState::BufferObject::SyncPersistentMappedRange() MobileGL::MG_State::GLState::BufferObject::WritebackFromBackend(MobileGL::DataPtr, unsigned long) MobileGL::MG_State::GLState::ClassifyVertexAttribType(unsigned int) MobileGL::MG_State::GLState::DecodeProgramArchive(unsigned char const*, unsigned long, MobileGL::MG_State::GLState::ProgramArchive&) -MobileGL::MG_State::GLState::FramebufferAttachmentObject::GetRenderbuffer() const -MobileGL::MG_State::GLState::FramebufferAttachmentObject::GetTexture() const -MobileGL::MG_State::GLState::FramebufferAttachmentObject::IsComplete() const # P13 monolith arm: reads an app FBO/RBO/VAO/buffer object (CONTRACT-P7 §12) -MobileGL::MG_State::GLState::FramebufferAttachmentObject::IsRenderbuffer() const -MobileGL::MG_State::GLState::FramebufferAttachmentObject::IsTexture() const -MobileGL::MG_State::GLState::FramebufferObject::GetAttachment(MobileGL::FramebufferAttachmentType) const -MobileGL::MG_State::GLState::FramebufferObject::GetDrawBuffers() const MobileGL::MG_State::GLState::GetBufferBackendOps() MobileGL::MG_State::GLState::ProgramArtifactsSchemaFingerprint() MobileGL::MG_State::GLState::ProgramObject::JoinPendingLink() const MobileGL::MG_State::GLState::RenderbufferObject::GetExternalIndex() const -MobileGL::MG_State::GLState::RenderbufferObject::GetInternalFormat() const MobileGL::MG_State::GLState::SamplerObject::GetAllSamplerParameters() const MobileGL::MG_State::GLState::SamplerObject::GetExternalIndex() const MobileGL::MG_State::GLState::SamplerObject::GetLifetimeId() const