From dab827d54f16e5cdef97198592e545423932263e Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 6 Oct 2026 18:57:15 -0400 Subject: [PATCH] [Refactor] (P13 W6e): the backends' frontend-framebuffer clear, blit and readback entries go - the verb port owns those slots in every mode - with Magma's frontend render-pass, pending-clear and renderbuffer halves. --- .../DirectGLES/BackendObject_DirectGLES.cpp | 7 - MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 1017 ------- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 16 - MobileGL/MG_Backend/DirectGLES/Managers.cpp | 579 ---- MobileGL/MG_Backend/DirectGLES/Managers.h | 15 - .../BackendObject_DirectVulkan.cpp | 8 - .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 56 - .../MG_Backend/DirectVulkan/DirectVulkan.h | 18 - .../DirectVulkan/Renderer/UniformManager.cpp | 178 +- .../DirectVulkan/Renderer/UniformManager.h | 76 +- .../DirectVulkan/Renderer/VkClearManager.cpp | 440 --- .../DirectVulkan/Renderer/VkClearManager.h | 36 - .../Renderer/VkRenderPassManager.cpp | 1404 --------- .../Renderer/VkRenderPassManager.h | 70 - .../Renderer/VkSamplerManager.cpp | 6 - .../DirectVulkan/Renderer/VkSamplerManager.h | 6 - .../Renderer/VkTextureManager.cpp | 375 --- .../DirectVulkan/Renderer/VkTextureManager.h | 32 - .../DirectVulkan/Renderer/VulkanRenderer.cpp | 2561 ----------------- .../DirectVulkan/Renderer/VulkanRenderer.h | 113 - .../MG_Test/Framebuffer/FramebufferTest.cpp | 33 - MobileGL/MG_Test/SanityTest.cpp | 17 +- MobileGL/MG_Test/Texture/CMakeLists.txt | 17 - 23 files changed, 24 insertions(+), 7056 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 4c87e1a8..324850bc 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -1605,7 +1605,6 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.DrawElements = DrawElements; funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex; funcsTable.GL.MultiDrawArrays = MultiDrawArrays; - funcsTable.GL.MultiDrawElements = MultiDrawElements; funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex; funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect; funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount; @@ -1633,18 +1632,12 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.ClearBufferfv = ClearBufferfv; funcsTable.GL.ClearBufferuiv = ClearBufferuiv; funcsTable.GL.ClearBufferiv = ClearBufferiv; - funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv; - funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv; - funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv; - funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi; funcsTable.GL.BlitFramebuffer = BlitFramebuffer; - funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer; funcsTable.GL.CopyTexImage2D = CopyTexImage2D; funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D; funcsTable.GL.CopyImageSubData = CopyImageSubData; funcsTable.GL.GenerateMipmap = GenerateMipmap; funcsTable.GL.ReadPixels = ReadPixels; - funcsTable.GL.GetTexImage = GetTexImage; funcsTable.GL.FenceSync = FenceSync; funcsTable.GL.ClientWaitSync = ClientWaitSync; funcsTable.GL.WaitSync = WaitSync; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index f489c47a..2a0eceb8 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -2570,14 +2570,6 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_State::GLState::ITextureObject* texture = nullptr; BackendTextureObject* backend = nullptr; }; - // True while every entry's borrowed slot still holds the texture the entry was paired - // with. Callers put it LAST in the key conjunction so it only runs on a key hit. - static Bool PairingsIntact(const Vector& list) { - for (const auto& entry : list) { - if (entry.slot->get() != entry.texture) return false; - } - return true; - } // P5e (tx2), CONTRACT-P5E §5.2: THE UNIT WORK LIST WITH NO BORROWED SLOT IN IT. // // The pre-P5e entry borrows a pointer INTO a frontend binding slot and remembers which @@ -3415,17 +3407,6 @@ namespace MobileGL::MG_Backend::DirectGLES { } // namespace TextureImpl namespace FramebufferImpl { - // Record that `target` now reflects this exact (binding, object, revision) triple. - // Every one of the three has to move together: stamping a subset leaves an early-out - // that either never fires or fires on state it never actually pushed. - static void StampSyncedFBO(FramebufferTarget target, Uint16 slotVersion, Uint16 objectVersion, - MG_State::GLState::FramebufferObject* fbo) { - g_fboSyncedSlotVersions[SizeT(target)] = slotVersion; - g_fboSyncedObjectVersions[SizeT(target)] = objectVersion; - g_fboSyncedObjects[SizeT(target)] = fbo; - g_fboSyncedBackendIdGenerations[SizeT(target)] = g_attachmentBackendIdGeneration; - } - // P4a e1 (D-C2, D-B3). The framebuffer family's handle arm. // // THE FOUR-PART MEMO BECOMES TWO VALUES, and the halves are not symmetric. @@ -3749,89 +3730,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // contract asks for, with the verb in the message so the strict lane's marker table // points at the site. RefuseFramebufferBindingSlotRead(); - - const FramebufferTarget fboTargets[] = {FramebufferTarget::Draw, FramebufferTarget::Read}; - - MG_State::GLState::FramebufferObject* lastUpdatedFBO = nullptr; - - for (auto& target : fboTargets) { - // P5c (G6, CONTRACT-P5C §5.4): the twin resolutions this iteration may run - // below are frontend-keyed, named debt inside the scope - P3b/P4b rekeys the - // registry onto handles. - const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; - auto& slot = GetFramebufferBindingSlotChecked(target); - auto& currentFBO = slot.GetBoundObject(); - - // The three memos together say "this target is already synced": which object is - // bound (pointer), that it is still the same binding and not a recycled address - // (slot version, bumped by every real rebind), and that the object has not been - // edited since (object version, bumped by every attachment/draw-buffer/read-buffer - // change). All three are stamped by every path below that leaves the target synced - // - including the ones that decide there is nothing to do. Leaving the slot version - // to ForceBindCurrentFBO alone made it a permanent mismatch for any app that never - // hits a blit or a DSA clear, so this early-out never fired and a Minecraft-shaped - // frame re-ran the whole attachment walk on all 5495 draws. - const Uint16 slotVersion = slot.GetVersion(); - const Uint16 objectVersion = currentFBO ? currentFBO->GetObjectVersion() : 0; - auto* currentPtr = currentFBO.get(); - // The backend-id generation joins the triple: a backend texture re-mint - // (RecreateBackendTexture) moves no frontend version, so without it the - // early-out would keep the driver FBO on the deleted texture name. - if (slotVersion == g_fboSyncedSlotVersions[SizeT(target)] && - objectVersion == g_fboSyncedObjectVersions[SizeT(target)] && - currentPtr == g_fboSyncedObjects[SizeT(target)] && - g_fboSyncedBackendIdGenerations[SizeT(target)] == g_attachmentBackendIdGeneration) { - lastUpdatedFBO = currentPtr; - continue; - } - - if (!currentFBO) { - MGLOG_E_ONCE("No FBO is currently bound, cannot sync current FBO."); - continue; - } - - if (currentFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { - // Default FBO, nothing to sync - except the widened-attachment mask, which is - // only ever WRITTEN by SyncToBackend and would otherwise still describe the - // user FBO that was draw-bound before. The window surface is a real RGBA - // buffer, so nothing here is ever widened. - if (target == FramebufferTarget::Draw) { - g_alphaWidenedDrawBufferMask = 0; - g_integerColorDrawBufferMask = 0; - } - StampSyncedFBO(target, slotVersion, objectVersion, currentPtr); - continue; - } - - if (currentFBO.get() == lastUpdatedFBO) { - MGLOG_D("Draw FBO and read FBO are the same, skipping sync."); - // The attachment/draw-buffer work was already done for this GL FBO as the DRAW - // target, but the read buffer (glReadBuffer) is READ-target-specific and would - // be dropped by this skip. Apply it so reads target the right attachment (e.g. - // KHR-GL33.draw_buffers reads each COLOR_ATTACHMENT while the FBO stays bound as - // GL_FRAMEBUFFER — without this every glReadBuffer is a no-op and all reads hit - // COLOR_ATTACHMENT0). - if (target == FramebufferTarget::Read) { - auto* syncedFBOSlot = g_backendFramebufferObjects.Find(currentFBO.get()); - if (syncedFBOSlot && *syncedFBOSlot) { - (*syncedFBOSlot)->SyncReadBufferToBackend(currentFBO); - } - } - StampSyncedFBO(target, slotVersion, objectVersion, currentPtr); - continue; - } - - auto* backendFBOSlot = g_backendFramebufferObjects.Find(currentFBO.get()); - auto& backendObj = - backendFBOSlot ? *backendFBOSlot : g_backendFramebufferObjects.GetOrCreate(currentFBO); - if (!backendObj) { - backendObj = MakeShared(); - } - backendObj->SyncToBackend(currentFBO, target); - - StampSyncedFBO(target, slotVersion, objectVersion, currentPtr); - lastUpdatedFBO = currentFBO.get(); - } } } // namespace FramebufferImpl @@ -5038,82 +4936,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // sync's trust latch is clear - and the sync aborted before it could be - or this bind // ran with no sync in front of it, which the latch exists to catch. RefuseFramebufferBindingSlotRead(); - - auto& slot = GetFramebufferBindingSlotChecked(target); - // No fast path on the binding slot's version. It is a 16-bit counter that only - // ForceBindCurrentFBO ever stamps here, so the comparison was against an arbitrarily old - // snapshot and any later slot version that happened to land on it - one wrap of the - // counter, or simply enough rebinds - read as "already bound" and left the driver on a - // completely different framebuffer. KHR-GL32.packed_pixels then read its gradient back - // out of the previous subtest's framebuffer. - // - // Skipping the work is BindFramebufferId's job anyway: it shadows the driver's own - // draw/read bindings and drops the glBindFramebuffer when the target already holds the id, - // which is where the cost actually is. What is left here is one registry lookup, - // and the twin memo replaces even that with an array probe on the steady path. - const auto& currentFBO = slot.GetBoundObject(); - if (currentFBO && currentFBO != MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { - // P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside - // the scope - P3b/P4b rekeys the registry onto handles. - const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; - FramebufferImpl::BackendFramebufferObject* twin = nullptr; - if (EsprytSlotTablesEnabled()) { - // Not "slot": the enclosing scope's `slot` is the framebuffer BINDING slot, - // and this one is the twin table's entry. - auto* twinEntry = FramebufferImpl::g_backendFramebufferObjects.Find(currentFBO.get()); - if (twinEntry && *twinEntry) { - twin = twinEntry->get(); - } - } else - { - } - if (twin) { - twin->Bind(target); - } else { - MGLOG_E_ONCE("No backend FBO found (maybe not synced) for current %s FBO, cannot bind FBO.", - (target == FramebufferTarget::Read ? "READ" : "DRAW")); - } - } else { - MGLOG_D("Binding default framebuffer as %s FBO", (target == FramebufferTarget::Read ? "READ" : "DRAW")); - // Through the shadow: a raw bind here would leave the shadow claiming - // the previous user FBO, false-skipping its next re-bind. - FramebufferImpl::BindFramebufferId( - target == FramebufferTarget::Draw ? GL_DRAW_FRAMEBUFFER : GL_READ_FRAMEBUFFER, 0); - } - } - - void SyncAndBindFramebufferObject(const SharedPtr& framebuffer, - FramebufferTarget target, Bool forceSync = false) { -#ifdef TRACY_ENABLE - ZoneScopedC(TRACY_ZONECOLOR_BACKEND); -#endif - if (!framebuffer || framebuffer == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) { - // Same reset as SyncCurrentFBO's default-framebuffer branch: SyncToBackend is the - // only writer of the widened-attachment mask, so a path that skips it has to say so - // explicitly. It matters here because the DSA clears and glBlitFramebuffer briefly - // sync a DIFFERENT framebuffer as DRAW and then restore the application's through - // ForceBindCurrentFBO - which lands right here when that one is the default. - if (target == FramebufferTarget::Draw) { - FramebufferImpl::g_alphaWidenedDrawBufferMask = 0; - FramebufferImpl::g_integerColorDrawBufferMask = 0; - } - FramebufferImpl::BindFramebufferId( - target == FramebufferTarget::Draw ? GL_DRAW_FRAMEBUFFER : GL_READ_FRAMEBUFFER, 0); - return; - } - - auto& registry = FramebufferImpl::g_backendFramebufferObjects; - auto* backendFBOSlot = registry.Find(framebuffer.get()); - auto& backendObj = backendFBOSlot ? *backendFBOSlot : registry.GetOrCreate(framebuffer); - if (!backendObj) { - backendObj = MakeShared(); - } - if (forceSync) { - backendObj->InvalidateSyncedState(); - } - - backendObj->SyncToBackend(framebuffer, target); - backendObj->Bind(target); } // P5c (hd, CONTRACT-P5C §3.2/§3.3): the handle-keyed form of SyncAndBindFramebufferObject. @@ -7389,18 +7211,6 @@ namespace MobileGL::MG_Backend::DirectGLES { if (feedDrawID) SetCurrentDrawID(0); } - // Both glMultiDrawElements entry points are emulated - ES has neither in core - by the - // tier ladder in MultiDraw.cpp, which owns the draw preparation too (its compute tier - // has to dispatch before the draw state is established). The only difference between - // them is whether the batch carries per-sub-draw base vertices. - void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, - GLsizei drawcount) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - MultiDrawImpl::DrawElementsBatch(mode, count, type, indices, drawcount, nullptr); - } - void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount, const GLint* basevertex) { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER @@ -8679,164 +8489,6 @@ namespace MobileGL::MG_Backend::DirectGLES { DrainBlitErrors(); } - // ---- glBlitFramebuffer onto a non-zero array layer ------------------------------------- - // - // Some drivers write to layer 0 whatever layer the DRAW framebuffer's - // glFramebufferTextureLayer attachment names, and raise no error doing it (Adreno 830; - // SelfTest::ProbeBlitIgnoresDestinationArrayLayer measures it, with the destination-layer-0 - // case as the control). glCopyImageSubData takes the destination layer as an argument rather - // than reading it off an attachment, and honours it on the same driver - so a blit that is a - // plain 1:1 copy is issued that way instead. - // - // ONLY a plain 1:1 copy. glCopyImageSubData cannot scale, flip, convert format or resolve - // samples, and it is not clipped by the scissor, so every one of those is a reason to hand - // the call back to the driver rather than quietly perform a different operation. Those blits - // still land on the wrong layer; a once-per-process line says so rather than leaving it to be - // rediscovered. - // - // Per aspect, not all-or-nothing: the returned mask is the bits this performed itself, and - // the caller passes the rest to the driver. A COLOR|DEPTH blit whose colour half scales and - // whose depth half does not still gets its depth half repaired. - static GLbitfield BlitLayeredDestinationAspects( - const SharedPtr& readFramebuffer, - const SharedPtr& drawFramebuffer, GLint srcX0, GLint srcY0, - GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask) { - if (mask == 0 || !readFramebuffer || !drawFramebuffer) return 0; - if (!g_GLESFuncs.glCopyImageSubData) return 0; - if (!MG_Util::SelfTest::BlitIgnoresDestinationArrayLayer(g_GLESFuncs)) return 0; - - // The default framebuffer has no layers to get wrong, and a blit between the two halves - // of the same framebuffer object is not a shape this substitutes for. - const Int width = srcX1 - srcX0; - const Int height = srcY1 - srcY0; - const Bool oneToOne = width > 0 && height > 0 && (dstX1 - dstX0) == width && (dstY1 - dstY0) == height; - // The scissor clips a blit and does not clip a copy, so an enabled scissor makes the two - // different operations no matter how the rectangles line up. - const Bool scissorEnabled = RenderStateImpl::DriverScissorTestEnabled(); - - using MobileGL::FramebufferAttachmentType; - struct AspectPlan { - GLbitfield bit; - FramebufferAttachmentType source; - FramebufferAttachmentType destination; - }; - // The colour aspect follows glReadBuffer on the read side and draw buffer 0 on the write - // side, which is the only draw buffer a blit onto a layered destination can be pinned to - // here: a blit writes EVERY enabled draw buffer, so a framebuffer with more than one is - // left to the driver rather than half-repaired. - const auto& drawBuffers = drawFramebuffer->GetDrawBuffers(); - // GL_NONE is what an unwritten draw-buffer slot holds, and it is a different value from - // the "no such attachment" one - counting it as enabled made every framebuffer look like - // it had eight and sent every colour blit to the driver. - // - // The buffer is found rather than assumed to be slot 0: a blit writes every ENABLED draw - // buffer, and glDrawBuffers(GL_NONE, GL_NONE, GL_NONE, GL_COLOR_ATTACHMENT0) leaves slot - // 0 empty while still naming exactly one destination. - Int enabledDrawBuffers = 0; - FramebufferAttachmentType colorDestination = FramebufferAttachmentType::None; - for (const FramebufferAttachmentType buffer : drawBuffers) { - if (buffer != FramebufferAttachmentType::Unknown && buffer != FramebufferAttachmentType::None) { - ++enabledDrawBuffers; - if (enabledDrawBuffers == 1) colorDestination = buffer; - } - } - const AspectPlan plans[] = { - {GL_COLOR_BUFFER_BIT, readFramebuffer->GetReadBuffer(), colorDestination}, - {GL_DEPTH_BUFFER_BIT, FramebufferAttachmentType::Depth, FramebufferAttachmentType::Depth}, - {GL_STENCIL_BUFFER_BIT, FramebufferAttachmentType::Stencil, FramebufferAttachmentType::Stencil}, - }; - - GLbitfield handled = 0; - for (const AspectPlan& plan : plans) { - if ((mask & plan.bit) == 0) continue; - if (plan.source == FramebufferAttachmentType::Unknown || - plan.destination == FramebufferAttachmentType::Unknown || - plan.source == FramebufferAttachmentType::None || - plan.destination == FramebufferAttachmentType::None) { - continue; - } - const auto& sourceAttachment = readFramebuffer->GetAttachment(plan.source); - const auto& destinationAttachment = drawFramebuffer->GetAttachment(plan.destination); - // Renderbuffers have no layers, so a destination that is one cannot be hitting this. - if (!sourceAttachment.IsTexture() || !destinationAttachment.IsTexture()) continue; - // Layer 0 is the case the driver gets right, and a LAYERED attachment (glFramebufferTexture - // with no layer) blits its layer 0 by spec - neither is this defect. - if (destinationAttachment.GetTextureLayer() == 0) continue; - if (destinationAttachment.IsLayered() || sourceAttachment.IsLayered()) continue; - - const auto& sourceTexture = sourceAttachment.GetTexture(); - const auto& destinationTexture = destinationAttachment.GetTexture(); - if (!sourceTexture || !destinationTexture) continue; - // glCopyImageSubData moves texel blocks: same format both ends, or it is a different - // operation. Multisample endpoints would additionally have to agree on sample count, - // which is a resolve the driver still owns. - if (sourceTexture->GetFormat() != destinationTexture->GetFormat()) continue; - if (sourceTexture->GetSamples() > 0 || destinationTexture->GetSamples() > 0) continue; - // Copying an image region onto itself is undefined for glCopyImageSubData, and a blit - // whose source and destination overlap is undefined for GL too - so this is not a - // shape to substitute FOR, it is one to leave exactly as the application wrote it. - if (sourceTexture == destinationTexture && - sourceAttachment.GetTextureLevel() == destinationAttachment.GetTextureLevel() && - sourceAttachment.GetTextureLayer() == destinationAttachment.GetTextureLayer()) { - continue; - } - - // A combined depth-stencil texture is ONE image to glCopyImageSubData: it carries both - // aspects across whether or not the mask asked for both. Taking only GL_DEPTH_BUFFER_BIT - // on a DEPTH24_STENCIL8 destination would overwrite a stencil the application asked to - // keep, so the copy is only allowed when the mask covers everything the format holds. - const TextureInternalFormat format = destinationTexture->GetFormat(); - const Bool hasDepth = MG_Util::IsDepthFormatInternalFormat(format); - const Bool hasStencil = MG_Util::IsStencilFormatInternalFormat(format); - if (hasDepth && (mask & GL_DEPTH_BUFFER_BIT) == 0) continue; - if (hasStencil && (mask & GL_STENCIL_BUFFER_BIT) == 0) continue; - // ... and having carried both, it must be credited with both, or the caller hands the - // stencil half to the driver and it lands on layer 0 after all. - const GLbitfield aspectBits = - hasDepth || hasStencil - ? static_cast((hasDepth ? GL_DEPTH_BUFFER_BIT : 0) | - (hasStencil ? GL_STENCIL_BUFFER_BIT : 0)) - : static_cast(GL_COLOR_BUFFER_BIT); - if ((handled & aspectBits) == aspectBits) continue; - - if (!oneToOne || scissorEnabled || (plan.bit == GL_COLOR_BUFFER_BIT && enabledDrawBuffers != 1)) { - MGLOG_E_ONCE("BlitFramebuffer: this driver ignores a non-zero destination array layer and this " - "blit cannot be expressed as a copy (%s), so it will land on layer 0", - !oneToOne ? "it scales or flips" - : scissorEnabled ? "the scissor test is enabled" - : "the destination has more than one draw buffer"); - continue; - } - - TextureImpl::NoteDriverSideTextureWrite(destinationTexture); - auto backendSource = TextureImpl::SyncTextureObjectToBackend(sourceTexture); - auto backendDestination = TextureImpl::SyncTextureObjectToBackend(destinationTexture); - if (!backendSource || !backendDestination) continue; - const GLuint sourceName = backendSource->GetBackendTextureId(); - const GLuint destinationName = backendDestination->GetBackendTextureId(); - if (sourceName == 0 || destinationName == 0) continue; - const GLenum sourceTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(sourceTexture->GetTarget()); - const GLenum destinationTarget = - TextureImpl::ConvertTextureTargetToBackendGLEnum(destinationTexture->GetTarget()); - - ClearGLErrors(); - g_GLESFuncs.glCopyImageSubData(sourceName, sourceTarget, sourceAttachment.GetTextureLevel(), srcX0, srcY0, - sourceAttachment.GetTextureLayer(), destinationName, destinationTarget, - destinationAttachment.GetTextureLevel(), dstX0, dstY0, - destinationAttachment.GetTextureLayer(), width, height, 1); - if (const GLenum error = g_GLESFuncs.glGetError(); error != GL_NO_ERROR) { - // The driver blit still runs for this aspect - onto the wrong layer, but the - // substitute has to leave the call no worse off than it found it. - MGLOG_E_ONCE("BlitFramebuffer: the layered-destination copy substitute failed with %s; the " - "driver blit will run instead and land on layer 0", - MG_Util::ConvertGLEnumToString(error).c_str()); - continue; - } - handled |= aspectBits; - } - return handled & mask; - } - // ---- P5e (fb, CONTRACT-P5E.md §5.4): the same aspect plan, from the two RECORDS --------- // // The blit was the last entry point in this family that still reached into two frontend @@ -9138,54 +8790,6 @@ namespace MobileGL::MG_Backend::DirectGLES { }); } - void BlitNamedFramebuffer(const SharedPtr& readFramebuffer, - const SharedPtr& drawFramebuffer, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLbitfield mask, GLenum filter) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - TextureImpl::SyncNeccessaryTextures(); - RenderStateImpl::SyncRenderState(); - - SyncAndBindFramebufferObject(readFramebuffer, FramebufferTarget::Read, true); - SyncAndBindFramebufferObject(drawFramebuffer, FramebufferTarget::Draw, true); - - MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1, - dstX0, dstY0, dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str()); - // See the DSA-free entry point above: only the probed defect makes this do anything. - mask &= ~BlitLayeredDestinationAspects(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, - dstY0, dstX1, dstY1, mask); - if (mask != 0) { - IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); - } - // Debug-only diagnostics: which GLES depth texture did this blit write? -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG - if (mask & GL_DEPTH_BUFFER_BIT) { - static int diagCount = 0; - if ((diagCount++ % 600) < 4) { - GLint readFbo = 0, drawFbo = 0, readDepth = 0, drawDepth = 0; - g_GLESFuncs.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFbo); - g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFbo); - g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &readDepth); - g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &drawDepth); - MGLOG_D("DBLIT readFbo=%d(depth=%d) -> drawFbo=%d(depth=%d) rect=(%d,%d,%d,%d)->(%d,%d,%d,%d)", - readFbo, readDepth, drawFbo, drawDepth, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, - dstY1); - } - } -#endif - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - - ForceBindCurrentFBO(FramebufferTarget::Read); - ForceBindCurrentFBO(FramebufferTarget::Draw); - } - Bool UpdateTextureBindingAtTarget(GLenum target) { #ifdef TRACY_ENABLE ZoneScopedNC(__func__, TRACY_ZONECOLOR_BACKEND); @@ -10864,22 +10468,6 @@ namespace MobileGL::MG_Backend::DirectGLES { (FramebufferImpl::g_alphaWidenedDrawBufferMask & (1u << drawbuffer)) != 0; } - // The same question for an explicitly named framebuffer (the DSA clears), which is NOT - // the one g_alphaWidenedDrawBufferMask describes at the point these run. - Bool IsWidenedNamedDrawBuffer(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer) { - using FBO = MG_State::GLState::FramebufferObject; - if (buffer != GL_COLOR || !framebuffer || drawbuffer < 0 || - drawbuffer >= static_cast(FBO::MAX_DRAW_BUFFERS)) { - return false; - } - const auto frontendBuf = framebuffer->GetDrawBuffers()[static_cast(drawbuffer)]; - if (frontendBuf < FramebufferAttachmentType::Color0 || - frontendBuf > FramebufferAttachmentType::Color31) { - return false; - } - return FramebufferImpl::IsAlphaWidenedColorAttachment(framebuffer->GetAttachment(frontendBuf)); - } } // namespace void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) { @@ -10924,83 +10512,6 @@ namespace MobileGL::MG_Backend::DirectGLES { SubstituteWidenedClearAlpha(value, IsWidenedBoundDrawBuffer(buffer, drawbuffer), GLuint(1), widenedValue)); } - void ClearNamedFramebufferfv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLfloat* value) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - TextureImpl::SyncNeccessaryTextures(); - RenderStateImpl::SyncRenderState(/*forColorClear=*/buffer == GL_COLOR); - - SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true); - GLfloat widenedValue[4] = {}; - value = SubstituteWidenedClearAlpha(value, IsWidenedNamedDrawBuffer(framebuffer, buffer, drawbuffer), 1.0f, - widenedValue); - g_GLESFuncs.glClearBufferfv(buffer, drawbuffer, value); - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - - ForceBindCurrentFBO(FramebufferTarget::Draw); - } - - void ClearNamedFramebufferfi(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - TextureImpl::SyncNeccessaryTextures(); - RenderStateImpl::SyncRenderState(); - - SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true); - g_GLESFuncs.glClearBufferfi(buffer, drawbuffer, depth, stencil); - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - - ForceBindCurrentFBO(FramebufferTarget::Draw); - } - - void ClearNamedFramebufferiv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLint* value) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - TextureImpl::SyncNeccessaryTextures(); - RenderStateImpl::SyncRenderState(/*forColorClear=*/buffer == GL_COLOR); - - SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true); - GLint widenedValue[4] = {}; - value = SubstituteWidenedClearAlpha(value, IsWidenedNamedDrawBuffer(framebuffer, buffer, drawbuffer), - GLint(1), widenedValue); - g_GLESFuncs.glClearBufferiv(buffer, drawbuffer, value); - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - - ForceBindCurrentFBO(FramebufferTarget::Draw); - } - - void ClearNamedFramebufferuiv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLuint* value) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - TextureImpl::SyncNeccessaryTextures(); - RenderStateImpl::SyncRenderState(/*forColorClear=*/buffer == GL_COLOR); - - SyncAndBindFramebufferObject(framebuffer, FramebufferTarget::Draw, true); - GLuint widenedValue[4] = {}; - value = SubstituteWidenedClearAlpha(value, IsWidenedNamedDrawBuffer(framebuffer, buffer, drawbuffer), - GLuint(1), widenedValue); - g_GLESFuncs.glClearBufferuiv(buffer, drawbuffer, value); - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - - ForceBindCurrentFBO(FramebufferTarget::Draw); - } - static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) { const SizeT resolvedAlignment = static_cast(std::max(alignment, 1)); return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1); @@ -12251,105 +11762,6 @@ namespace MobileGL::MG_Backend::DirectGLES { return true; } - // GetTexImage fallback for internal formats the ES driver cannot attach to a framebuffer - // (SNORM, RGB16, RGB9_E5, ...): decodes the canonical CPU shadow-mip storage into wide RGBA - // rows and repacks them into the client layout. Only valid while the shadow copy is - // authoritative, which holds for non-renderable formats (they can never be GPU-written). - static Bool GetTexImageViaShadowConversion(MG_State::GLState::TextureObjectMipmap* textureMipmapObject, - TextureUploadTarget uploadTarget, GLint level, GLsizei width, - GLsizei sliceHeight, GLsizei sliceCount, GLenum format, GLenum type, - void* pixels, Bool applyPackImageParams) { - ReadbackChannelMapping mapping{}; - if (!GetReadbackChannelMapping(format, mapping)) { - return false; - } - if (GetReadbackDstPixelSize(mapping, type) == 0) { - return false; - } - if (width <= 0 || sliceHeight <= 0 || sliceCount <= 0) { - return true; - } - const void* shadow = textureMipmapObject->MapMipmapData(uploadTarget, level); - // P4a (D-M) SAID "a split server holds no shadow to convert" AND ABORTED. That is the - // premise this row of P3b/P4b re-examined, and it is wrong in the only case that can - // get here: the level storage this converts IS the server's own, fed by the staged - // texture store, so when MapMipmapData answers the conversion is reading bytes that - // exist on this side and the answer is right. There is nothing to migrate - the - // emulation was never reaching across the split, it only looked as though it must. - // - // WHEN IT DOES NOT ANSWER, THE VERB DECLINES BY NAME (rule I) RATHER THAN ABORTING. - // std::abort inside a server that is answering a client's readback takes the whole - // session down for a texture level it happens not to hold; GL's own answer for "this - // read cannot be served" is an error code and untouched destination bytes, and a - // client that gets GL_INVALID_OPERATION can carry on. The abort also bypassed - // Session::Fail entirely, which is the funnel the census gate watches. - // - // THE FUNNEL KEEPS ITS TEETH FOR THE OTHER SITES: MGPipeUnmigratedEmulation still - // names generate-mipmap-storage and generate-mipmap-cpu-fallback, on monolith arms only - // (P8-SE dropped copy-image-shadow-mirror, a site no transport reached). Only this call - // site is retired here, and only because its own premise did not hold (texture-remint-pull - // went the same way in P9 W2, and P8-B the two split generate-mipmap names, likewise). - if (shadow == nullptr) { - MGLOG_E_ONCE("GetTexImage: no level shadow on this side for target=0x%x level=%d, and under an " - "active transport there is no client shadow to fall back to - the read is " - "DECLINED and the destination keeps its bytes", - static_cast(uploadTarget), level); - MG_Pipe::gPipeInputs.RecordError( - ErrorCode::InvalidOperation, - MakeUnique( - "DirectGLES", "GetTexImage", - "the level has no shadow on the server and there is no client shadow to convert")); - return false; - } - const auto& pixelPackBufferObject = - SplitReadbackPackBuffer(); - if (!pixelPackBufferObject && pixels == nullptr) { - return true; - } - - if (!shadow) { - return false; - } - - // glGetTexImage returns the stored texels, and for a packed internal format read with the - // matching client type the shadow word already IS the client word. Decoding it to float and - // re-encoding would canonicalize an RGB9_E5 shared exponent (0xf8fc0000 -> 0xe7e00000: the - // same value, different bits), so those pairs copy the words straight through. - if (MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer( - textureMipmapObject->GetFormat(), MG_Util::ConvertGLEnumToTextureInputFormat(format), - MG_Util::ConvertGLEnumToTexturePixelDataType(type))) { - if (!ReadbackImpl::StorePackedWordsToClient(static_cast(shadow), width, sliceHeight, - sliceCount, type, pixels, applyPackImageParams)) { - return false; - } - MGLOG_D("GetTexImage: copied %s/%s verbatim from the CPU shadow copy", - MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); - return true; - } - - Vector wide; - Bool isInteger = false; - Bool isSigned = false; - if (!MG_Util::PixelStoreProcessor::DecodeShadowDataToWideRGBA( - textureMipmapObject->GetFormat(), shadow, - static_cast(width) * static_cast(sliceHeight) * static_cast(sliceCount), - wide, isInteger, isSigned)) { - return false; - } - if (mapping.isInteger != isInteger) { - // Spec-invalid combinations are rejected with GL errors at the state layer already. - return false; - } - const GLenum wideType = isInteger ? (isSigned ? GL_INT : GL_UNSIGNED_INT) : GL_FLOAT; - if (!ReadbackImpl::StoreWideRowsToClient(wide.data(), wideType, width, sliceHeight, sliceCount, mapping, type, pixels, - applyPackImageParams)) { - return false; - } - MGLOG_D("GetTexImage: converted %s/%s from the CPU shadow copy", - MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); - return true; - } - // ---- Bit-exact readback of a 32-bit packed colour level --------------------------------------- // // glGetTexImage of a packed format read with its OWN client type owes the application the words @@ -12686,435 +12098,6 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("ReadPixels: finished"); } - // Combinations the ES driver has always handled directly for GetTexImage; everything else that maps - // to a color channel layout is repacked via ReadPixelsViaFormatConversion. - static Bool IsNativeGetTexImagePair(GLenum format, GLenum type) { - if (format == GL_RGBA) { - return type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_INT || type == GL_UNSIGNED_INT_2_10_10_10_REV || - type == GL_INT || type == GL_FLOAT || type == GL_HALF_FLOAT || - type == GL_UNSIGNED_INT_8_8_8_8_REV; - } - if (format == GL_RGBA_INTEGER) { - return type == GL_INT || type == GL_UNSIGNED_INT || type == GL_UNSIGNED_INT_2_10_10_10_REV; - } - if (format == GL_DEPTH_STENCIL || format == GL_DEPTH_COMPONENT || format == GL_STENCIL_INDEX) { - return IsSupportedDepthStencilReadPixelsPair(format, type); - } - return false; - } - - void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) { - DebugImpl::ErrorLopper errorLopper; - MGLOG_D("GetTexImage: target=%s level=%d format=%s type=%s pixels=%p", - MG_Util::ConvertGLEnumToString(target).c_str(), level, MG_Util::ConvertGLEnumToString(format).c_str(), - MG_Util::ConvertGLEnumToString(type).c_str(), pixels); - - // Unimplemented readback formats degrade to a logged no-op instead of killing the process; - // spec-invalid combinations are already rejected with GL errors at the state layer. - const Bool useNativeReadback = IsNativeGetTexImagePair(format, type); - ReadbackChannelMapping conversionMapping{}; - const Bool convertible = GetReadbackChannelMapping(format, conversionMapping) && - GetReadbackDstPixelSize(conversionMapping, type) != 0; - if (!useNativeReadback && !convertible) { - MGLOG_E_ONCE("GetTexImage: format %s with type %s is not implemented yet, skipping readback", - MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); - return; - } - - GLenum esFormat = format, esType = type; - // On little-endian hosts UNSIGNED_INT_8_8_8_8_REV has the same memory layout as UNSIGNED_BYTE. - if (esType == GL_UNSIGNED_INT_8_8_8_8_REV) esType = GL_UNSIGNED_BYTE; - - MGLOG_D("GetTexImage: SyncNeccessaryTextures()"); - TextureImpl::SyncNeccessaryTextures(); - - MGLOG_D("GetTexImage: SyncCurrentFBO()"); - FramebufferImpl::SyncCurrentFBO(); - - auto activeTextureUnit = MG_Pipe::gPipeInputs.GetActiveTextureUnit(); - MGLOG_D("GetTexImage: active texture unit = %u", activeTextureUnit); - - const auto& textureObject = MG_Pipe::gPipeInputs.GetTextureUnitObject(activeTextureUnit) - .GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)) - .GetBoundObject(); - - MGLOG_D("GetTexImage: bound texture object = %p (name=%u)", textureObject.get(), - textureObject ? textureObject->GetExternalIndex() : 0); - - // P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named debt inside the - // scope - P3b/P4b rekeys the registry onto handles. - const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; - auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); - - if (!backendTextureSlot || !*backendTextureSlot) { - MGLOG_E_ONCE("GetTexImage: No backend texture found for texture %u.", - textureObject ? textureObject->GetExternalIndex() : 0); - return; - } - - GLuint backendTexId = (*backendTextureSlot)->GetBackendTextureId(); - MGLOG_D("GetTexImage: backend texture id = %u", backendTexId); - - // Force pending rendering to complete before reading the texture back through the temp READ FBO. - // Tile-based GPUs (Mali) do not guarantee that a render into this texture through its own FBO has - // been resolved to memory when it is subsequently sampled through a *different* (temp) FBO: the - // cross-FBO glReadPixels below races the deferred tile resolve and returns the pre-render (clear) - // contents, so distinct render targets read back byte-identical (e.g. KHR-GLxx.glsl_noperspective - // fails on Mali-G715, all four programs reading as the clear colour). glGetTexImage is already a - // CPU/GPU sync point, so the extra drain is negligible; Adreno resolves eagerly and is unaffected. - g_GLESFuncs.glFinish(); - - MGLOG_D("GetTexImage: Binding temporary FBO"); - TempFBOBinder tempFBOBinder(true); - auto& tempFB = tempFBOBinder.Framebuffer(); - - MGLOG_D("GetTexImage: attaching level %d to the scratch FBO", level); - const GLenum backendAttachTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum( - MG_Util::ConvertGLEnumToTextureUploadTarget(target)); - // GL_DEPTH_STENCIL can't be attached as a color attachment (glCheckFramebufferStatus - // would report it incomplete); it has its own combined depth+stencil attachment point. - // A STENCIL_INDEX texture has a third one, GL_STENCIL_ATTACHMENT: attaching it as a - // depth attachment (or as a color one) leaves the scratch FBO incomplete, and the read - // that follows writes nothing at all. glReadBuffer only selects among color attachments, - // so it does not apply to any of the three. - const Bool depthStencilAttachment = - format == GL_DEPTH_STENCIL || format == GL_DEPTH_COMPONENT || format == GL_STENCIL_INDEX; - if (depthStencilAttachment) { - ScratchFBOImpl::EnsureDepthAttachment2D( - tempFB, GL_READ_FRAMEBUFFER, backendTexId, - backendAttachTarget == GL_UNKNOWN_MGL ? target : backendAttachTarget, level, - /*withStencil=*/format == GL_DEPTH_STENCIL, /*stencilOnly=*/format == GL_STENCIL_INDEX); - } else if (backendAttachTarget == GL_TEXTURE_3D || backendAttachTarget == GL_TEXTURE_2D_ARRAY || - backendAttachTarget == GL_TEXTURE_CUBE_MAP_ARRAY) { - // ES cannot attach 3D/array textures through glFramebufferTexture2D; layer 0 here, and - // the deeper slices one at a time in the per-layer loop below. A CUBE MAP ARRAY is in - // this list for the same reason its layer-faces are addressed as array layers: - // glFramebufferTexture2D has no target token for it, so the 2D attach it used to take - // left the scratch FBO incomplete and every read fell through to the (stale) CPU - // shadow - which is exactly the all-zero result the conformance suite saw. - ScratchFBOImpl::EnsureColorAttachmentLayer(tempFB, GL_READ_FRAMEBUFFER, backendTexId, level, 0); - } else { - ScratchFBOImpl::EnsureColorAttachment2D( - tempFB, GL_READ_FRAMEBUFFER, backendTexId, - backendAttachTarget == GL_UNKNOWN_MGL ? target : backendAttachTarget, level); - } - if (!depthStencilAttachment) { - MGLOG_D("GetTexImage: glReadBuffer(GL_COLOR_ATTACHMENT0)"); - ScratchFBOImpl::EnsureReadBuffer(tempFB, GL_COLOR_ATTACHMENT0); - } - - GLenum fbStatus = g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); - MGLOG_D("GetTexImage: GL_READ_FRAMEBUFFER status = %s", MG_Util::ConvertGLEnumToString(fbStatus).c_str()); - - // Non-renderable internal formats (SNORM, RGB16, RGB9_E5, ...) leave the temp FBO incomplete; - // those readbacks are served from the CPU shadow copy below instead of bailing out. - const Bool tempFBOComplete = fbStatus == GL_FRAMEBUFFER_COMPLETE; - - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - - MGLOG_D("GetTexImage: Applying the PACK pixel-store scope"); - ScopedPackState packParamsScope(PackStateFromContext()); - - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - - const auto& storageType = textureObject->GetStorageType(); - MGLOG_D("GetTexImage: texture storage type = %d", (int)storageType); - - if (storageType == TextureStorageType::Buffer) { - MGLOG_E_ONCE("GetTexImage: Texture storage type Buffer is not supported."); - return; - } - - auto* textureMipmapObject = static_cast(textureObject.get()); - - auto& levelRange = textureMipmapObject->GetLevelRange(); - MGLOG_D("GetTexImage: mipmap level range = [%d, %d]", levelRange.x(), levelRange.y()); - - // levelRange.y() is GL_TEXTURE_MAX_LEVEL, an inclusive level index — a single-level - // texture has range [0, 0] and level 0 must be readable. - if (static_cast(level) < levelRange.x() || static_cast(level) > levelRange.y()) { - MGLOG_E_ONCE("GetTexImage: Requested level %d is out of range (base level %u, max level %u), skipping readback", - level, levelRange.x(), levelRange.y()); - return; - } - - auto size = textureMipmapObject->GetMipmapTexelSize(MG_Util::ConvertGLEnumToTextureUploadTarget(target), level); - - // GL_TEXTURE_1D_ARRAY keeps its LAYERS in the state-side height (that is what - // glTexImage2D(GL_TEXTURE_1D_ARRAY, w, layers) means), while the ES texture behind it is a - // 2D array of height 1 with the layers in depth - GetBackendUploadSize performs exactly - // that swap on the way in. Everything below addresses the ES image, so the same swap has - // to happen here: without it the readback asked layer 0 for a `layers`-row rectangle it - // does not have, and every layer but the first came back undefined (all zeroes on Adreno, - // KHR-GL4x.shader_image_load_store.basic-allTargets-*). - const Bool oneDimensionalArray = textureObject->GetTarget() == TextureTarget::Texture1DArray; - if (oneDimensionalArray) { - size = TextureImpl::GetBackendUploadSize(TextureTarget::Texture1DArray, size); - } - - MGLOG_D("GetTexImage: mip level %d size = %dx%d", level, size.x(), size.y()); - - // Prefer the client-format conversion for every convertible combination: the "native" ES pairs - // are only guaranteed for matching attachment classes (e.g. GL_RGBA/GL_UNSIGNED_INT is invalid - // for normalized attachments), while the conversion path reads a wide format that is always - // accepted and repacks on the CPU. - if (convertible) { - // The image being read is this texture, not whatever the application left bound to - // GL_READ_FRAMEBUFFER, so the widening question has to be asked of the texture. - const Bool forceOpaqueAlpha = - TextureImpl::BackendTextureFormatAddsAlpha(textureObject->GetFormat(), textureObject->GetTarget()); - // An image-bindable texture in one of the seven normalized formats has its ES storage - // in a GL_RGBA16UI, holding the format's own channel CODES. glGetTexImage still owes - // the application the NORMALIZED value, so the conversion has to be undone here - and - // it can only be asked of the TEXTURE, which is why it is not derived from the - // attachment the scratch framebuffer happens to hold. - NormalizedImageCarrierRead normalizedCarrier; - if (const auto imageWidening = - TextureImpl::GetImageBindableStorageWidening(textureObject->GetFormat()); - imageWidening && imageWidening.CarriesNormalizedCodes() && - (*backendTextureSlot)->RequiresImageBindableStorage()) { - for (SizeT channel = 0; channel < 4; ++channel) { - normalizedCarrier.ChannelMax[channel] = imageWidening.ChannelMax[channel]; - } - normalizedCarrier.SignedNormalized = imageWidening.SignedNormalized; - } - // GL_PACK_IMAGE_HEIGHT/GL_PACK_SKIP_IMAGES only apply to 3D/array image - // readbacks (cube-map arrays address as arrays); 2D targets must ignore - // them (GL 3.3 section 6.1.4). A 1D ARRAY is one of those 2D targets: GL hands it back - // as a single two-dimensional image whose ROWS are the layers, so the layer stride is - // one packed row and the image parameters do not enter into it - even though the ES - // texture underneath is an array and is read one layer at a time. - const Bool applyPackImageParams = !oneDimensionalArray && - (backendAttachTarget == GL_TEXTURE_3D || - backendAttachTarget == GL_TEXTURE_2D_ARRAY || - backendAttachTarget == GL_TEXTURE_CUBE_MAP_ARRAY); - const GLsizei sliceCount = std::max(size.z(), 1); - const Bool multiSlice = size.z() > 1; - // glGetTexImage answers with the STORED texels, and for a packed format whose encoding - // is not unique the GPU route below cannot: it reads GL_RGBA/GL_FLOAT and re-encodes, - // which canonicalizes an RGB9_E5 shared exponent (0xf8fc0000 -> 0xe7e00000 - the same - // value 8064, different words), and the conformance suite compares the words - // ("CopyImageSubData modified contents of source image"). The scratch FBO does NOT - // decide this for us: Adreno reports an RGB9_E5 colour attachment complete, so the - // shadow branch further down was unreachable. Every other format still prefers the - // GPU, so a rendered-into texture is unaffected. - const Bool rawPackedWordRead = MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer( - textureObject->GetFormat(), MG_Util::ConvertGLEnumToTextureInputFormat(format), - MG_Util::ConvertGLEnumToTexturePixelDataType(type)); - // ...and the GPU CAN answer with the stored words after all, for any 32-bit packed - // format and whoever wrote the level, by going through a scratch GL_R32UI image (see - // ReadPackedLevelWordsViaScratch). Preferred over both routes below because it is the - // only one that is right for a level glCopyImageSubData wrote: the shadow may never - // have seen that write, and re-encoding the attachment cannot reproduce an RGB9_E5 - // shared exponent or an R11F_G11F_B10F NaN payload. A multisample image is excluded - // because copy-image requires matching sample counts. - if (rawPackedWordRead && textureObject->GetSamples() == 0) { - // Copy-image addresses a cube map as ONE image with the face on z, where - // glGetTexImage names the face in its target. - const auto readUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); - const GLint copyBaseSlice = - (readUploadTarget >= TextureUploadTarget::CubeMapPositiveX && - readUploadTarget <= TextureUploadTarget::CubeMapNegativeZ) - ? static_cast(readUploadTarget) - - static_cast(TextureUploadTarget::CubeMapPositiveX) - : 0; - const GLenum copyTarget = - TextureImpl::ConvertTextureTargetToBackendGLEnum(textureObject->GetTarget()); - const SizeT sliceWords = static_cast(size.x()) * static_cast(size.y()); - Vector words(sliceWords * static_cast(sliceCount)); - Bool allSlicesRead = true; - for (GLsizei slice = 0; slice < sliceCount && allSlicesRead; ++slice) { - allSlicesRead = ReadPackedLevelWordsViaScratch(backendTexId, copyTarget, level, - copyBaseSlice + slice, size.x(), size.y(), - words.data() + sliceWords * static_cast(slice)); - } - if (allSlicesRead && - ReadbackImpl::StorePackedWordsToClient(reinterpret_cast(words.data()), size.x(), - size.y(), sliceCount, type, pixels, - applyPackImageParams)) { - MGLOG_D("GetTexImage: finished %d slice(s) via the bit-exact GL_R32UI word readback", sliceCount); - return; - } - } - // The last resort for the one format the attachment route can never answer for: the - // shadow is only right while nothing but a glTexImage has written the level, which is - // why CopyImageSubData mirrors itself into it where it can. - const Bool verbatimPackedShadowRead = - MG_Util::PixelStoreProcessor::HasRedundantPackedEncoding(textureObject->GetFormat()) && - rawPackedWordRead; - if (verbatimPackedShadowRead && - GetTexImageViaShadowConversion(textureMipmapObject, - MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(), - size.y(), sliceCount, format, type, pixels, applyPackImageParams)) { - MGLOG_D("GetTexImage: finished via shadow conversion (verbatim packed words)"); - return; - } - // A multi-slice read used to go to the CPU shadow outright, on the grounds that the - // scratch FBO can only expose one layer at a time. But the shadow only holds what was - // uploaded, so every slice that was rendered to came back stale - which is exactly what - // a layered framebuffer produces, and what textures_storage_multisample_3d_* checks. - // Attach the layers one at a time instead and read each off the GPU, keeping the shadow - // for the formats the FBO cannot represent at all. - if (multiSlice && tempFBOComplete && - (backendAttachTarget == GL_TEXTURE_3D || backendAttachTarget == GL_TEXTURE_2D_ARRAY || - backendAttachTarget == GL_TEXTURE_CUBE_MAP_ARRAY)) { - // Each slice is packed as its own 2D image, so the per-slice call must not apply - // GL_PACK_SKIP_IMAGES / GL_PACK_IMAGE_HEIGHT itself - this walks the destination - // over them, using the same layout StoreWideRowsToClient computes. - const auto packParams = MG_Pipe::gPipeInputs.GetPixelStoreParameters(false); - const SizeT dstPixelBytes = GetReadbackDstPixelSize(conversionMapping, type); - const SizeT rowPixels = - static_cast(packParams.RowLength > 0 ? packParams.RowLength : size.x()); - const SizeT alignment = - packParams.Alignment > 0 ? static_cast(packParams.Alignment) : SizeT{1}; - const SizeT dstRowStride = (rowPixels * dstPixelBytes + alignment - 1) / alignment * alignment; - const SizeT imageRows = applyPackImageParams && packParams.ImageHeight > 0 - ? static_cast(packParams.ImageHeight) - : static_cast(size.y()); - const SizeT dstImageStride = imageRows * dstRowStride; - const SizeT skipImages = - applyPackImageParams ? static_cast(std::max(packParams.SkipImages, 0)) : SizeT{0}; - - Bool allSlicesRead = true; - for (GLsizei slice = 0; slice < sliceCount; ++slice) { - ScratchFBOImpl::EnsureColorAttachmentLayer(tempFB, GL_READ_FRAMEBUFFER, backendTexId, level, - slice); - if (g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - allSlicesRead = false; - break; - } - const SizeT sliceOffset = (skipImages + static_cast(slice)) * dstImageStride; - void* sliceDst = static_cast(pixels) + sliceOffset; - if (!ReadPixelsViaFormatConversion(0, 0, size.x(), size.y(), format, type, sliceDst, - /*honorPackImageParams=*/false, - /*applyFixedPointReadClamp=*/false, forceOpaqueAlpha, - normalizedCarrier)) { - allSlicesRead = false; - break; - } - } - // Leave the scratch FBO on layer 0 so the single-slice paths below see what they - // set up. - ScratchFBOImpl::EnsureColorAttachmentLayer(tempFB, GL_READ_FRAMEBUFFER, backendTexId, level, 0); - if (allSlicesRead) { - MGLOG_D("GetTexImage: finished %d slices via per-layer readback", sliceCount); - return; - } - } - if (multiSlice && - GetTexImageViaShadowConversion(textureMipmapObject, - MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(), - size.y(), sliceCount, format, type, pixels, applyPackImageParams)) { - MGLOG_D("GetTexImage: finished via shadow conversion"); - return; - } - if (tempFBOComplete && ReadPixelsViaFormatConversion(0, 0, size.x(), size.y(), format, type, pixels, - applyPackImageParams, - /*applyFixedPointReadClamp=*/false, - forceOpaqueAlpha, normalizedCarrier)) { - MGLOG_D("GetTexImage: finished via client-format conversion"); - return; - } - if (GetTexImageViaShadowConversion(textureMipmapObject, - MG_Util::ConvertGLEnumToTextureUploadTarget(target), level, size.x(), - size.y(), sliceCount, format, type, pixels, applyPackImageParams)) { - MGLOG_D("GetTexImage: finished via shadow conversion"); - return; - } - if (!tempFBOComplete) { - MGLOG_E_ONCE("GetTexImage: READ FBO incomplete and no shadow copy available, skipping readback"); - return; - } - MGLOG_E_ONCE("GetTexImage: format %s with type %s is not implemented yet, skipping readback", - MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str()); - return; - } - if (!tempFBOComplete) { - MGLOG_E_ONCE("GetTexImage: bound READ FBO is not complete"); - return; - } - - // The level is attached to the scratch READ framebuffer above, so the depth and - // stencil aspects are read with exactly the same helpers glReadPixels uses - native - // where the driver has it, shader sampling where it does not. ES accepts neither - // spelling natively, which is why glGetTexImage(GL_DEPTH_STENCIL) used to leave - // packed_depth_stencil.verify_get_tex_image reading its own zero-filled buffer. - // GL_STENCIL_INDEX is the third spelling of the same read and needs the same hand: - // it is not a colour format, so it has no conversion table entry either, and used to - // fall through to the native glReadPixels below, which ES refuses. - if (format == GL_DEPTH_COMPONENT && ReadPixelsDepthComponent(0, 0, size.x(), size.y(), type, pixels)) { - MGLOG_D("GetTexImage: finished via depth readback helper"); - return; - } - if (format == GL_STENCIL_INDEX && ReadPixelsStencilViaNative(0, 0, size.x(), size.y(), type, pixels)) { - MGLOG_D("GetTexImage: finished via stencil readback helper"); - return; - } - if (format == GL_DEPTH_STENCIL && ReadPixelsDepthStencilPacked(0, 0, size.x(), size.y(), type, pixels)) { - MGLOG_D("GetTexImage: finished via packed depth/stencil readback helper"); - return; - } - - // Handle PBO. The pack binding is scoped: it returns to the resting 0 state - // on every exit path, so a later readback can never land in a stale PBO. - auto& pixelPackBufferObject = - SplitReadbackPackBuffer(); - Bool usePBO = false; - GLuint packBufferId = 0; - if (pixelPackBufferObject) { - auto* backendResource = BufferImpl::EnsureBufferResource(pixelPackBufferObject); - MGLOG_D("GetTexImage: Using PBO %u", pixelPackBufferObject->GetExternalIndex()); - if (!backendResource || backendResource->id == 0) { - MGLOG_E_ONCE("GetTexImage: No backend buffer found for PBO %u.", - pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0); - return; - } - usePBO = true; - packBufferId = backendResource->id; - } else { - MGLOG_D("GetTexImage: Not using PBO"); - } - ScopedPixelPackBuffer packBufferBinding(packBufferId); - - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - MGLOG_D("GetTexImage: glReadPixels(0, 0, %d, %d, %s, %s, %p)", size.x(), size.y(), - MG_Util::ConvertGLEnumToString(esFormat).c_str(), MG_Util::ConvertGLEnumToString(esType).c_str(), - pixels); - g_GLESFuncs.glReadPixels(0, 0, size.x(), size.y(), esFormat, esType, pixels); - - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - if (usePBO) { - // pull back to client memory if PBO is used - MGLOG_D("ReadPixels: PBO used, mapping buffer to client memory"); - GLvoid* pboMappedPtr = g_GLESFuncs.glMapBufferRange( - GL_PIXEL_PACK_BUFFER, 0, (GLsizeiptr)pixelPackBufferObject->GetSize(), GL_MAP_READ_BIT); - if (pboMappedPtr) { - MGLOG_D("ReadPixels: Copying data from PBO to client memory"); - SizeT size = pixelPackBufferObject->GetSize(); - pixelPackBufferObject->WritebackFromBackend({pboMappedPtr, size}, 0); - // Serial bumped with no backend op; re-open the buffer draw-clean memos. - BufferImpl::BumpBufferMutationEpoch(); - MGLOG_D("ReadPixels: Unmapping PBO"); - g_GLESFuncs.glUnmapBuffer(GL_PIXEL_PACK_BUFFER); - } else { - MGLOG_E_ONCE("ReadPixels: glMapBufferRange returned nullptr"); - } - } - - DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); - }); - MGLOG_D("GetTexImage: finished"); - } - void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs) { g_EGLFuncs = eglFuncs; } diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 7b82ee9c..b66bfe96 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -46,8 +46,6 @@ namespace MobileGL::MG_Backend::DirectGLES { void DrawArrays(GLenum mode, GLint first, GLsizei count); void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex); void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount); - void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, - GLsizei drawcount); void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount, const GLint* basevertex); void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); @@ -71,21 +69,8 @@ namespace MobileGL::MG_Backend::DirectGLES { GLuint baseinstance); void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); void DrawArraysIndirect(GLenum mode, const void* indirect); - void ClearNamedFramebufferfv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLfloat* value); - void ClearNamedFramebufferfi(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); - void ClearNamedFramebufferiv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLint* value); - void ClearNamedFramebufferuiv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLuint* value); void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - void BlitNamedFramebuffer(const SharedPtr& readFramebuffer, - const SharedPtr& drawFramebuffer, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLbitfield mask, GLenum filter); void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, @@ -98,7 +83,6 @@ namespace MobileGL::MG_Backend::DirectGLES { void GenerateMipmap(GLenum target); const GLubyte* GetString(GLenum name); void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); - void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); void DispatchComputeIndirect(GLintptr indirect); void MemoryBarrier(GLbitfield barriers); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 2a30c4a1..f0616b45 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -10389,91 +10389,6 @@ namespace MobileGL::MG_Backend::DirectGLES { m_syncedAttachmentRecordHash = 0; } - static Bool SyncAttachmentObject(GLenum glFBOTarget, - const MG_State::GLState::FramebufferAttachmentObject& attachmentObject, - GLenum glBackendAttachment) { - if (attachmentObject.IsTexture()) { - const auto& textureObject = attachmentObject.GetTexture(); - SharedPtr backendTextureObject; - // P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on - // a first attach), named debt inside the scope - P3b/P4b rekeys the registry. - const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; - if (auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get())) { - backendTextureObject = *backendTextureSlot; - } else { - auto& newTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject); - if (!newTextureSlot) { - newTextureSlot = MakeShared(); - } - backendTextureObject = newTextureSlot; - } - if (!backendTextureObject) { - MGLOG_E_ONCE("%s: No backend texture found for FBO attachment, cannot bind texture.", __func__); - return false; - } - backendTextureObject->SyncMipmapsToBackend(textureObject); - // From here on every draw, clear and blit into this framebuffer can write the - // texture behind the shadow's back. - TextureImpl::NoteDriverSideTextureWrite(textureObject); - if (attachmentObject.IsLayered()) { - g_GLESFuncs.glFramebufferTexture(glFBOTarget, glBackendAttachment, - backendTextureObject->GetBackendTextureId(), - static_cast(attachmentObject.GetTextureLevel())); - } else if (const auto uploadTarget = attachmentObject.GetTextureUploadTarget(); - uploadTarget == TextureUploadTarget::Texture3D || - uploadTarget == TextureUploadTarget::Texture2DArray || - uploadTarget == TextureUploadTarget::Texture1DArray || - uploadTarget == TextureUploadTarget::CubeMapArray || - uploadTarget == TextureUploadTarget::Texture2DMultisampleArray) { - // Single slice/layer of a 3D or array texture: ES has no - // glFramebufferTexture3D, layers attach via glFramebufferTextureLayer. - g_GLESFuncs.glFramebufferTextureLayer(glFBOTarget, glBackendAttachment, - backendTextureObject->GetBackendTextureId(), - static_cast(attachmentObject.GetTextureLevel()), - static_cast(attachmentObject.GetTextureLayer())); - } else { - auto glTextureTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum( - attachmentObject.GetTextureUploadTarget()); - if (glTextureTarget == GL_UNKNOWN_MGL) { - glTextureTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(textureObject->GetTarget()); - } - // glBindTexture rejects cube-face enums (INVALID_ENUM with no - // bind, while Bind() would still record the cube-map cache slot - // as bound): bind via the owning cube target; the attach below - // keeps the face target. - const Bool isCubeFace = glTextureTarget >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && - glTextureTarget <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; - backendTextureObject->Bind(isCubeFace ? GL_TEXTURE_CUBE_MAP : glTextureTarget); - g_GLESFuncs.glFramebufferTexture2D(glFBOTarget, glBackendAttachment, glTextureTarget, - backendTextureObject->GetBackendTextureId(), - static_cast(attachmentObject.GetTextureLevel())); - } - } else if (attachmentObject.IsRenderbuffer()) { - const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); - // P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution (and the mint on - // a first attach), named debt inside the scope - P3b/P4b rekeys the registry. - const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; - SharedPtr backendRenderbufferObject; - if (auto* backendRenderbufferSlot = - RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get())) { - backendRenderbufferObject = *backendRenderbufferSlot; - } else { - auto& newRenderbufferSlot = - RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreate(renderbufferObject); - if (!newRenderbufferSlot) { - newRenderbufferSlot = MakeShared(); - } - backendRenderbufferObject = newRenderbufferSlot; - } - - backendRenderbufferObject->SyncToBackend(renderbufferObject); - backendRenderbufferObject->Bind(); - g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER, - backendRenderbufferObject->GetBackendRenderbufferId()); - } - return true; - } - static Bool IsSnormFormat(TextureInternalFormat format) { switch (format) { case TextureInternalFormat::R8Snorm: @@ -10502,53 +10417,6 @@ namespace MobileGL::MG_Backend::DirectGLES { } } - static Bool IsSnormFallbackAttachment( - const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) { - if (attachmentObject.IsTexture()) { - const auto& textureObject = attachmentObject.GetTexture(); - return textureObject && IsSnormFormat(textureObject->GetFormat()) && - TextureImpl::ShouldUseCaveatTextureFormat(textureObject->GetFormat(), textureObject->GetTarget()); - } - if (attachmentObject.IsRenderbuffer()) { - const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); - return renderbufferObject && - IsSnormFormat(renderbufferObject->GetInternalFormat()) && - TextureImpl::ShouldUseCaveatRenderbufferFormat(renderbufferObject->GetInternalFormat()); - } - return false; - } - - static Bool IsUnormFallbackAttachment( - const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) { - if (attachmentObject.IsTexture()) { - const auto& textureObject = attachmentObject.GetTexture(); - return textureObject && IsUnormFormat(textureObject->GetFormat()) && - TextureImpl::ShouldUseCaveatTextureFormat(textureObject->GetFormat(), textureObject->GetTarget()); - } - if (attachmentObject.IsRenderbuffer()) { - const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); - return renderbufferObject && - IsUnormFormat(renderbufferObject->GetInternalFormat()) && - TextureImpl::ShouldUseCaveatRenderbufferFormat(renderbufferObject->GetInternalFormat()); - } - return false; - } - - Bool IsAlphaWidenedColorAttachment( - const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) { - if (attachmentObject.IsTexture()) { - const auto& textureObject = attachmentObject.GetTexture(); - return textureObject && TextureImpl::BackendTextureFormatAddsAlpha(textureObject->GetFormat(), - textureObject->GetTarget()); - } - if (attachmentObject.IsRenderbuffer()) { - const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); - return renderbufferObject && - TextureImpl::BackendRenderbufferFormatAddsAlpha(renderbufferObject->GetInternalFormat()); - } - return false; - } - Uint32 g_alphaWidenedDrawBufferMask = 0; Uint32 g_integerColorDrawBufferMask = 0; @@ -10585,19 +10453,6 @@ namespace MobileGL::MG_Backend::DirectGLES { } } - static Bool IsIntegerColorAttachment( - const MG_State::GLState::FramebufferAttachmentObject& attachmentObject) { - if (attachmentObject.IsTexture()) { - const auto& textureObject = attachmentObject.GetTexture(); - return textureObject && IsIntegerColorFormat(textureObject->GetFormat()); - } - if (attachmentObject.IsRenderbuffer()) { - const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); - return renderbufferObject && IsIntegerColorFormat(renderbufferObject->GetInternalFormat()); - } - return false; - } - // THE FOUR CROSS-OBJECT MASKS, ANSWERED FROM THE RECORD (ID-12 DV-5, review M-3 / DV-5). // // All four reduce to (internal format, TEXTURE TARGET) for a texture attachment and to @@ -10850,23 +10705,6 @@ namespace MobileGL::MG_Backend::DirectGLES { return false; } - Uint32 ComputeAlphaWidenedDrawBufferMask(const MG_State::GLState::FramebufferObject& fbo) { - using FBO = MG_State::GLState::FramebufferObject; - const auto& drawBuffers = fbo.GetDrawBuffers(); - Uint32 mask = 0; - for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS && i < 32; ++i) { - const auto frontendBuf = drawBuffers[i]; - if (frontendBuf < FramebufferAttachmentType::Color0 || - frontendBuf > FramebufferAttachmentType::Color31) { - continue; - } - if (IsAlphaWidenedColorAttachment(fbo.GetAttachment(frontendBuf))) { - mask |= (1u << i); - } - } - return mask; - } - // The read attachment's storage carries an alpha channel its frontend format does not // (the three-channel colour-renderable widening). GL answers such a read with 1.0, but // the storage holds whatever the draw wrote there, so the readback has to overwrite it. @@ -11011,49 +10849,6 @@ namespace MobileGL::MG_Backend::DirectGLES { ApplyReadBufferFromRecord(*record, 0); } - void BackendFramebufferObject::SyncReadBufferToBackend( - const SharedPtr& stateFBOObject) { - if (!stateFBOObject) { - return; - } - // P4a (D-C2): the READ buffer is answered from the RESOLVED read surface of the READ - // framebuffer's own record, which is what structurally closes the read-buffer - // shared-FBO defect class. The comment two lines below - "when this is reached from - // SyncCurrentFBO's 'same FBO as draw' skip path" - describes a hazard that becomes - // unrepresentable: the record says which target it is, and ReadSurface was resolved - // from the read framebuffer's own read buffer before it ever crossed. - // - // The record carries the resolved SURFACE and not an index, so the attachment POINT - // is recovered by matching it against Color[] - the one place both spellings sit - // side by side. An empty surface (Res == the null handle, which D-C1 makes the - // spelling of "no attachment" independently of Kind's numbering) is - // FramebufferAttachmentType::None, i.e. GL_NONE. A surface that matches no colour - // point cannot be a legal read buffer and is refused rather than guessed. - // - // P5e (fb): THAT DECISION now lives in ApplyReadBufferFromRecord, which takes no - // frontend object; what is left in this overload is the monolith glue that finds - // the handle by identity. Under an active transport nothing reaches it - - // SyncToBackendByHandle runs the record form directly. - // P5c (hd): with an active transport the handle is the caller's - // (m_pushedSyncHandle), never the client allocator's - see SyncToBackend. - const MG_Pipe::MGPipeHandle fbo = - m_pushedSyncHandle; - // ID-19: the OBJECT's record, not "the record of whatever is bound to READ". A - // framebuffer whose read buffer is being pushed need not be the read binding at - // all - glNamedFramebufferReadBuffer and the DSA clears reach here by name - and - // ReadSurface is resolved from THIS framebuffer's own read buffer under every - // Target, Named included (c0e's MGPFramebufferState comment). - const auto* record = PushedFramebufferRecord(fbo); - if (record == nullptr) { - MGLOG_E_ONCE("MGPipe: framebuffer %u has no applier record on the handle arm, so " - "its read buffer cannot be pushed (handle {%u, %u})", - stateFBOObject->GetExternalIndex(), fbo.Slot, fbo.Gen); - return; - } - ApplyReadBufferFromRecord(*record, stateFBOObject->GetExternalIndex()); - return; - } - Bool BackendFramebufferObject::RecomputeBackendColorSlots( const FramebufferObject::FramebufferAttachmentArray& stateDrawBuffers) { // Only the first GL_MAX_COLOR_ATTACHMENTS points exist in the backend. The frontend's own @@ -11143,380 +10938,6 @@ namespace MobileGL::MG_Backend::DirectGLES { return moved; } - void BackendFramebufferObject::SyncToBackend( - const SharedPtr& stateFBOObject, FramebufferTarget asTarget) { -#ifdef TRACY_ENABLE - ZoneScopedC(TRACY_ZONECOLOR_BACKEND); -#endif - if (!stateFBOObject) { - MGLOG_E_ONCE("State FBO object is null, cannot sync to backend."); - return; - } - MGLOG_D("Syncing FBO with backend ID %u to backend for state ID %u, as %s FBO", m_backendFBOId, - stateFBOObject->GetExternalIndex(), (asTarget == FramebufferTarget::Draw ? "DRAW" : "READ")); - GLenum glFBOTarget = MG_Util::ConvertFramebufferTargetToGLEnum(asTarget); - - // P4a (D-C2 as corrected by ID-19): THE RECORD OF THIS FRAMEBUFFER OBJECT, resolved - // BEFORE the driver framebuffer is bound. - // - // The order is the whole of C-1's fix. v1 bound first and refused afterwards, so a - // glClearNamedFramebufferfv / glBlitNamedFramebuffer on a framebuffer that is bound - // to neither target left this twin's freshly minted driver FBO bound with NO - // ATTACHMENTS and the caller then issued the clear or the blit against it - - // GL_INVALID_FRAMEBUFFER_OPERATION and nothing cleared, where the legacy arm cleared - // correctly. What the reorder buys is exactly this and no more: THE REFUSAL ITSELF - // HAS NO DRIVER EFFECT - it no longer mints and binds a driver FBO as a side effect - // of declining. It does NOT decide the driver's binding (review N-1): every caller - // binds immediately afterwards and none of them looks at a return value - - // SyncAndBindFramebufferObject is SyncToBackend then an unconditional Bind(target) - // (DirectGLES.cpp:3292-3293, the path all five DSA entry points take), and - // BindCurrentFBO binds the current FBO's twin whether or not SyncCurrentFBO synced - // it. So an unconfigured framebuffer can still end up bound; what the log line - // promises is only that this function did not put it there. - // - // The record is per FRAMEBUFFER OBJECT and is found by handle, so it describes this - // object whether it is bound to Draw, to Read, to both or to neither, and its own - // Target is NOT compared against `asTarget`: a Named record legitimately names no - // binding at all. What is still gated on `asTarget` is what reaches the DRIVER's - // bound target - glDrawBuffers and the four cross-object masks below, which stay - // Draw-only for the reason the OIT comment gives. - const MG_Pipe::MGPFramebufferState* pushedRecord = nullptr; - FramebufferObject::FramebufferAttachmentArray pushedDrawBuffers{}; - // MONOLITH GLUE, named as such: the Framebuffer handle of an object this backend - // still arrives holding. A framebuffer has a handle but NO wire lifetime (D-I2) - - // there is no framebuffer create or destroy in the catalogue and none is invented - // - so the handle exists purely to key set_framebuffer_state, which is exactly - // what it is used for here. - // - // P5c (hd): with an active transport the handle is the one the caller is applying - // (m_pushedSyncHandle - the record-driven sync set it), and the client allocator - // is never probed (T2). A null there means a caller reached this arm without a - // record, which the null-record refusal below names. - const MG_Pipe::MGPipeHandle fbo = - m_pushedSyncHandle; - pushedRecord = PushedFramebufferRecord(fbo); - if (pushedRecord == nullptr) { - MGLOG_E_ONCE("MGPipe: framebuffer %u has no applier record on the handle arm, so it " - "is not configured here; nothing is bound by this refusal, but the " - "caller's own bind still targets it (handle {%u, %u})", - stateFBOObject->GetExternalIndex(), fbo.Slot, fbo.Gen); - return; - } - DecodePushedDrawBuffers(*pushedRecord, pushedDrawBuffers.data()); - Bind(asTarget); - - // -------------------- Connect attachments (set buffers) ----------------------- - // 1. Remap draw buffers - const FramebufferObject::FramebufferAttachmentArray& stateDrawBuffers = - pushedRecord != nullptr ? pushedDrawBuffers : stateFBOObject->GetDrawBuffers(); - Bool drawBufferClean = false; - if (memcmp(m_frontendDrawBuffers, stateDrawBuffers.data(), - FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType)) == 0) { - drawBufferClean = true; - } - - // glDrawBuffers writes the state of the FBO bound to GL_DRAW_FRAMEBUFFER. - // When this object is only bound as the READ target the call would land on - // whatever framebuffer is draw-bound AND falsely stamp this object's memo, - // so the later draw-target sync skips as "clean" while the real state is - // stale (Minecraft 26.x OIT: the scratch clear-FBO kept draw buffers NONE - // from its blit-destination configuration, silently dropping every - // offscreen color clear). - if (!drawBufferClean && asTarget == FramebufferTarget::Draw) { - memcpy(m_frontendDrawBuffers, stateDrawBuffers.data(), - FramebufferObject::MAX_DRAW_BUFFERS * sizeof(FramebufferAttachmentType)); - std::fill(m_backendDrawBuffers, m_backendDrawBuffers + FramebufferObject::MAX_DRAW_BUFFERS, GL_NONE); - int nEffectiveBuffers = 0; - for (GLint i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS; ++i) { - auto& frontendBuf = stateDrawBuffers[i]; - if (frontendBuf == FramebufferAttachmentType::None) { - m_backendDrawBuffers[i] = GL_NONE; - continue; - } - - // Create compacted mapping - if (frontendBuf == FramebufferAttachmentType::FrontLeft || - frontendBuf == FramebufferAttachmentType::FrontRight || - frontendBuf == FramebufferAttachmentType::BackLeft || - frontendBuf == FramebufferAttachmentType::BackRight) { - MGLOG_D("%s: frontend buf token found for default fbo, shouldn't remap", __func__); - m_backendDrawBuffers[i] = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendBuf); - } else { - m_backendDrawBuffers[i] = GL_COLOR_ATTACHMENT0 + i; - } - nEffectiveBuffers = i + 1; - } - g_GLESFuncs.glDrawBuffers(nEffectiveBuffers, m_backendDrawBuffers); - // The line above pinned backend point s to draw-buffer slot s, so the images have to - // be moved under those points. Rebuild the whole colour map and, when anything moved, - // also drop the read-buffer memo: SyncReadBufferToBackend keys it on the frontend - // enum alone, which does not change when the point under it does. - if (RecomputeBackendColorSlots(stateDrawBuffers)) { - m_frontendReadBuffer = FramebufferAttachmentType::Unknown; - } - MGLOG_D("DBAPPLY beFbo=%u target=%d n=%d db0=0x%x feDb0=%d", m_backendFBOId, (int)asTarget, - nEffectiveBuffers, m_backendDrawBuffers[0], (int)stateDrawBuffers[0]); - } - - if (asTarget == FramebufferTarget::Draw) { - Uint32 snormClampOutputMask = 0; - Uint32 unormClampOutputMask = 0; - Uint32 alphaWidenedMask = 0; - Uint32 integerColorMask = 0; - for (Uint i = 0; i < FramebufferObject::MAX_DRAW_BUFFERS && i < 32; ++i) { - const auto frontendBuf = stateDrawBuffers[i]; - if (frontendBuf < FramebufferAttachmentType::Color0 || - frontendBuf > FramebufferAttachmentType::Color31) { - continue; - } - // ID-12 DV-5 / M-3: on the handle arm the four masks are answered from the - // record's own surface for the point this draw buffer names, and the frontend - // attachment object below is never touched - the whole arm returns here. - // A draw buffer that names a point the record cannot describe is D-C3's - // refusal arriving too late to refuse, so it is loud and contributes no bit. - if (pushedRecord != nullptr) { - const MG_Pipe::MGPSurface* pushedSurface = - PushedSurfaceForAttachment(*pushedRecord, frontendBuf); - if (pushedSurface == nullptr) { - MGLOG_E_ONCE("MGPipe: framebuffer %u's draw buffer %u names colour point %d, " - "which its record does not describe (the record carries %u " - "colour points) - contributing no fallback mask bit", - stateFBOObject->GetExternalIndex(), i, - static_cast(frontendBuf) - - static_cast(FramebufferAttachmentType::Color0), - MG_Pipe::kMGPipeMaxColorAttachments); - continue; - } - if (IsSnormFallbackSurface(*pushedSurface)) { - snormClampOutputMask |= (1u << i); - } else if (IsUnormFallbackSurface(*pushedSurface)) { - unormClampOutputMask |= (1u << i); - } - if (IsAlphaWidenedColorSurface(*pushedSurface)) { - alphaWidenedMask |= (1u << i); - } - if (IsIntegerColorSurface(*pushedSurface)) { - integerColorMask |= (1u << i); - } - continue; - } - const auto& attachmentObject = stateFBOObject->GetAttachment(frontendBuf); - if (IsSnormFallbackAttachment(attachmentObject)) { - snormClampOutputMask |= (1u << i); - } else if (IsUnormFallbackAttachment(attachmentObject)) { - unormClampOutputMask |= (1u << i); - } - // Independent of the two above: a widened attachment can be SNORM - // (GL_RGB8_SNORM -> GL_RGBA16F, which also clamps) or not (GL_SRGB8 -> - // GL_SRGB8_ALPHA8, which does not), so it gets its own bit rather than an - // `else if` branch of theirs. - if (IsAlphaWidenedColorAttachment(attachmentObject)) { - alphaWidenedMask |= (1u << i); - } - if (IsIntegerColorAttachment(attachmentObject)) { - integerColorMask |= (1u << i); - } - } - PrgramImpl::g_snormFallbackClampOutputMask = snormClampOutputMask; - PrgramImpl::g_unormFallbackClampOutputMask = unormClampOutputMask; - g_alphaWidenedDrawBufferMask = alphaWidenedMask; - g_integerColorDrawBufferMask = integerColorMask; - } - - // 2. Remap read buffer. glReadBuffer writes the READ-bound FBO's state, so - // only apply (and stamp the memo) when this object is bound as READ. - if (asTarget == FramebufferTarget::Read) { - SyncReadBufferToBackend(stateFBOObject); - } - - // -------------------- Attach texture to backend FBO ----------------------- - // A backend texture id was re-minted since this twin's last walk - // (RecreateBackendTexture): any point here may still hold the dead id while - // its frontend attachment version is unchanged, so the memo below would skip - // exactly the attachment that needs repair. Re-arm every point first. - if (m_syncedBackendIdGeneration != g_attachmentBackendIdGeneration) { - std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(), - static_cast(~0u)); - m_syncedBackendIdGeneration = g_attachmentBackendIdGeneration; - } - // P4a (D-C4): the record's ContentHash is what says this framebuffer's resolved state - // moved, and it REPLACES the frontend attachment versions AS A KEY. It covers every - // field the record carries, so an attachment set that moved, a draw-buffer array - // that moved, an extent that moved and a recycled Fbo whose successor happens to - // carry an identical attachment set are all one compare - and it moves in ONE place - // rather than in an array of 41 the twin had to walk. - // - // The re-arm is expressed through m_syncedFrontendAttachmentVersions rather than - // around it, so the attachment loop below is unchanged on both arms: a moved record - // arms every point and the loop then does exactly what it does today, including the - // empty-colour-point detach that keeps m_backendColorSlots a permutation of the - // PHYSICAL layout. - // - // OVER-FIRING IS FREE AND UNDER-FIRING IS FATAL, so the per-attachment versions stay - // as a second, narrower gate underneath: a frontend attachment that moved without - // the record moving still re-attaches. That direction is the safe one and it is the - // reason the array is re-armed rather than retired here. It is what the array is FOR - // now: the walk below resolves each attachment from the record (SyncAttachmentSurface), - // so the versions no longer say WHICH object a point holds - they say only "this point - // may have moved since I last looked", and they retire with the frontend attachment - // array itself, which is a later phase's. - if (pushedRecord != nullptr && m_syncedRecordHashes[SizeT(asTarget)] != pushedRecord->ContentHash) { - std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(), - static_cast(~0u)); - m_syncedRecordHashes[SizeT(asTarget)] = pushedRecord->ContentHash; - } - const auto& attachments = stateFBOObject->GetAllAttachmentObjects(); - const auto& attachmentVersions = stateFBOObject->GetAllFramebufferAttachmentVersions(); - for (SizeT i = 0; i < attachments.size(); ++i) { - const auto& attachmentObject = attachments[i]; - auto frontendType = static_cast(i); - GLenum glBackendAttachment = GL_NONE; - if (frontendType >= FramebufferAttachmentType::Color0 && - frontendType <= FramebufferAttachmentType::Color31) - glBackendAttachment = GetBackendAttachmentType(frontendType); - else - glBackendAttachment = MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendType); - - // relevant FRONTEND!!! version should be checked and updated - if (m_syncedFrontendAttachmentVersions[i] != attachmentVersions[i]) { - // SyncAttachmentObject only ever attaches: for an empty frontend attachment it - // returns true and issues nothing, so the point keeps whatever was there. That is - // what makes m_backendColorSlots a permutation of the PHYSICAL layout rather than - // a claim about one - a point handed to an attachment with no image would - // otherwise still hold the previous owner's image and glReadBuffer would return - // it. Bounded by GL_MAX_COLOR_ATTACHMENTS because GL_COLOR_ATTACHMENTn above the - // driver's limit is INVALID_ENUM, and restricted to colour points because - // FRONT_LEFT/BACK_LEFT and co. are not ES attachment points at all. - const Bool isColorPoint = - frontendType >= FramebufferAttachmentType::Color0 && - frontendType <= FramebufferAttachmentType::Color31 && - (static_cast(frontendType) - static_cast(FramebufferAttachmentType::Color0)) < - g_GLESCapabilities.MaxColorAttachments; - // P5e (fb, §5.4): ON THE RECORD ARM THE EMPTY POINT IS THE RECORD'S, and it - // is the SAME test SyncAttachmentSurface attaches on - `Kind == None || - // Res null`. Asking the frontend here and the record there is what left - // review N-6's one-directional hole; one test closes it by construction. - const Bool pointIsEmpty = - pushedRecord != nullptr - ? [&] { - const MG_Pipe::MGPSurface* s = - PushedSurfaceForAttachment(*pushedRecord, frontendType); - return s == nullptr || s->Kind == MG_Pipe::kMGPipeSurfaceKindNone || - MG_Pipe::MGPipeHandleIsNull(s->Res); - }() - : attachmentObject.IsEmpty(); - if (isColorPoint && pointIsEmpty && glBackendAttachment != GL_NONE) { - g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER, 0); - } - // M-3: the ATTACHMENT RESOLUTION is the record's on the handle arm. The walk - // itself still runs over the frontend's 41 points, because that is what says - // which points EXIST and it is also what carries the per-attachment version - // memo underneath the record hash; what the record answers is WHICH object - // each point holds and in what shape. - // - // A point the record does not describe (Color8..Color31; the FRONT/BACK - // tokens) is only reachable with a non-empty attachment if D-C3's refusal - // failed to fire, so an empty one is silently fine and a live one is loud. - Bool attachmentSynced; - if (pushedRecord != nullptr) { - const MG_Pipe::MGPSurface* pushedSurface = - PushedSurfaceForAttachment(*pushedRecord, frontendType); - if (pushedSurface == nullptr) { - if (attachmentObject.IsEmpty()) { - attachmentSynced = true; - } else { - MGLOG_E_ONCE("MGPipe: framebuffer %u holds an attachment at point %s, which " - "its record does not describe - refusing to attach it", - stateFBOObject->GetExternalIndex(), - MG_Util::ConvertFramebufferAttachmentTypeToString(frontendType).c_str()); - attachmentSynced = false; - } - } else { - attachmentSynced = - SyncAttachmentSurface(glFBOTarget, *pushedSurface, glBackendAttachment, - &attachmentObject); - } - } else { - attachmentSynced = SyncAttachmentObject(glFBOTarget, attachmentObject, glBackendAttachment); - } - if (attachmentSynced) { - m_syncedFrontendAttachmentVersions[i] = attachmentVersions[i]; - } - } -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG - else { - MGLOG_D("%s: Skipped SyncAttachmentObject(target=%s, frontendObj=(%dx%dx%d, %s), backendAtt=%s), " - "version = %u", - __func__, MG_Util::ConvertGLEnumToString(glFBOTarget).c_str(), - attachmentObject.GetSize().x(), attachmentObject.GetSize().y(), - attachmentObject.GetSize().z(), - MG_Util::ConvertFramebufferAttachmentTypeToString(frontendType).c_str(), - MG_Util::ConvertGLEnumToString(glBackendAttachment).c_str(), - m_syncedFrontendAttachmentVersions[i]); - if (!attachmentObject.IsTexture() && !attachmentObject.IsRenderbuffer()) { - continue; - } - GLint objectType = GL_NONE; - g_GLESFuncs.glGetFramebufferAttachmentParameteriv( - glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &objectType); - MOBILEGL_ASSERT((objectType == GL_NONE) || - (attachmentObject.IsTexture() && objectType == GL_TEXTURE) || - (attachmentObject.IsRenderbuffer() && objectType == GL_RENDERBUFFER), - "Attachment type not match!"); - GLint objectName = 0; - g_GLESFuncs.glGetFramebufferAttachmentParameteriv( - glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &objectName); - // Verify that the backend object's name and parameters match the frontend attachment state - if (attachmentObject.IsTexture()) { - const auto& textureObject = attachmentObject.GetTexture(); - // P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named - // debt inside the scope - P3b/P4b rekeys the registry. - const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; - auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); - MOBILEGL_ASSERT(backendTextureSlot != nullptr && *backendTextureSlot != nullptr, - "No backend texture found while framebuffer reports texture attachment."); - GLuint backendTexId = (*backendTextureSlot)->GetBackendTextureId(); - MOBILEGL_ASSERT(static_cast(backendTexId) == objectName, - "Attachment texture name mismatch between GLES (%d) and backend texture object " - "(%d), frontend texture object ID=%d.", - objectName, backendTexId, textureObject->GetExternalIndex()); - - GLint texLevel = 0; - g_GLESFuncs.glGetFramebufferAttachmentParameteriv( - glFBOTarget, glBackendAttachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &texLevel); - MOBILEGL_ASSERT(texLevel == static_cast(attachmentObject.GetTextureLevel()), - "Attachment texture level mismatch between GLES and state object."); - } else if (attachmentObject.IsRenderbuffer()) { - const auto& renderbufferObject = attachmentObject.GetRenderbuffer(); - // P5c (G6, CONTRACT-P5C §5.4): frontend-keyed twin resolution, named - // debt inside the scope - P3b/P4b rekeys the registry. - const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry; - auto* backendRboSlot = - RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get()); - MOBILEGL_ASSERT( - backendRboSlot != nullptr && *backendRboSlot != nullptr, - "No backend renderbuffer found while framebuffer reports renderbuffer attachment."); - GLuint backendRboId = (*backendRboSlot)->GetBackendRenderbufferId(); - MOBILEGL_ASSERT(static_cast(backendRboId) == objectName, - "Attachment renderbuffer name mismatch between GLES and state object."); - } - } -#endif - } - - // The walk itself can re-mint an id (SyncAttachmentObject -> - // SyncMipmapsToBackend -> RecreateBackendTexture), invalidating points this - // walk already attached or version-skipped - e.g. one texture attached at two - // points. Re-enter until the generation is quiescent: every pass syncs each - // dirty texture clean, so each repeat finds strictly fewer re-mints and the - // common case (no re-mint) never takes a second pass. The head's draw/read- - // buffer syncs are memoized against their own shadows, so a repeat re-walks - // only the attachments. - if (m_syncedBackendIdGeneration != g_attachmentBackendIdGeneration) { - SyncToBackend(stateFBOObject, asTarget); - } - } - // ---- P5e (fb, CONTRACT-P5E.md §5.4): the reverse index, texture -> framebuffers ------ // // WHY IT EXISTS. ScopedDetachedTextureFramebufferAttachments used to answer "which diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 3e5482ac..6fb04298 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -2324,8 +2324,6 @@ namespace MobileGL::MG_Backend::DirectGLES { ~BackendFramebufferObject(); BackendFramebufferObject(const BackendFramebufferObject&) = delete; BackendFramebufferObject& operator=(const BackendFramebufferObject&) = delete; - void SyncToBackend(const SharedPtr& stateFBOObject, - FramebufferTarget asTarget); // P5e SEAM (MG_Remote/CONTRACT-P5E.md §5.4; declared by c0e, bodied by fb): the same // sync keyed on the framebuffer HANDLE. The record's eleven surfaces ARE the point // set - the emitter refuses a point at or above the wire width - so the attachment @@ -2333,10 +2331,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // handle is the argument. An OVERLOAD rather than a changed signature, so the // monolith arm and the pull build see no token move. void SyncToBackendByHandle(MG_Pipe::MGPipeHandle fbo, FramebufferTarget asTarget); - // Apply only this FBO's read buffer (glReadBuffer) to the backend. Split out so it can - // still run when SyncCurrentFBO skips the READ-target sync because the same GL FBO is - // bound as both draw and read (otherwise glReadBuffer changes would be silently dropped). - void SyncReadBufferToBackend(const SharedPtr& stateFBOObject); // P5e (fb, §5.4): the same read-buffer push keyed on the handle, for the one path // that applies a read buffer without doing the rest of the sync - SyncCurrentFBO's // "one object is bound to BOTH bindings" skip, where the DRAW pass already did the @@ -2514,15 +2508,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // GL_CLAMP_READ_COLOR rule the clamp above implements. Bool IsAlphaWidenedFallbackReadAttachment(); - // True when this attachment's storage carries an alpha channel its frontend format does - // not (the three-channel colour-renderable widening). - Bool IsAlphaWidenedColorAttachment(const MG_State::GLState::FramebufferAttachmentObject& attachmentObject); - - // Bit i set = DRAW BUFFER i of `fbo` resolves to a colour attachment the backend widened - // from three channels to four. Indexed by draw-buffer slot, not by attachment point, - // because that is what glColorMaski / glClearBufferfv address. - Uint32 ComputeAlphaWidenedDrawBufferMask(const MG_State::GLState::FramebufferObject& fbo); - // The same mask for whatever is currently bound to GL_DRAW_FRAMEBUFFER, recomputed by // SyncCurrentFBO (BackendFramebufferObject::SyncToBackend for the DRAW target, and reset // to 0 on the default framebuffer). Read by the draw/clear state sync, so it is only diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index e10741f7..74813f91 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -987,7 +987,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { funcsTable.GL.DrawElements = DrawElements; funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex; funcsTable.GL.MultiDrawArrays = MultiDrawArrays; - funcsTable.GL.MultiDrawElements = MultiDrawElements; funcsTable.GL.MultiDrawElementsBaseVertex = MultiDrawElementsBaseVertex; funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect; funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect; @@ -1008,19 +1007,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { funcsTable.GL.ClearBufferfv = ClearBufferfv; funcsTable.GL.ClearBufferuiv = ClearBufferuiv; funcsTable.GL.ClearBufferiv = ClearBufferiv; - funcsTable.GL.ClearNamedFramebufferfv = ClearNamedFramebufferfv; - funcsTable.GL.ClearNamedFramebufferfi = ClearNamedFramebufferfi; - funcsTable.GL.ClearNamedFramebufferiv = ClearNamedFramebufferiv; - funcsTable.GL.ClearNamedFramebufferuiv = ClearNamedFramebufferuiv; funcsTable.GL.BlitFramebuffer = BlitFramebuffer; - funcsTable.GL.BlitNamedFramebuffer = BlitNamedFramebuffer; funcsTable.GL.CopyTexImage2D = CopyTexImage2D; funcsTable.GL.CopyTexSubImage2D = CopyTexSubImage2D; funcsTable.GL.CopyImageSubData = CopyImageSubData; funcsTable.GL.GenerateMipmap = GenerateMipmap; funcsTable.GL.ReadPixels = ReadPixels; - funcsTable.GL.GetTexImage = GetTexImage; - funcsTable.GL.GetTextureImage = GetTextureImage; funcsTable.GL.DispatchCompute = DispatchCompute; funcsTable.GL.DispatchComputeIndirect = DispatchComputeIndirect; funcsTable.GL.MemoryBarrier = MemoryBarrier; diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 20a3ba34..597f9466 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -312,34 +312,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { pVulkanRenderer->ClearBufferiv(buffer, drawbuffer, value); } - void ClearNamedFramebufferfv(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, const GLfloat* value) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfv called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferfv called with null GL context"); - pVulkanRenderer->ClearNamedFramebufferfv(framebuffer, buffer, drawbuffer, value); - } - - void ClearNamedFramebufferiv(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, const GLint* value) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferiv called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferiv called with null GL context"); - pVulkanRenderer->ClearNamedFramebufferiv(framebuffer, buffer, drawbuffer, value); - } - - void ClearNamedFramebufferuiv(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, const GLuint* value) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferuiv called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferuiv called with null GL context"); - pVulkanRenderer->ClearNamedFramebufferuiv(framebuffer, buffer, drawbuffer, value); - } - - void ClearNamedFramebufferfi(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, GLfloat depth, GLint stencil) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearNamedFramebufferfi called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ClearNamedFramebufferfi called with null GL context"); - pVulkanRenderer->ClearNamedFramebufferfi(framebuffer, buffer, drawbuffer, depth, stencil); - } - void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { DrawWireIndirect(mode, type, indirect, drawcount, stride, true); return; @@ -560,18 +532,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::ReadPixels called with null GL context"); pVulkanRenderer->ReadPixels(x, y, width, height, format, type, pixels); } - void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GetTexImage called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::GetTexImage called with null GL context"); - pVulkanRenderer->GetTexImage(target, level, format, type, pixels); - } - void GetTextureImage(const SharedPtr& texture, TextureUploadTarget uploadTarget, - GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GetTextureImage called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::GetTextureImage called with null GL context"); - pVulkanRenderer->GetTextureImage(texture, uploadTarget, level, format, type, bufSize, pixels); - } - void Clear(GLbitfield mask) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::Clear called with null GL context"); @@ -780,13 +740,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { pVulkanRenderer->MultiDrawElements(payload); } - void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, - GLsizei drawcount) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElements called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElements called with null GL context"); - MultiDrawElementsImpl(mode, count, type, indices, drawcount, nullptr); - } - void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawElementsBaseVertex called with null GL context"); @@ -824,15 +777,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { pVulkanRenderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } - void BlitNamedFramebuffer(const SharedPtr& readFramebuffer, - const SharedPtr& drawFramebuffer, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, - GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitNamedFramebuffer called with null VulkanRenderer"); - pVulkanRenderer->BlitNamedFramebuffer(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, - dstY0, dstX1, dstY1, mask, filter); - } - namespace { // Backend fence handle: the queue-submission index captured at fence // creation (see VulkanRenderer::GetSyncPointSubmitIndex). The fence is diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index da6d3e8e..920f34c6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -65,21 +65,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value); void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value); void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value); - void ClearNamedFramebufferfv(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, const GLfloat* value); - void ClearNamedFramebufferiv(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, const GLint* value); - void ClearNamedFramebufferuiv(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, const GLuint* value); - void ClearNamedFramebufferfi(const SharedPtr& framebuffer, GLenum buffer, - GLint drawbuffer, GLfloat depth, GLint stencil); void Clear(GLbitfield mask); void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); void DrawArrays(GLenum mode, GLint first, GLsizei count); void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex); void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount); - void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, - GLsizei drawcount); void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount, const GLint* basevertex); void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); @@ -105,11 +95,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DrawArraysIndirect(GLenum mode, const void* indirect); void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - void BlitNamedFramebuffer(const SharedPtr& readFramebuffer, - const SharedPtr& drawFramebuffer, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLbitfield mask, GLenum filter); void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, @@ -129,9 +114,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void GetIntegeri_v(GLenum target, GLuint index, GLint* data); void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding); void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); - void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); - void GetTextureImage(const SharedPtr& texture, TextureUploadTarget uploadTarget, - GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid* pixels); // GL fence sync objects, mapped onto the renderer's frame-serial busy // tracking: a fence captures the frame serial current at creation and is // signaled once every command recorded under that serial has completed on diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index c8b809b5..245c2f5b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -602,7 +602,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (auto& entry : m_descriptorReuseMemo) { entry.valid = false; } - m_fastRebindMemo.valid = false; m_lastBindValid = false; } @@ -689,60 +688,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (auto& entry : m_descriptorReuseMemo) { entry.valid = false; } - // The rebind memo's set may be among the freed ones. - m_fastRebindMemo.valid = false; MGLOG_D("UniformDescriptorBinder: freed %zu descriptor sets for destroyed layout", purgedSets); } } - Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, - const MagmaProgramSource& program, - const ProgramFactory::VkProgramObject& programObj, - Uint32 binding, Uint32 element, - VkDescriptorImageInfo& outImageInfo, - Bool trustUnchangedHint) const { - return ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, element, false, outImageInfo); - } - - Bool UniformManager::ResolveSamplerDescriptorOverride( - const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const { - MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptorOverride: texture manager is null"); - MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptorOverride: sampler manager is null"); - MOBILEGL_ASSERT(samplerBindingOverride.texture != nullptr, - "ResolveSamplerDescriptorOverride: override texture is null for binding %u", - samplerBindingOverride.binding); - MOBILEGL_ASSERT(samplerBindingOverride.sampler != nullptr, - "ResolveSamplerDescriptorOverride: override sampler is null for binding %u", - samplerBindingOverride.binding); - - auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*samplerBindingOverride.texture); - MOBILEGL_ASSERT(resource != nullptr, - "ResolveSamplerDescriptorOverride: failed to sync override texture resource for binding %u textureId=%d", - samplerBindingOverride.binding, samplerBindingOverride.texture->GetExternalIndex()); - MOBILEGL_ASSERT(IsValidSampledImageLayout(resource->layout), - "ResolveSamplerDescriptorOverride: invalid layout %d for binding %u textureId=%d", - static_cast(resource->layout), samplerBindingOverride.binding, - samplerBindingOverride.texture->GetExternalIndex()); - - outImageInfo = { - .sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler, - *samplerBindingOverride.texture, - samplerBindingOverride.forceNearestFiltering, - resource->sampledLevelCount), - .imageView = samplerBindingOverride.imageView != VK_NULL_HANDLE ? - samplerBindingOverride.imageView : - // Same reason as in ResolveSamplerDescriptor: the resource's own views describe - // the storage texture, so a view has to be asked for its own. - (samplerBindingOverride.texture->IsTextureView() - ? m_textureManager->GetOrCreateSampledImageView(*samplerBindingOverride.texture, - VK_FORMAT_UNDEFINED) - : (resource->sampledView != VK_NULL_HANDLE ? resource->sampledView : resource->fullView)), - .imageLayout = samplerBindingOverride.imageLayout != VK_IMAGE_LAYOUT_UNDEFINED ? - samplerBindingOverride.imageLayout : resource->layout, - }; - return outImageInfo.sampler != VK_NULL_HANDLE; - } - Bool UniformManager::ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex, Bool storage, VkBufferView& out) { @@ -1553,10 +1502,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex, - VkPipelineBindPoint bindPoint, - const SamplerBindingOverride* samplerBindingOverride, - Bool samplerDescriptorsUnchangedHint, - const Vector* samplerBindingOverrides) { + VkPipelineBindPoint bindPoint) { // This program has a descriptor MobileGL could not resolve (see // VkProgramObject::declinedDescriptors). Refusing here is the whole of the decline: the // binding is still declared in the layout, so the pipeline is consistent with the shader @@ -1577,39 +1523,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { frame.activeDescriptorPoolIndex = 0; } - // Dynamic-offset-only rebind (see FastRebindMemo in the header): the last - // cacheable walk of this exact program selected a set whose contents are - // provably still what this walk would write - the hint covers every - // sampler binding, and an unchanged (buffer, range) for the single - // dynamic UBO covers the rest - except the dynamic offset, which rebinding - // the SAME set delivers without any descriptor write. - const Bool cacheable = samplerBindingOverride == nullptr && - (samplerBindingOverrides == nullptr || samplerBindingOverrides->empty()); - if (cacheable && samplerDescriptorsUnchangedHint && m_fastRebindMemo.valid && - m_fastRebindMemo.frameIndex == frameIndex && - // A wire store destroyed since the memo was taken may have handed its handle - // value to a later mint (see FastRebindMemo): "same VkBuffer" then names a - // different store, so the memo is refused and the full walk re-records it. - m_fastRebindMemo.wireStoreDestroyEpoch == m_bufferManager->GetWireStoreDestroyEpoch() && - m_fastRebindMemo.programLifetimeId == program.GetLifetimeId() && - m_fastRebindMemo.programHash == programObj.hash) { - VkBuffer uboBuffer = VK_NULL_HANDLE; - VkDeviceSize uboRange = 0; - Uint32 uboDynamicOffset = 0; - if (ResolveDynamicUboDescriptor(program, programObj, m_fastRebindMemo.uboBinding, 0, frameIndex, - uboBuffer, uboRange, uboDynamicOffset) && - uboBuffer == m_fastRebindMemo.uboBuffer && uboRange == m_fastRebindMemo.uboRange) { - auto& fastOffsets = m_dynamicOffsetsScratch; - fastOffsets.clear(); - fastOffsets.push_back(uboDynamicOffset); - BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, - m_fastRebindMemo.set, fastOffsets); - return true; - } - // Any mismatch (arena wrap or growth, direct-bind retarget, upload - // failure) falls through to the full walk, which re-records the memo. - } - // The descriptor set is chosen AFTER the writes are built (below), so a draw // whose resolved descriptor content matches the previous draw can reuse that // set and skip both AcquireDescriptorSet and vkUpdateDescriptorSets. @@ -1660,13 +1573,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { texelBufferViews.reserve(m_maxBindings); dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra); - // Eligibility probe for FastRebindMemo, filled by this walk: exactly one - // dynamic-UBO descriptor (no arrayed elements) and otherwise only - // combined-image samplers, so the whole set's content is pinned by the - // sampler hint plus one (buffer, range) compare. - Uint32 dynamicUboDescriptorCount = 0; - Uint32 fastRebindUboBinding = 0; - Bool fastRebindKindsEligible = true; // Iterate only the bindings this program declares. The old walk covered all 256 slots of // bindingKinds on every draw to find the 1-8 a real program uses. @@ -1685,8 +1591,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) { const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding); - dynamicUboDescriptorCount += descriptorCount; - fastRebindUboBinding = binding; const SizeT firstBufferInfoIndex = bufferInfos.size(); for (Uint32 element = 0; element < descriptorCount; ++element) { VkDescriptorBufferInfo bufferInfo{}; @@ -1719,7 +1623,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } texelBufferViews.push_back(bufferView); - fastRebindKindsEligible = false; write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; write.pTexelBufferView = &texelBufferViews.back(); writes.push_back(write); @@ -1738,7 +1641,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } texelBufferViews.push_back(bufferView); - fastRebindKindsEligible = false; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER; write.pTexelBufferView = &texelBufferViews.back(); writes.push_back(write); @@ -1760,7 +1662,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { bufferInfos.push_back(bufferInfo); } - fastRebindKindsEligible = false; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; write.descriptorCount = descriptorCount; write.pBufferInfo = &bufferInfos[firstBufferInfoIndex]; @@ -1786,7 +1687,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } imageInfos.push_back(imageInfo); } - fastRebindKindsEligible = false; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; write.descriptorCount = descriptorCount; write.pImageInfo = &imageInfos[firstImageInfoIndex]; @@ -1798,37 +1698,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // so a shader indexing them sampled a descriptor nobody had filled in // (KHR-GL42.shading_language_420pack.binding_sampler_array). const Uint32 descriptorCount = BindingDescriptorCount(programObj, binding); - // Overrides come only from MobileGL's own blit and depth-mipmap programs, whose - // samplers are scalars; the override replaces THE descriptor at its binding, so - // there is no element for it to mean on an arrayed one. - const Bool overrideThisBinding = samplerBindingOverride != nullptr && - samplerBindingOverride->binding == binding && - samplerBindingOverride->texture != nullptr && - samplerBindingOverride->sampler != nullptr; - MOBILEGL_ASSERT( - !overrideThisBinding || descriptorCount == 1, - "BindProgramUniformBuffers: sampler override targets arrayed binding %u (%u descriptors)", - binding, descriptorCount); const SizeT firstImageInfoIndex = imageInfos.size(); for (Uint32 element = 0; element < descriptorCount; ++element) { VkDescriptorImageInfo imageInfo{}; - const SamplerBindingOverride* overrideForElement = - overrideThisBinding && element == 0 ? samplerBindingOverride : nullptr; - if (overrideForElement == nullptr && samplerBindingOverrides != nullptr) { - const auto overrideIt = std::find_if( - samplerBindingOverrides->begin(), samplerBindingOverrides->end(), - [binding, element](const SamplerBindingOverride& candidate) { - return candidate.binding == binding && candidate.element == element; - }); - if (overrideIt != samplerBindingOverrides->end()) { - overrideForElement = &*overrideIt; - } - } - const Bool hasImage = overrideForElement != nullptr - ? ResolveSamplerDescriptorOverride(*overrideForElement, imageInfo) - : ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, - element, imageInfo, - samplerDescriptorsUnchangedHint); + const Bool hasImage = ResolveWireImageDescriptor(commandBuffer, program, programObj, binding, + element, false, imageInfo); if (!hasImage) { MGLOG_E_ONCE( "UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u " @@ -1845,14 +1719,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } imageInfos.push_back(imageInfo); } - if (descriptorCount > 1) { - // The dynamic-offset-only rebind replays a whole descriptor set on the - // strength of the sampler hint alone, and its eligibility probe was written - // for bindings that carry one descriptor each. An arrayed sampler binding - // also bypasses the per-binding descriptor memo, so there is nothing for it - // to win here either. - fastRebindKindsEligible = false; - } write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.descriptorCount = descriptorCount; write.pImageInfo = &imageInfos[firstImageInfoIndex]; @@ -1906,12 +1772,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkDescriptorSet reusedSet = VK_NULL_HANDLE; - if (cacheable) { - for (const auto& entry : m_descriptorReuseMemo) { - if (entry.valid && entry.signature == signature) { - reusedSet = entry.set; - break; - } + for (const auto& entry : m_descriptorReuseMemo) { + if (entry.valid && entry.signature == signature) { + reusedSet = entry.set; + break; } } if (reusedSet != VK_NULL_HANDLE) { @@ -1929,31 +1793,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!writes.empty()) { vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } - if (cacheable) { - m_descriptorReuseMemo[m_descriptorReuseMemoNext] = - DescriptorReuseEntry{signature, descriptorSet, true}; - m_descriptorReuseMemoNext = (m_descriptorReuseMemoNext + 1) % kDescriptorReuseMemoSize; - } else { - for (auto& entry : m_descriptorReuseMemo) { - entry.valid = false; - } - } - } - - // (Re)record the dynamic-offset-only rebind memo. Recording on every - // cacheable walk (allocated or reused set alike - both hold exactly the - // content just computed) keeps the single slot tracking the most recent - // program; a non-cacheable override walk drops it alongside the reuse - // memo above. - if (cacheable && fastRebindKindsEligible && dynamicUboDescriptorCount == 1) { - m_fastRebindMemo = FastRebindMemo{ - /*valid=*/true, frameIndex, program.GetLifetimeId(), programObj.hash, - fastRebindUboBinding, bufferInfos[0].buffer, - bufferInfos[0].range, descriptorSet, - m_bufferManager->GetWireStoreDestroyEpoch(), - }; - } else { - m_fastRebindMemo.valid = false; + m_descriptorReuseMemo[m_descriptorReuseMemoNext] = + DescriptorReuseEntry{signature, descriptorSet, true}; + m_descriptorReuseMemoNext = (m_descriptorReuseMemoNext + 1) % kDescriptorReuseMemoSize; } BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, descriptorSet, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index fa167889..bf4cf270 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -27,16 +27,6 @@ namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_Backend::DirectVulkan { class UniformManager { public: - struct SamplerBindingOverride { - Uint32 binding = 0; - Uint32 element = 0; - MG_State::GLState::ITextureObject* texture = nullptr; - const MG_State::GLState::SamplerObject* sampler = nullptr; - VkImageView imageView = VK_NULL_HANDLE; - VkImageLayout imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; - Bool forceNearestFiltering = false; - }; - struct SamplerImageFeedbackBinding { Uint32 samplerBinding = 0; Uint32 samplerElement = 0; @@ -107,20 +97,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // submit older work, but descriptor recording must never rotate it. Bool PrepareWireTextureResources(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj); - // samplerDescriptorsUnchangedHint: the caller (SetupDraw fast path) proved that - // every input of every combined-image-sampler resolution is unchanged since the - // previous draw's resolve - same (texture, sampler) per binding, texture params - // sum, sampling-resolution generation (sampler params + texture shape), image - // epochs AND per-resource layout values - so the per-binding cached - // VkDescriptorImageInfo may be reused without re-running the resolve chain. Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex, - VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, - const SamplerBindingOverride* samplerBindingOverride = nullptr, - Bool samplerDescriptorsUnchangedHint = false, - const Vector* samplerBindingOverrides = nullptr); + VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS); // Pure format-policy helper kept public for host regression tests. Formatted storage // images use their shader qualifier; transformed float images use glBindImageTexture's @@ -277,18 +258,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // class when the device cannot use the declared one as a texel buffer. VkBufferView AcquireUnboundTexelBufferView(VkFormat declaredFormat, SamplerNumericDomain numericDomain, Bool storage); - // `element` indexes a sampler ARRAY inside one binding; each element carries its own - // independently assigned GL texture unit, so it selects the texture, the sampler - // override and the fallback separately from its neighbours. - // - // trustUnchangedHint: reuse this binding's cached VkDescriptorImageInfo outright - // (see BindProgramUniformBuffers' samplerDescriptorsUnchangedHint for the proof - // obligations the caller carries). The cache is keyed by binding alone, so it is - // used ONLY for single-descriptor bindings - see m_samplerResolveMemo. - Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, - const ProgramFactory::VkProgramObject& programObj, Uint32 binding, - Uint32 element, VkDescriptorImageInfo& outImageInfo, - Bool trustUnchangedHint = false) const; Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element, Bool storage, VkDescriptorImageInfo& out) const; @@ -301,8 +270,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDeviceSize m_wireMaxUniformRange = ~VkDeviceSize{0}; VkDeviceSize m_wireMaxStorageRange = ~VkDeviceSize{0}; Uint32 m_wireMaxTexelElements = ~Uint32{0}; - Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride, - VkDescriptorImageInfo& outImageInfo) const; Bool ResolveTexelBufferDescriptor(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex, VkBufferView& outBufferView); @@ -440,47 +407,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { DescriptorReuseEntry m_descriptorReuseMemo[kDescriptorReuseMemoSize]; Uint32 m_descriptorReuseMemoNext = 0; - // Dynamic-offset-only rebind (see BindProgramUniformBuffers): records the - // descriptor set selected by the last cacheable full walk of a program - // whose active bindings are exactly one dynamic UBO (single descriptor) - // plus combined-image samplers. When the next call proves every sampler - // descriptor input unchanged (samplerDescriptorsUnchangedHint) and the - // UBO re-resolves to the SAME VkBuffer+range - only the dynamic offset - // moved, the per-draw glUniform case - the walk collapses to: resolve one - // offset, rebind the recorded set with new pDynamicOffsets (Vulkan allows - // rebinding the same set with different dynamic offsets). - // Invalidation inventory: BeginFrame clears it (the frame's sets are - // recycled) and the frameIndex field guards cross-frame confusion on top; - // OnDescriptorSetLayoutDestroyed clears it (the set may be freed); a - // sampler-override walk clears it (mirrors m_descriptorReuseMemo); a - // program relink bumps the backend state version and thus programObj.hash - // so the key misses; the program lifetime id is never reused, so a - // deleted-and-recreated program misses; a texture/sampler/binding change - // drops the hint upstream; an arena wrap or growth resolves a different - // VkBuffer and misses. AcquireDescriptorSet's per-frame cursor only - // advances, so the recorded set is never re-written within its frame. - // P7 M2 round 2 (ID-P7-43): a WIRE store can die MID-FRAME on the reclaim - // path (VkBufferManager::DeferredWireRelease), and the next mint can hand - // its VkBuffer handle value back - a heap pointer under lavapipe - so "the - // same VkBuffer+range" is no longer proof of the same store. The memo - // therefore records VkBufferManager::GetWireStoreDestroyEpoch() and misses - // once any wire store has been destroyed since; the descriptor-reuse - // signature below folds the same epoch in. On the wire arm this memo is - // never consulted (SetupWireDraw passes no sampler hint), so the field is - // the defensive half; the signature is the half the wire arm reaches. - struct FastRebindMemo { - Bool valid = false; - Uint32 frameIndex = 0; - Uint64 programLifetimeId = 0; - ProgramFactory::HashType programHash = 0; - Uint32 uboBinding = 0; - VkBuffer uboBuffer = VK_NULL_HANDLE; - VkDeviceSize uboRange = 0; - VkDescriptorSet set = VK_NULL_HANDLE; - Uint64 wireStoreDestroyEpoch = 0; - }; - FastRebindMemo m_fastRebindMemo; - // vkCmdBindDescriptorSets dedup: consecutive draws with a static uniform // block resolve to the same set AND the same dynamic offsets, so the // driver call can be skipped outright. Command-buffer-scope state; reset diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp index fee95ad2..45def086 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp @@ -68,24 +68,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { toLinear(payload.color.z()), payload.color.w()); } - void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload) { - switch (payload.colorEncoding) { - case ClearColorEncoding::Int: - payload.colorInt = IntVec4(payload.colorInt.x(), payload.colorInt.y(), payload.colorInt.z(), 1); - break; - case ClearColorEncoding::Uint: - payload.colorUint = UintVec4(payload.colorUint.x(), payload.colorUint.y(), payload.colorUint.z(), 1u); - break; - case ClearColorEncoding::Float: - payload.color = FloatVec4(payload.color.x(), payload.color.y(), payload.color.z(), 1.0f); - break; - } - } - - static Bool PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity) { - return key.texture == identity.texture && key.textureLifetimeId == identity.lifetimeId; - } - static Uint32 ResolveAttachmentBaseArrayLayer(TextureUploadTarget target) { if (!IsCubeMapFaceUploadTarget(target)) { return 0; @@ -93,18 +75,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return static_cast(target) - static_cast(TextureUploadTarget::CubeMapPositiveX); } - static Uint32 ResolveAttachmentBaseArrayLayer( - const MG_State::GLState::FramebufferAttachmentObject& attachment) { - if (attachment.IsLayered()) { - return 0; - } - const TextureUploadTarget uploadTarget = attachment.GetTextureUploadTarget(); - if (!IsCubeMapFaceUploadTarget(uploadTarget)) { - return static_cast(std::max(attachment.GetTextureLayer(), 0)); - } - return ResolveAttachmentBaseArrayLayer(uploadTarget); - } - // ResolveAttachmentLayerCount used to be duplicated here, reading attachment.GetSize().z() // raw - no ToVulkanLevelExtent remap for a 1D array, no six-faces arm for a cube map. That is // not a cosmetic difference: the count below is not key-only, it is written straight into @@ -113,65 +83,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // for good, while the very same queued clear cleared all six through the render pass's // LOAD_OP_CLEAR. The helper now lives once, in VkTextureManager.h beside ToVulkanLevelExtent. - static const MG_State::GLState::FramebufferAttachmentObject* GetClearableAttachment( - const MG_State::GLState::FramebufferObject& drawFbo, FramebufferAttachmentType attachmentType) { - if (attachmentType == FramebufferAttachmentType::None) { - return nullptr; - } - - const auto& attachment = drawFbo.GetAttachment(attachmentType); - if (!attachment.IsTexture() || attachment.IsRenderbuffer()) { - return nullptr; - } - - return &attachment; - } - - // The texture a pending clear is actually ABOUT. A clear issued through a GL texture view - // (ARB_texture_view) targets the storage it views, so it must queue against - and be found - // by - the storage texture; keying it on the view instead left the clear invisible to every - // materialisation done through the parent's name (and vice versa), so the image stayed in - // VK_IMAGE_LAYOUT_UNDEFINED and the readback was dropped as unreadable. - static MG_State::GLState::ITextureObject* ClearStorageTextureOf(MG_State::GLState::ITextureObject* texture) { - if (texture == nullptr) { - return nullptr; - } - const auto& storageOwner = texture->GetViewStorageOwner(); - return storageOwner ? storageOwner.get() : texture; - } - - PendingClearKey VkClearManager::MakePendingClearKey(MG_State::GLState::ITextureObject* rawTexture, Uint32 mipLevel, - Uint32 baseArrayLayer, Uint32 layerCount) { - MG_State::GLState::ITextureObject* texture = ClearStorageTextureOf(rawTexture); - if (rawTexture != nullptr && texture != rawTexture) { - // The caller named a level and a layer of the VIEW; the key describes the STORAGE, so - // both have to be shifted into its numbering (GL 4.6 core 8.18). Without this a clear - // of a view's level 0 would collide with a clear of the storage's level 0 even when - // the view opened onto level 1. - mipLevel += static_cast(rawTexture->GetViewMinLevel()); - baseArrayLayer += static_cast(rawTexture->GetViewMinLayer()); - } - return PendingClearKey { - .texture = texture, - .textureLifetimeId = texture ? texture->GetLifetimeId() : 0, - .mipLevel = mipLevel, - .baseArrayLayer = baseArrayLayer, - .layerCount = layerCount, - }; - } - - PendingClearKey VkClearManager::MakePendingClearKey( - const MG_State::GLState::FramebufferAttachmentObject& attachment) { - MOBILEGL_ASSERT(attachment.IsTexture() && !attachment.IsRenderbuffer(), - "MakePendingClearKey requires a texture framebuffer attachment"); - auto* texture = attachment.GetTexture().get(); - MOBILEGL_ASSERT(texture != nullptr, "MakePendingClearKey: texture attachment resolved to null"); - const Uint32 mipLevel = static_cast(std::max(attachment.GetTextureLevel(), 0)); - const Uint32 baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment); - const Uint32 layerCount = ResolveAttachmentLayerCount(attachment); - return MakePendingClearKey(texture, mipLevel, baseArrayLayer, layerCount); - } - Bool VkClearManager::Initialize() { return true; } @@ -183,355 +94,4 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_pendingCount.store(static_cast(m_pendingClears.size()), std::memory_order_relaxed); } - TextureIdentity VkClearManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture) { - // Same rule as VkTextureManager::MakeTextureIdentity: a GL texture view is identified by - // the storage it views. A clear posted against a view and one posted against its parent - // target the same image, so they have to coalesce rather than queue independently. - if (texture != nullptr) { - const auto& storageOwner = texture->GetViewStorageOwner(); - if (storageOwner) { - texture = storageOwner.get(); - } - } - return TextureIdentity { - .texture = texture, - .lifetimeId = texture ? texture->GetLifetimeId() : 0, - }; - } - - void VkClearManager::MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src) { - dst.mask |= src.mask; - if ((src.mask & GL_COLOR_BUFFER_BIT) != 0) { - // The whole colour story travels together (same rule as - // VkRenderPassManager::QueueRenderbufferClear): a glClearBufferiv/uiv - // payload carries its value in colorInt/colorUint and its branch selector - // in colorEncoding - dropping them here would leave the pending clear - // reading as an all-zero float one. - dst.color = src.color; - dst.colorEncoding = src.colorEncoding; - dst.colorInt = src.colorInt; - dst.colorUint = src.colorUint; - } - if ((src.mask & GL_DEPTH_BUFFER_BIT) != 0) { - dst.depth = src.depth; - } - if ((src.mask & GL_STENCIL_BUFFER_BIT) != 0) { - dst.stencil = src.stencil; - } - } - - void VkClearManager::ErasePendingClearsForTextureLocked(const TextureIdentity& identity) { - Vector keysToErase; - keysToErase.reserve(m_pendingClears.size()); - for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) { - if (PendingClearMatchesTextureIdentity(it->first, identity)) { - keysToErase.emplace_back(it->first); - } - } - for (const auto& key : keysToErase) { - m_pendingClears.erase(key); - } - m_aliveObjects.erase(identity); - m_pendingCount.store(static_cast(m_pendingClears.size()), std::memory_order_relaxed); - } - - Bool VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity, - SharedPtr& outTexture) { - outTexture.reset(); - if (identity.texture == nullptr) { - return false; - } - - auto aliveIt = m_aliveObjects.find(identity); - if (aliveIt == m_aliveObjects.end()) { - ErasePendingClearsForTextureLocked(identity); - return false; - } - - outTexture = aliveIt->second.lock(); - if (!outTexture || outTexture.get() != identity.texture || outTexture->GetLifetimeId() != identity.lifetimeId) { - ErasePendingClearsForTextureLocked(identity); - outTexture.reset(); - return false; - } - - return true; - } - - Bool VkClearManager::LockTextureLocked(const PendingClearKey& key, - SharedPtr& outTexture) { - return LockTextureIdentityLocked(TextureIdentity{ - .texture = key.texture, - .lifetimeId = key.textureLifetimeId, - }, outTexture); - } - - void VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, - const MG_State::GLState::FramebufferObject& drawFbo) { - if (mask & GL_COLOR_BUFFER_BIT) { - auto& drawbufs = drawFbo.GetDrawBuffers(); - // This should automatically work on default & offscreen FBO - for (auto drawbuf: drawbufs) { - const auto* attachment = GetClearableAttachment(drawFbo, drawbuf); - if (!attachment) { - continue; - } - - QueueClear({ - .mask = GL_COLOR_BUFFER_BIT, - .color = clearPayload.color - }, *attachment); - - MGLOG_D("%s: %s (texture %d) - color = (%.2f, %.2f, %.2f, %.2f)", __func__, - MG_Util::ConvertFramebufferAttachmentTypeToString(drawbuf).c_str(), - attachment->GetTexture()->GetExternalIndex(), - clearPayload.color[0], clearPayload.color[1], clearPayload.color[2], clearPayload.color[3]); - } - } - - if (mask & GL_DEPTH_BUFFER_BIT) { - const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Depth); - if (attachment) { - QueueClear({ - .mask = GL_DEPTH_BUFFER_BIT, - .depth = clearPayload.depth, - }, *attachment); - - MGLOG_D("%s: Depth (texture %d) - depth = (%.2f)", __func__, - attachment->GetTexture()->GetExternalIndex(), clearPayload.depth); - } - } - - if (mask & GL_STENCIL_BUFFER_BIT) { - const auto* attachment = GetClearableAttachment(drawFbo, FramebufferAttachmentType::Stencil); - if (attachment) { - QueueClear({ - .mask = GL_STENCIL_BUFFER_BIT, - .stencil = clearPayload.stencil, - }, *attachment); - - MGLOG_D("%s: Stencil (texture %d) - stencil = (%u)", __func__, - attachment->GetTexture()->GetExternalIndex(), clearPayload.stencil); - } - } - } - - void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, - const SharedPtr& texture) { - if (clearPayload.mask == 0 || !texture) { - return; - } - - const auto& storageOwner = texture->GetViewStorageOwner(); - const SharedPtr& storageTexture = storageOwner ? storageOwner : texture; - const PendingClearKey key = MakePendingClearKey(storageTexture.get()); - const std::lock_guard lock(m_mutex); - m_aliveObjects[MakeTextureIdentity(storageTexture.get())] = storageTexture; - auto& pending = m_pendingClears[key]; - MergeClearPayload(pending, clearPayload); - m_pendingCount.store(static_cast(m_pendingClears.size()), std::memory_order_relaxed); - } - - void VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, - const MG_State::GLState::FramebufferAttachmentObject& attachment) { - if (clearPayload.mask == 0 || !attachment.IsTexture() || attachment.IsRenderbuffer()) { - return; - } - const auto texture = attachment.GetTexture(); - if (!texture) { - return; - } - - const PendingClearKey key = MakePendingClearKey(attachment); - // The alive entry must hold the STORAGE object, because the key names it: - // LockTextureIdentityLocked cross-checks the two, and registering a view here under its - // storage's identity made every lookup of this clear fail that check and silently report - // "nothing pending" - which is how a clear issued through a view's framebuffer vanished. - const auto& storageOwner = texture->GetViewStorageOwner(); - const SharedPtr& storageTexture = storageOwner ? storageOwner : texture; - const std::lock_guard lock(m_mutex); - m_aliveObjects[MakeTextureIdentity(storageTexture.get())] = storageTexture; - auto& pending = m_pendingClears[key]; - MergeClearPayload(pending, clearPayload); - m_pendingCount.store(static_cast(m_pendingClears.size()), std::memory_order_relaxed); - } - - Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) { - if (texture == nullptr) { - return false; - } - - if (m_pendingCount.load(std::memory_order_relaxed) == 0) { - return false; // per-draw hot path: nothing pending anywhere - } - - texture = ClearStorageTextureOf(texture); - const Uint64 lifetimeId = texture->GetLifetimeId(); - const std::lock_guard lock(m_mutex); - for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) { - if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) { - SharedPtr liveTexture; - return LockTextureLocked(it->first, liveTexture); - } - } - return false; - } - - Bool VkClearManager::HasPendingClear(const PendingClearKey& key) { - if (key.texture == nullptr) { - return false; - } - if (m_pendingCount.load(std::memory_order_relaxed) == 0) { - return false; // per-draw hot path: nothing pending anywhere - } - - const std::lock_guard lock(m_mutex); - if (m_pendingClears.find(key) == m_pendingClears.end()) { - return false; - } - - SharedPtr liveTexture; - return LockTextureLocked(key, liveTexture); - } - - Bool VkClearManager::HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) { - if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) { - return false; - } - return HasPendingClear(MakePendingClearKey(attachment)); - } - - Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload) { - SharedPtr liveTexture; - return GetPendingClear(key, outPayload, liveTexture); - } - - Bool VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload, - SharedPtr& outTexture) { - if (key.texture == nullptr) { - return false; - } - if (m_pendingCount.load(std::memory_order_relaxed) == 0) { - return false; // per-draw hot path: nothing pending anywhere - } - - const std::lock_guard lock(m_mutex); - if (!LockTextureLocked(key, outTexture)) { - return false; - } - auto it = m_pendingClears.find(key); - if (it == m_pendingClears.end()) { - outTexture.reset(); - return false; - } - - outPayload = it->second; - MGLOG_D("%s: Got pending clear for texture@%p lifetime=%llu, mip=%u layer=%u count=%u mask=0x%x clear value: color = (%.2f, %.2f, %.2f, %.2f), depth = (%.2f), stencil = (%u)", __func__, - static_cast(key.texture), - static_cast(key.textureLifetimeId), - key.mipLevel, key.baseArrayLayer, key.layerCount, - static_cast(outPayload.mask), - outPayload.color[0], outPayload.color[1], outPayload.color[2], outPayload.color[3], - outPayload.depth, - outPayload.stencil); - return true; - } - - Bool VkClearManager::GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment, - ClearAttachmentPayload& outPayload) { - if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) { - MGLOG_D("%s: Failed getting pending clear for non-texture framebuffer attachment", __func__); - return false; - } - return GetPendingClear(MakePendingClearKey(attachment), outPayload); - } - - Bool VkClearManager::GetPendingClears(MG_State::GLState::ITextureObject* texture, - Vector& outEntries) { - outEntries.clear(); - if (texture == nullptr) { - return false; - } - if (m_pendingCount.load(std::memory_order_relaxed) == 0) { - return false; // per-draw hot path: nothing pending anywhere - } - - texture = ClearStorageTextureOf(texture); - const Uint64 lifetimeId = texture->GetLifetimeId(); - const std::lock_guard lock(m_mutex); - SharedPtr liveTexture; - if (!LockTextureIdentityLocked(MakeTextureIdentity(texture), liveTexture)) { - return false; - } - for (auto it = m_pendingClears.begin(); it != m_pendingClears.end(); ++it) { - if (it->first.texture == texture && it->first.textureLifetimeId == lifetimeId) { - outEntries.emplace_back(PendingClearEntry{.key = it->first, .payload = it->second}); - } - } - return !outEntries.empty(); - } - - void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) { - if (texture == nullptr) { - return; - } - - if (m_pendingCount.load(std::memory_order_relaxed) == 0) { - return; // per-draw hot path: nothing pending anywhere - } - const TextureIdentity identity = MakeTextureIdentity(texture); - MGLOG_D("%s: Pop all pending clears for texture %d", __func__, texture->GetExternalIndex()); - const std::lock_guard lock(m_mutex); - ErasePendingClearsForTextureLocked(identity); - } - - void VkClearManager::PopPendingClear(const PendingClearKey& key) { - if (key.texture == nullptr) { - return; - } - - { - const std::lock_guard lock(m_mutex); - auto it = m_pendingClears.find(key); - if (it != m_pendingClears.end()) { - m_pendingClears.erase(it); - m_pendingCount.store(static_cast(m_pendingClears.size()), std::memory_order_relaxed); - } - } - - MGLOG_D("%s: Pop pending clear for texture@%p lifetime=%llu mip=%u layer=%u count=%u", __func__, - static_cast(key.texture), static_cast(key.textureLifetimeId), - key.mipLevel, key.baseArrayLayer, key.layerCount); - } - - void VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment) { - if (!attachment.IsTexture() || attachment.IsRenderbuffer() || !attachment.GetTexture()) { - return; - } - PopPendingClear(MakePendingClearKey(attachment)); - } - - SizeT VkClearManager::CollectGarbage() { - const std::lock_guard lock(m_mutex); - m_gcCounter++; - if (m_gcCounter != 0) { - return 0; - } - - Vector expiredTextures; - expiredTextures.reserve(m_aliveObjects.size()); - for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) { - if (it->second.expired()) { - expiredTextures.emplace_back(it->first); - } - } - if (expiredTextures.empty()) { - return 0; - } - - for (const auto& identity : expiredTextures) { - ErasePendingClearsForTextureLocked(identity); - } - return expiredTextures.size(); - } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h index 0008d5c1..ccc1162c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h @@ -46,10 +46,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // expressed in whichever type matches (GL 4.6 core 15.2.3). VkClearColorValue MakeVkClearColorValue(const ClearAttachmentPayload& payload, Bool formatLacksAlpha); - // Applies that same rule in place, for the paths that have to bake it into the payload before - // the destination is known. - void ForceOpaqueClearAlpha(ClearAttachmentPayload& payload); - // vkCmdClearColorImage names the image, so the driver applies the destination format's transfer // function to whatever value it is handed. Every other write path in this backend goes through // the UNORM twin view while GL_FRAMEBUFFER_SRGB is off (ResolveSrgbAttachmentWriteFormat) and @@ -113,42 +109,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { class VkClearManager { public: - static PendingClearKey MakePendingClearKey(const MG_State::GLState::FramebufferAttachmentObject& attachment); - // Resolves a GL texture view to the storage it views before keying; see the definition. - static PendingClearKey MakePendingClearKey(MG_State::GLState::ITextureObject* rawTexture, Uint32 mipLevel = 0, - Uint32 baseArrayLayer = 0, Uint32 layerCount = 1); - Bool Initialize(); void Shutdown(); - void QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, const MG_State::GLState::FramebufferObject& drawFbo); - void QueueClear( - const ClearAttachmentPayload& clearPayload, - const SharedPtr& texture); - void QueueClear(const ClearAttachmentPayload& clearPayload, - const MG_State::GLState::FramebufferAttachmentObject& attachment); - Bool HasPendingClear(MG_State::GLState::ITextureObject* texture); - Bool HasPendingClear(const PendingClearKey& key); - Bool HasPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment); - Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload); - Bool GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload, - SharedPtr& outTexture); - Bool GetPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment, - ClearAttachmentPayload& outPayload); - Bool GetPendingClears(MG_State::GLState::ITextureObject* texture, Vector& outEntries); - void PopPendingClear(MG_State::GLState::ITextureObject* texture); - void PopPendingClear(const PendingClearKey& key); - void PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment); - SizeT CollectGarbage(); private: - static TextureIdentity MakeTextureIdentity(MG_State::GLState::ITextureObject* texture); - static void MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src); - void ErasePendingClearsForTextureLocked(const TextureIdentity& identity); - Bool LockTextureIdentityLocked(const TextureIdentity& identity, - SharedPtr& outTexture); - Bool LockTextureLocked(const PendingClearKey& key, - SharedPtr& outTexture); - Uint8 m_gcCounter = 0; public: // Lock-free probe for the consecutive-draw fast path: any pending clear diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 73a875d2..54c8130a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -18,45 +18,6 @@ #include namespace MobileGL::MG_Backend::DirectVulkan { - static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) { - // GL promises "at least the requested samples", so a non-power-of-two - // request (legal in GL, e.g. 3) rounds up to the next Vulkan bit. - if (requestedSamples <= 1) { - outSampleCount = VK_SAMPLE_COUNT_1_BIT; - return true; - } - if (requestedSamples > 64) { - return false; - } - Uint32 bit = 1; - while (bit < static_cast(requestedSamples)) { - bit <<= 1; - } - outSampleCount = static_cast(bit); - return true; - } - - static VkImageAspectFlags ResolveImageAspectMaskForFormat(VkFormat format) { - switch (format) { - case VK_FORMAT_D16_UNORM: - case VK_FORMAT_X8_D24_UNORM_PACK32: - case VK_FORMAT_D32_SFLOAT: - return VK_IMAGE_ASPECT_DEPTH_BIT; - case VK_FORMAT_S8_UINT: - return VK_IMAGE_ASPECT_STENCIL_BIT; - case VK_FORMAT_D16_UNORM_S8_UINT: - case VK_FORMAT_D24_UNORM_S8_UINT: - case VK_FORMAT_D32_SFLOAT_S8_UINT: - return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - default: - return VK_IMAGE_ASPECT_COLOR_BIT; - } - } - - static Bool ColorFormatLacksAlpha(const MG_State::GLState::ITextureObject* texture) { - return texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3; - } - [[maybe_unused]] static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture, Float requestedAlpha) { if (texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3) { return 1.0f; @@ -64,124 +25,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { return requestedAlpha; } - static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) { - return target >= TextureUploadTarget::CubeMapPositiveX && - target <= TextureUploadTarget::CubeMapNegativeZ; - } - - static Uint32 ResolveAttachmentBaseArrayLayer(const MG_State::GLState::FramebufferAttachmentObject& attachment) { - // Every branch has to go through ToStorageArrayLayer, including the two that name layer 0 - // implicitly: a layered attachment of a texture VIEW starts at the view's first layer, not - // at the image's, and a cube FACE index is a layer index like any other. Leaving either - // unshifted made the render pass write layers [0, n) while the clear key, the blit, the - // copy and the readback for the same attachment all addressed [minLayer, minLayer + n) - - // they resolve the layer through their own copies of this helper, which do shift. - const auto* texture = attachment.GetTexture().get(); - if (attachment.IsLayered()) { - return ToStorageArrayLayer(texture, 0); - } - const TextureUploadTarget uploadTarget = attachment.GetTextureUploadTarget(); - if (!IsCubeMapFaceUploadTarget(uploadTarget)) { - return ToStorageArrayLayer(texture, attachment.GetTextureLayer()); - } - const Int face = - static_cast(uploadTarget) - static_cast(TextureUploadTarget::CubeMapPositiveX); - return ToStorageArrayLayer(texture, face); - } - // ResolveAttachmentLayerCount lives in VkTextureManager.h, beside ToVulkanLevelExtent, because // VkClearManager needs the SAME answer: its pending-clear key's layerCount becomes a real // VkImageSubresourceRange when a clear is materialised outside a render pass. See the header. - // VUID-VkFramebufferCreateInfo-flags-04113: every view handed to vkCreateFramebuffer must have - // been created as VK_IMAGE_VIEW_TYPE_2D or VK_IMAGE_VIEW_TYPE_2D_ARRAY. The image's OWN view - // type is not a legal answer for several of the targets GL can attach, and returning it - // unchanged is what took the process down on every layered 3D / cube-map-array attachment: - // a 3D view is refused outright by the layer-span guard in GetOrCreateAttachmentViewAtMipLevel - // (3D images have arrayLayers == 1) and a CUBE_ARRAY view is built happily and then rejected - - // or dereferenced - by the driver inside vkCreateFramebuffer. - // - // A 2D_ARRAY view is the legal spelling of all three: over a 2D-array-compatible 3D image its - // "layers" are the mip's z slices (VUID-VkImageViewCreateInfo-image-04970), and over a - // CUBE_COMPATIBLE 2D image - which is what both cube targets are - its layers are the faces. - // - // Knowingly NOT remapped: VK_IMAGE_VIEW_TYPE_1D / _1D_ARRAY, which 04113 also forbids. There is - // no legal alternative for them (a VK_IMAGE_TYPE_1D image admits no 2D-family view at all), so - // the only honest answer would be to decline the attachment - and every driver this has run on, - // lavapipe included, accepts them. Declining would turn working GL_TEXTURE_1D[_ARRAY] render - // targets into skipped draws to satisfy a VU nothing enforces. Left as-is, deliberately. - static VkImageViewType ResolveAttachmentViewType( - const MG_State::GLState::FramebufferAttachmentObject& attachment, - const VkTextureManager::TextureResource& resource) { - if (attachment.IsLayered()) { - switch (resource.viewType) { - case VK_IMAGE_VIEW_TYPE_3D: - case VK_IMAGE_VIEW_TYPE_CUBE: - case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: - return VK_IMAGE_VIEW_TYPE_2D_ARRAY; - default: - return resource.viewType; - } - } - // A non-layered attachment names ONE layer, so the view over it is a plain 2D view whatever - // the image's own view type is. The cube-face upload targets always meant this; a cube map - // array attached through glFramebufferTextureLayer means it too, and a CUBE_ARRAY view over - // a single layer is not a legal attachment. The CUBE arm is inert today - no frontend path - // produces a non-layered cube attachment without a face upload target - and is kept for - // symmetry with CUBE_ARRAY. - // - // 3D belongs in the same list and was missing from it, which is why the "per-slice - // attachment view is a 2D view whose array layer is the slice" branch in - // GetOrCreateAttachmentViewAtMipLevel was unreachable: glFramebufferTextureLayer on a - // GL_TEXTURE_3D asked for a 3D view (illegal as an attachment) whose span was then checked - // against arrayLayers == 1, so every slice above z = 0 came back VK_NULL_HANDLE. - if (IsCubeMapFaceUploadTarget(attachment.GetTextureUploadTarget()) || - resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY || resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE || - resource.viewType == VK_IMAGE_VIEW_TYPE_3D) { - return VK_IMAGE_VIEW_TYPE_2D; - } - return resource.viewType; - } - - static MG_State::GLState::ITextureObject* ResolveCompleteColorAttachmentTexture( - const MG_State::GLState::FramebufferObject& fbo, - FramebufferAttachmentType attachmentType, - Uint32 drawBufferIndex) { - if (attachmentType == FramebufferAttachmentType::None) { - return nullptr; - } - - const auto& attachment = fbo.GetAttachment(attachmentType); - if (!attachment.IsTexture()) { - if (attachment.IsEmpty()) { - MGLOG_D("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u has no bound color attachment; using VK_ATTACHMENT_UNUSED", - drawBufferIndex, - MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(), - fbo.GetExternalIndex()); - } - return nullptr; - } - - if (!attachment.IsComplete()) { - MGLOG_W_ONCE("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u has an incomplete texture attachment; using VK_ATTACHMENT_UNUSED", - drawBufferIndex, - MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(), - fbo.GetExternalIndex()); - return nullptr; - } - - auto* texture = attachment.GetTexture().get(); - if (texture == nullptr) { - MGLOG_W_ONCE("GetOrCreateRenderPass: draw buffer slot %u (%s) on FBO %u resolved to a null texture; using VK_ATTACHMENT_UNUSED", - drawBufferIndex, - MG_Util::ConvertFramebufferAttachmentTypeToString(attachmentType).c_str(), - fbo.GetExternalIndex()); - return nullptr; - } - - return texture; - } - DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo( VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil) { DepthStencilAttachmentLoadInfo info{}; @@ -261,7 +108,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { RenderPassEntry::s_textureResourcesScratch.clear(); s_activeRenderPass.Get() = {}; s_hasActiveRenderPass.Get() = false; - m_rpFastValid = false; } Uint64 VkRenderPassManager::RetireAgeFrames() const { @@ -341,1202 +187,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - VkRenderPassManager::RenderbufferResource* VkRenderPassManager::GetOrCreateRenderbufferResource( - const SharedPtr& renderbuffer) { - if (!renderbuffer || !renderbuffer->IsAllocated()) { - return nullptr; - } - - CollectRenderbufferGarbage(); - - VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; - if (!TryResolveSampleCountFlagBits(renderbuffer->GetSamples(), sampleCount)) { - MGLOG_E_ONCE("GetOrCreateRenderbufferResource: unsupported renderbuffer sample count %d for renderbuffer %u", - renderbuffer->GetSamples(), - renderbuffer->GetExternalIndex()); - return nullptr; - } - - const auto internalFormat = renderbuffer->GetInternalFormat(); - // ONE resolver, shared with textures (VkTextureManager::ResolveTextureFormatInfo), so a - // renderbuffer and a texture of the same GL format cannot disagree about their VkFormat. - // `expandRgbToRgba` / `componentByteCount` / `alphaBytes` describe how to reshape a SHADOW - // UPLOAD, and a renderbuffer has none, so only `.format` is taken. - // - // This used to be a hand-maintained second copy of that table, and it was missing exactly - // four rows: RGBA2 and RGBA12 fell through to ConvertTextureInternalFormatToVkEnum's - // VK_FORMAT_UNDEFINED (no image at all - bound as a draw buffer the attachment became - // VK_ATTACHMENT_UNUSED and every draw into it was dropped), while RGBA4 and RGB5A1 fell - // through to the 16-bit packed formats and then faced 32-bit R8G8B8A8_UNORM textures across - // a size-incompatible vkCmdCopyImage. - const VkFormat format = ResolveTextureFormatInfo(internalFormat).format; - const VkImageAspectFlags aspect = ResolveImageAspectMaskForFormat(format); - // Renderbuffers are never sampled (GL has no way to bind one to a sampler), so the - // usage set is attachment + transfer: transfer covers readback (vkCmdCopyImageToBuffer), - // BlitFramebuffer, CopyTexImage sources, and out-of-render-pass clear materialization. - const VkImageUsageFlags imageUsage = - ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT - : VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - - // GL allows the implementation to allocate more samples than requested - // (glRenderbufferStorageMultisample only promises "at least"), and devices - // like llvmpipe expose 1x/4x but not 2x. Round the request up to the - // nearest supported count for this format. - if (renderbuffer->GetSamples() > 0) { - auto supportedIt = m_attachmentSampleCountsByFormat.find(format); - if (supportedIt == m_attachmentSampleCountsByFormat.end()) { - VkImageFormatProperties formatProperties{}; - VkSampleCountFlags supported = VK_SAMPLE_COUNT_1_BIT; - if (vkGetPhysicalDeviceImageFormatProperties(m_physicalDevice, format, VK_IMAGE_TYPE_2D, - VK_IMAGE_TILING_OPTIMAL, imageUsage, 0, - &formatProperties) == VK_SUCCESS) { - supported = formatProperties.sampleCounts; - } - supportedIt = m_attachmentSampleCountsByFormat.emplace(format, supported).first; - } - const VkSampleCountFlags supported = supportedIt->second; - if ((supported & sampleCount) == 0) { - // Smallest supported count above the request, else the largest below it. - Uint32 rounded = 0; - for (Uint32 bit = static_cast(sampleCount) << 1; bit <= VK_SAMPLE_COUNT_64_BIT; bit <<= 1) { - if ((supported & bit) != 0) { - rounded = bit; - break; - } - } - if (rounded == 0) { - for (Uint32 bit = static_cast(sampleCount) >> 1; bit != 0; bit >>= 1) { - if ((supported & bit) != 0) { - rounded = bit; - break; - } - } - } - if (rounded != 0) { - sampleCount = static_cast(rounded); - } - } - } - - auto& resource = m_renderbufferResources[renderbuffer.get()]; - const Bool needsCreate = - resource.image == VK_NULL_HANDLE || - resource.format != format || - resource.extent.width != static_cast(renderbuffer->GetWidth()) || - resource.extent.height != static_cast(renderbuffer->GetHeight()) || - resource.sampleCount != sampleCount || - resource.internalFormat != internalFormat || - resource.samples != renderbuffer->GetSamples(); - if (!needsCreate) { - resource.renderbuffer = renderbuffer; - // A new renderbuffer at a recycled address may adopt a compatible entry that - // was already stamped dead; it is alive again, so cancel the aging. - resource.deadSinceFrame = RenderbufferResource::kNeverObservedDead; - return &resource; - } - - // Respecify: park the old backing for aged destruction instead of destroying - // inline - it may still be referenced by in-flight command buffers. - DeferRenderbufferBackingRelease(resource); - resource.Destroy(m_device, m_allocator); - resource.renderbuffer = renderbuffer; - - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = static_cast(renderbuffer->GetWidth()); - imageInfo.extent.height = static_cast(renderbuffer->GetHeight()); - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = imageUsage; - imageInfo.samples = sampleCount; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - // sRGB renderbuffers attach through their UNORM twin while GL_FRAMEBUFFER_SRGB - // is disabled, which needs a format-reinterpreting second view. - const Bool hasUnormTwin = ResolveSrgbAttachmentWriteFormat(format, false) != format; - if (hasUnormTwin) { - imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - - VkImageFormatProperties imageFormatProperties{}; - const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( - m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, - &imageFormatProperties); - if (imageFormatResult != VK_SUCCESS || (imageFormatProperties.sampleCounts & sampleCount) == 0) { - MGLOG_E_ONCE("GetOrCreateRenderbufferResource: unsupported renderbuffer format=%d samples=%d for renderbuffer %u", - static_cast(format), - static_cast(sampleCount), - renderbuffer->GetExternalIndex()); - return nullptr; - } - - VmaAllocationCreateInfo allocationInfo{}; - allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr), - "vmaCreateImage(renderbuffer)"); - ++m_renderbufferImageEpoch; // a new attachment image invalidates cached render passes - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = resource.image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = format; - viewInfo.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, - VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; - viewInfo.subresourceRange.aspectMask = aspect; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.view), - "vkCreateImageView(renderbuffer)"); - if (hasUnormTwin) { - viewInfo.format = ResolveSrgbAttachmentWriteFormat(format, false); - VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &resource.unormTwinView), - "vkCreateImageView(renderbuffer unorm twin)"); - } - - resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; - resource.format = format; - resource.aspect = aspect; - resource.extent = {imageInfo.extent.width, imageInfo.extent.height}; - resource.sampleCount = sampleCount; - resource.internalFormat = internalFormat; - resource.samples = renderbuffer->GetSamples(); - return &resource; - } - - Bool VkRenderPassManager::GetPendingRenderbufferClear( - MG_State::GLState::RenderbufferObject* renderbuffer, ClearAttachmentPayload& outPayload) const { - if (renderbuffer == nullptr) { - return false; - } - auto it = m_pendingRenderbufferClears.find(renderbuffer); - if (it == m_pendingRenderbufferClears.end()) { - return false; - } - const auto liveRenderbuffer = it->second.renderbuffer.lock(); - if (!liveRenderbuffer || liveRenderbuffer.get() != renderbuffer) { - return false; - } - outPayload = it->second.payload; - return outPayload.mask != 0; - } - - Bool VkRenderPassManager::HasPendingRenderbufferClear( - const MG_State::GLState::FramebufferAttachmentObject& attachment) const { - if (!attachment.IsRenderbuffer() || !attachment.GetRenderbuffer()) { - return false; - } - ClearAttachmentPayload payload{}; - return GetPendingRenderbufferClear(attachment.GetRenderbuffer().get(), payload); - } - - void VkRenderPassManager::QueueRenderbufferClear( - const ClearAttachmentPayload& clearPayload, - const MG_State::GLState::FramebufferAttachmentObject& attachment) { - if (clearPayload.mask == 0 || !attachment.IsRenderbuffer() || !attachment.IsComplete()) { - return; - } - const auto renderbuffer = attachment.GetRenderbuffer(); - if (!renderbuffer) { - return; - } - auto& pending = m_pendingRenderbufferClears[renderbuffer.get()]; - pending.renderbuffer = renderbuffer; - pending.payload.mask |= clearPayload.mask; - if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { - // The whole colour description, not just the float vector: an integer clear keeps its - // value in colorInt/colorUint, and dropping the encoding here would leave the pending - // clear reading as an all-zero float one. - pending.payload.color = clearPayload.color; - pending.payload.colorEncoding = clearPayload.colorEncoding; - pending.payload.colorInt = clearPayload.colorInt; - pending.payload.colorUint = clearPayload.colorUint; - } - if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { - pending.payload.depth = clearPayload.depth; - } - if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { - pending.payload.stencil = clearPayload.stencil; - } - } - - void VkRenderPassManager::QueueRenderbufferClear( - GLbitfield mask, const ClearFramebufferPayload& clearPayload, - const MG_State::GLState::FramebufferObject& drawFbo) { - if ((mask & GL_COLOR_BUFFER_BIT) != 0) { - // Color renderbuffer draw buffers take the framebuffer-level clear too; texture - // attachments are skipped by the per-attachment overload's IsRenderbuffer guard. - for (const auto attachmentType : drawFbo.GetDrawBuffers()) { - if (attachmentType == FramebufferAttachmentType::None) { - continue; - } - QueueRenderbufferClear( - ClearAttachmentPayload{.mask = GL_COLOR_BUFFER_BIT, .color = clearPayload.color}, - drawFbo.GetAttachment(attachmentType)); - } - } - if ((mask & GL_DEPTH_BUFFER_BIT) != 0) { - QueueRenderbufferClear( - ClearAttachmentPayload{.mask = GL_DEPTH_BUFFER_BIT, .depth = clearPayload.depth}, - drawFbo.GetAttachment(FramebufferAttachmentType::Depth)); - } - if ((mask & GL_STENCIL_BUFFER_BIT) != 0) { - QueueRenderbufferClear( - ClearAttachmentPayload{.mask = GL_STENCIL_BUFFER_BIT, .stencil = clearPayload.stencil}, - drawFbo.GetAttachment(FramebufferAttachmentType::Stencil)); - } - } - - void VkRenderPassManager::PopPendingRenderbufferClear( - MG_State::GLState::RenderbufferObject* renderbuffer) { - if (renderbuffer != nullptr) { - m_pendingRenderbufferClears.erase(renderbuffer); - } - } - - VkRenderPassManager::HashType VkRenderPassManager::ComputeHash( - const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear, - Bool includeDefaultFboDepthStencil) { - XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); - const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); - if (isDefaultFbo) { - XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex))); - XXHASH_VERIFY(XXH64_update(m_hashState, &m_defaultFramebufferTarget, sizeof(m_defaultFramebufferTarget))); - } - // sRGB attachments switch between their sRGB and UNORM-twin views with this - // capability (ResolveSrgbAttachmentWriteFormat), changing the render pass formats. - const Bool framebufferSrgbEnabled = - MG_Pipe::gPipeInputs.IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb); - XXHASH_VERIFY(XXH64_update(m_hashState, &framebufferSrgbEnabled, sizeof(framebufferSrgbEnabled))); - auto& drawBuffers = fbo.GetDrawBuffers(); - XXHASH_VERIFY(XXH64_update(m_hashState, drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0]))); - auto readBuffer = fbo.GetReadBuffer(); - XXHASH_VERIFY(XXH64_update(m_hashState, &readBuffer, sizeof(FramebufferAttachmentType))); - Int validDrawBufCount = 0; - for (Int i = 0; i < drawBuffers.size(); ++i) { - auto drawbuf = drawBuffers[i]; - if (drawbuf != FramebufferAttachmentType::None) - validDrawBufCount = std::max(validDrawBufCount, i + 1); - } - XXHASH_VERIFY(XXH64_update(m_hashState, &validDrawBufCount, sizeof(validDrawBufCount))); - - auto combineFramebufferAttachmentObjHash = [&](FramebufferAttachmentType attachment) { - auto& att = fbo.GetAttachment(attachment); - - Int type = 0; - if (att.IsEmpty()) type = 0; - else if (att.IsTexture()) type = 1; - else if (att.IsRenderbuffer()) type = 2; - XXHASH_VERIFY(XXH64_update(m_hashState, &type, sizeof(type))); - void* contentPtr = nullptr; - if (att.IsTexture()) - contentPtr = att.GetTexture().get(); - else if (att.IsRenderbuffer()) - contentPtr = att.GetRenderbuffer().get(); - XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr))); - if (att.IsTexture()) { - const Uint64 textureLifetimeId = att.GetTexture()->GetLifetimeId(); - XXHASH_VERIFY(XXH64_update(m_hashState, &textureLifetimeId, sizeof(textureLifetimeId))); - const Int textureLevel = static_cast(ToStorageMipLevel(att.GetTexture().get(), - att.GetTextureLevel())); - XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel))); - const TextureUploadTarget textureUploadTarget = att.GetTextureUploadTarget(); - XXHASH_VERIFY(XXH64_update(m_hashState, &textureUploadTarget, sizeof(textureUploadTarget))); - const Int textureLayer = static_cast(ToStorageArrayLayer(att.GetTexture().get(), - att.GetTextureLayer())); - XXHASH_VERIFY(XXH64_update(m_hashState, &textureLayer, sizeof(textureLayer))); - const Bool textureLayered = att.IsLayered(); - XXHASH_VERIFY(XXH64_update(m_hashState, &textureLayered, sizeof(textureLayered))); - - Uint64 imageIdentity = 0; - auto* texture = att.GetTexture().get(); - auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture); - if (resource != nullptr) { - imageIdentity = reinterpret_cast(resource->image); - XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount))); - } else { - const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT; - XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount))); - } - XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity))); - } - - if (includePendingClear && att.IsTexture()) { - auto* texture = att.GetTexture().get(); - const auto pendingClearKey = VkClearManager::MakePendingClearKey(att); - auto hasClear = m_clearManager.HasPendingClear(pendingClearKey); - XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear))); - if (hasClear) { - ClearAttachmentPayload clearPayload{}; - Bool hasPayload = m_clearManager.GetPendingClear(pendingClearKey, clearPayload); - XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload))); - if (hasPayload) { - XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask))); - } - } - - VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; - if (isDefaultFbo) { - const Bool isDefaultColorAttachment = - attachment == FramebufferAttachmentType::Color0 || - (attachment >= FramebufferAttachmentType::FrontLeft && - attachment <= FramebufferAttachmentType::BackRight); - if (isDefaultColorAttachment) { - currentLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex); - // Content validity feeds the attachment's loadOp (see the - // creation path), so it must key the cache as well. - if (!m_swapchainObject.IsImageContentDefined(swapchainImageIndex)) { - currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; - } - } else if (attachment == FramebufferAttachmentType::Depth || - attachment == FramebufferAttachmentType::Stencil) { - currentLayout = m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex); - if (!m_swapchainObject.IsDepthStencilContentDefined(swapchainImageIndex)) { - currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; - } - } - } else { - auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture); - if (textureResource != nullptr) { - currentLayout = textureResource->layout; - } - } - XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout))); - } - if (att.IsRenderbuffer() && att.GetRenderbuffer()) { - const auto& renderbuffer = att.GetRenderbuffer(); - const auto internalFormat = renderbuffer->GetInternalFormat(); - const Int width = renderbuffer->GetWidth(); - const Int height = renderbuffer->GetHeight(); - const Int samples = renderbuffer->GetSamples(); - XXHASH_VERIFY(XXH64_update(m_hashState, &internalFormat, sizeof(internalFormat))); - XXHASH_VERIFY(XXH64_update(m_hashState, &width, sizeof(width))); - XXHASH_VERIFY(XXH64_update(m_hashState, &height, sizeof(height))); - XXHASH_VERIFY(XXH64_update(m_hashState, &samples, sizeof(samples))); - - Uint64 imageIdentity = 0; - VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; - auto* resource = GetOrCreateRenderbufferResource(renderbuffer); - if (resource != nullptr) { - imageIdentity = reinterpret_cast(resource->image); - currentLayout = resource->layout; - XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount))); - } else { - const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT; - XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount))); - } - XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity))); - - if (includePendingClear) { - const Bool hasClear = HasPendingRenderbufferClear(att); - XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear))); - if (hasClear) { - ClearAttachmentPayload clearPayload{}; - const Bool hasPayload = GetPendingRenderbufferClear(renderbuffer.get(), clearPayload); - XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload))); - if (hasPayload) { - XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask))); - } - } - XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout))); - } - } - }; - - for (Int i = 0; i < validDrawBufCount; ++i) { - auto drawbuf = drawBuffers[i]; - combineFramebufferAttachmentObjHash(drawbuf); - } - - // The depth-less default-FBO flavor omits the depth/stencil attachment - // entirely, so it must hash differently from the depth-full flavor. - const Bool depthStencilIncluded = !isDefaultFbo || includeDefaultFboDepthStencil; - XXHASH_VERIFY(XXH64_update(m_hashState, &depthStencilIncluded, sizeof(depthStencilIncluded))); - if (depthStencilIncluded) { - combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Depth); - combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Stencil); - } - - return XXH64_digest(m_hashState); - } - - RenderPassEntry* VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, - Uint32 swapchainImageIndex, - Bool drawUsesDepthStencil) { - // Resolve the default-FBO depth flavor (see the header comment): keep the - // depth attachment when the caller needs it, when a depth/stencil clear is - // pending, or when the active pass already carries it (escalate-only, so - // alternating depth-less draws never split an established depth pass). - Bool includeDefaultFboDepthStencil = true; - if (fbo.IsDefaultFramebuffer()) { - Bool activeDefaultHasDepthStencil = false; - if (const auto* active = GetActiveRenderPass()) { - Bool activeIsSwapchainPass = false; - Bool activeHasSwapchainDepthStencil = false; - for (const auto& tracked : active->trackedAttachmentLayouts) { - activeIsSwapchainPass |= tracked.target == TrackedAttachmentTarget::SwapchainColor; - activeHasSwapchainDepthStencil |= - tracked.target == TrackedAttachmentTarget::SwapchainDepthStencil; - } - activeDefaultHasDepthStencil = activeIsSwapchainPass && activeHasSwapchainDepthStencil; - } - const auto& defaultDepthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth); - const auto& defaultStencilAtt = fbo.GetAttachment(FramebufferAttachmentType::Stencil); - const Bool pendingDepthStencilClear = - (defaultDepthAtt.IsTexture() && m_clearManager.HasPendingClear(defaultDepthAtt)) || - HasPendingRenderbufferClear(defaultDepthAtt) || - (defaultStencilAtt.IsTexture() && m_clearManager.HasPendingClear(defaultStencilAtt)) || - HasPendingRenderbufferClear(defaultStencilAtt); - includeDefaultFboDepthStencil = - drawUsesDepthStencil || activeDefaultHasDepthStencil || pendingDepthStencilClear; - } - - auto hasPendingClearOnFramebuffer = [&]() -> Bool { - const auto& drawBuffers = fbo.GetDrawBuffers(); - for (auto attachment : drawBuffers) { - if (attachment == FramebufferAttachmentType::None) { - continue; - } - - const auto& att = fbo.GetAttachment(attachment); - if (att.IsTexture() && m_clearManager.HasPendingClear(att)) { - return true; - } - if (HasPendingRenderbufferClear(att)) { - return true; - } - } - - const auto& depthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth); - if (depthAtt.IsTexture() && m_clearManager.HasPendingClear(depthAtt)) { - return true; - } - if (HasPendingRenderbufferClear(depthAtt)) { - return true; - } - - const auto& stencilAtt = fbo.GetAttachment(FramebufferAttachmentType::Stencil); - if (stencilAtt.IsTexture() && m_clearManager.HasPendingClear(stencilAtt)) { - return true; - } - if (HasPendingRenderbufferClear(stencilAtt)) { - return true; - } - - return false; - }; - - // retrieve from cache first - auto* activeRenderPass = GetActiveRenderPass(); - - // Dirty-flag state tracking: when the framebuffer state is provably unchanged since the - // render pass was last resolved, the active render pass is still valid -> skip the - // expensive per-draw ComputeHash (XXH64 over every attachment + a SyncTexture per - // attachment). Correctness signals: same FBO object + GetObjectVersion (attachment / - // draw-buffer / read-buffer changes bump it), same swapchain image, no attachment VkImage - // recreated since (texture + renderbuffer image epochs), and no pending clear (which alters - // load ops). Any of these differing forces the full recompute below. Portable to VK 1.1. - if (activeRenderPass != nullptr && m_rpFastValid && m_rpFastFbo == &fbo && - m_rpFastFboLifetimeId == fbo.GetLifetimeId() && - m_rpFastFboVersion == fbo.GetObjectVersion() && m_rpFastSwapchainIndex == swapchainImageIndex && - m_rpFastTexEpoch == m_textureManager.GetTextureImageEpoch() && - m_rpFastRbEpoch == m_renderbufferImageEpoch && - (!fbo.IsDefaultFramebuffer() || m_rpFastHadDepthStencil == includeDefaultFboDepthStencil) && - m_rpFastRenderPassHash == activeRenderPass->hash && !hasPendingClearOnFramebuffer()) { - auto activeIt = m_renderPasses.find(activeRenderPass->hash); - if (activeIt != m_renderPasses.end()) { - activeIt->second.lastUsedFrame = m_frameCounter; - return &activeIt->second; - } - } - - auto compatibilityHash = ComputeHash(fbo, swapchainImageIndex, false, includeDefaultFboDepthStencil); - if (activeRenderPass != nullptr && - activeRenderPass->CompatibleWith(compatibilityHash) && - !hasPendingClearOnFramebuffer()) { - auto activeIt = m_renderPasses.find(activeRenderPass->hash); - MOBILEGL_ASSERT(activeIt != m_renderPasses.end(), - "GetOrCreateRenderPass: active render pass hash=0x%llx is missing from cache", - static_cast(activeRenderPass->hash)); - // Populate the fast-path memo so subsequent unchanged draws skip ComputeHash. Read the - // epochs AFTER ComputeHash: its attachment SyncTexture can create an image (bump the epoch). - m_rpFastValid = true; - m_rpFastFbo = &fbo; - m_rpFastFboLifetimeId = fbo.GetLifetimeId(); - m_rpFastFboVersion = fbo.GetObjectVersion(); - m_rpFastSwapchainIndex = swapchainImageIndex; - m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch(); - m_rpFastRbEpoch = m_renderbufferImageEpoch; - m_rpFastRenderPassHash = activeRenderPass->hash; - m_rpFastHadDepthStencil = activeIt->second.hasDepthStencilAttachment; - activeIt->second.lastUsedFrame = m_frameCounter; - return &activeIt->second; - } - auto hash = ComputeHash(fbo, swapchainImageIndex, true, includeDefaultFboDepthStencil); - auto it = m_renderPasses.find(hash); - if (it != m_renderPasses.end()) { - it->second.lastUsedFrame = m_frameCounter; - return &it->second; - } - - Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); - // Color attachment - auto& drawbufs = fbo.GetDrawBuffers(); - const Uint32 colorAttachmentSlotCount = static_cast(drawbufs.size()); - - const VkExtent2D swapchainExtent = m_swapchainObject.GetExtent(); - // Default framebuffer attachments are frontend placeholders; Vulkan framebuffer extent must match the swapchain. - const IntVec2 defaultFramebufferExtent = - ResolveRenderPassFramebufferExtent(isDefaultFbo, {0, 0, 0}, swapchainExtent); - Int width = defaultFramebufferExtent.x(); - Int height = defaultFramebufferExtent.y(); - Vector attachmentDescriptions; - attachmentDescriptions.reserve(colorAttachmentSlotCount + 1); - // Keep the full GL draw buffer slot span so fragment outputs targeting GL_NONE map to VK_ATTACHMENT_UNUSED. - Vector colorAttachmentRefs(colorAttachmentSlotCount); - for (auto& attachmentRef : colorAttachmentRefs) { - attachmentRef.attachment = VK_ATTACHMENT_UNUSED; - attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - } - Vector pendingClearAttachments; - pendingClearAttachments.reserve(colorAttachmentSlotCount + 1); - Vector trackedAttachmentLayouts; - trackedAttachmentLayouts.reserve(colorAttachmentSlotCount + 1); - auto& textureResources = RenderPassEntry::s_textureResourcesScratch; - textureResources.clear(); - textureResources.reserve(colorAttachmentSlotCount + 1); - Vector attachmentViews; - attachmentViews.reserve(colorAttachmentSlotCount + 1); - VkSampleCountFlagBits renderPassSampleCount = VK_SAMPLE_COUNT_1_BIT; - Bool hasRenderPassSampleCount = false; - Uint32 framebufferLayers = 1; - const auto adoptRenderPassSampleCount = [&](VkSampleCountFlagBits sampleCount, - const char* attachmentKind, - Int attachmentId) { - if (!hasRenderPassSampleCount) { - renderPassSampleCount = sampleCount; - hasRenderPassSampleCount = true; - return; - } - MOBILEGL_ASSERT(renderPassSampleCount == sampleCount, - "GetOrCreateRenderPass: mismatched sample count %d on %s attachment %d (expected %d)", - static_cast(sampleCount), attachmentKind, attachmentId, - static_cast(renderPassSampleCount)); - }; - // This should automatically work on default & offscreen FBO - // assuming default FBO has the right param - for (Uint32 i = 0; i < colorAttachmentSlotCount; ++i) { - auto drawbuf = drawbufs[i]; - - // Renderbuffer color attachments mirror the texture path below, with the - // resource (image/view/format/layout) coming from the render-pass manager's - // renderbuffer store instead of the texture manager. - if (drawbuf != FramebufferAttachmentType::None && !isDefaultFbo) { - const auto& rbAtt = fbo.GetAttachment(drawbuf); - if (rbAtt.IsRenderbuffer() && rbAtt.IsComplete()) { - const auto& renderbuffer = rbAtt.GetRenderbuffer(); - auto* rbResource = GetOrCreateRenderbufferResource(renderbuffer); - if (rbResource == nullptr || (rbResource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - MGLOG_E_ONCE("GetOrCreateRenderPass: draw buffer slot %u on FBO %u has an unsupported color " - "renderbuffer %u; using VK_ATTACHMENT_UNUSED", - i, fbo.GetExternalIndex(), renderbuffer->GetExternalIndex()); - continue; - } - - const Uint32 rbAttachmentIndex = static_cast(attachmentDescriptions.size()); - attachmentDescriptions.emplace_back(); - VkAttachmentDescription& rbDesc = attachmentDescriptions.back(); - - ClearAttachmentPayload rbClearPayload{}; - Bool rbHasClear = GetPendingRenderbufferClear(renderbuffer.get(), rbClearPayload) && - (rbClearPayload.mask & GL_COLOR_BUFFER_BIT) != 0; - if (rbHasClear && - MG_Util::GetBaseInternalFormatComponentCount(renderbuffer->GetInternalFormat()) == 3) { - // RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1. - ForceOpaqueClearAlpha(rbClearPayload); - } - - const VkImageLayout trackedRbLayout = rbResource->layout; - const Bool rbFramebufferSrgb = - MG_Pipe::gPipeInputs.IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb); - const VkFormat rbAttachmentFormat = - ResolveSrgbAttachmentWriteFormat(rbResource->format, rbFramebufferSrgb); - rbDesc.flags = 0; - rbDesc.format = rbAttachmentFormat; - rbDesc.samples = rbResource->sampleCount; - rbDesc.loadOp = rbHasClear ? VK_ATTACHMENT_LOAD_OP_CLEAR : - (trackedRbLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE - : VK_ATTACHMENT_LOAD_OP_LOAD); - rbDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - rbDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - rbDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - rbDesc.initialLayout = (rbHasClear || trackedRbLayout == VK_IMAGE_LAYOUT_UNDEFINED) ? - VK_IMAGE_LAYOUT_UNDEFINED : trackedRbLayout; - rbDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - adoptRenderPassSampleCount(rbResource->sampleCount, "color", - static_cast(renderbuffer->GetExternalIndex())); - - if (rbHasClear) { - pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { - .attachmentIndex = rbAttachmentIndex, - .colorAttachmentSlot = i, - .renderbuffer = renderbuffer.get(), - .hasInlinePayload = true, - .inlinePayload = rbClearPayload, - }); - } - - if (width == 0) - width = static_cast(rbResource->extent.width); - if (height == 0) - height = static_cast(rbResource->extent.height); - - trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { - .target = TrackedAttachmentTarget::Renderbuffer, - .renderbuffer = renderbuffer, - .finalLayout = rbDesc.finalLayout, - }); - textureResources.emplace_back(nullptr); - attachmentViews.emplace_back(rbAttachmentFormat != rbResource->format ? rbResource->unormTwinView - : rbResource->view); - if (attachmentViews.back() == VK_NULL_HANDLE) { - MGLOG_E_ONCE("GetOrCreateRenderPass: renderbuffer %u has no usable view for color attachment " - "%u on FBO %u; declining the render pass", - renderbuffer->GetExternalIndex(), i, fbo.GetExternalIndex()); - return nullptr; - } - - colorAttachmentRefs[i].attachment = rbAttachmentIndex; - continue; - } - } - - auto* texture = ResolveCompleteColorAttachmentTexture(fbo, drawbuf, i); - if (texture == nullptr) - continue; - - auto& att = fbo.GetAttachment(drawbuf); - const Uint32 attachmentMipLevel = ToStorageMipLevel(att.GetTexture().get(), att.GetTextureLevel()); - const auto textureTarget = texture->GetTarget(); - const Uint32 attachmentIndex = static_cast(attachmentDescriptions.size()); - attachmentDescriptions.emplace_back(); - - // Color attachment description - VkAttachmentDescription& desc = attachmentDescriptions.back(); - switch (textureTarget) { - case TextureTarget::Texture1D: - case TextureTarget::Texture1DArray: - case TextureTarget::Texture2D: - case TextureTarget::Texture2DArray: - case TextureTarget::Texture2DMultisample: - case TextureTarget::Texture2DMultisampleArray: - case TextureTarget::Texture3D: - case TextureTarget::TextureCubeMap: - case TextureTarget::TextureCubeMapArray: - case TextureTarget::TextureRectangle: { - desc.flags = 0; - desc.format = isDefaultFbo ? - m_swapchainObject.GetSurfaceFormat().format : - MG_Util::ConvertTextureInternalFormatToVkEnum( - texture->GetFormat()); - VkSampleCountFlagBits attachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; - ClearAttachmentPayload clearPayload{}; - Bool hasClear = m_clearManager.GetPendingClear(att, clearPayload); - VkImageLayout trackedColorLayout = VK_IMAGE_LAYOUT_UNDEFINED; - desc.loadOp = hasClear ? - VK_ATTACHMENT_LOAD_OP_CLEAR : - VK_ATTACHMENT_LOAD_OP_LOAD; - desc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - desc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - desc.finalLayout = isDefaultFbo ? - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - if (hasClear) { - pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { - .attachmentIndex = attachmentIndex, - .colorAttachmentSlot = i, - .key = VkClearManager::MakePendingClearKey(att) - }); - } - // Same remap as ResolveAttachmentLayerCount, for the same reason: a - // 1D-array attachment's GL height is its layer count, and using it as the - // framebuffer height asks for a framebuffer taller than the VK_IMAGE_TYPE_1D - // image it is built over. - const IntVec2 attachmentExtent = ResolveRenderPassFramebufferExtent( - isDefaultFbo, ToVulkanLevelExtent(texture->GetTarget(), att.GetSize()), swapchainExtent); - if (width == 0) - width = attachmentExtent.x(); - if (height == 0) - height = attachmentExtent.y(); - - if (isDefaultFbo) { - const auto& swapchainViews = m_swapchainObject.GetImageViews(); - MOBILEGL_ASSERT(swapchainImageIndex < swapchainViews.size(), - "GetOrCreateRenderPass: swapchain image index out of range"); - trackedColorLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex); - // EGL: a presented color buffer's content is undefined when its - // image comes back around (EGL_BUFFER_DESTROYED, the default - // swap behaviour) - skip the tile load instead of reloading - // stale pixels nobody may rely on. - if (!hasClear && !m_swapchainObject.IsImageContentDefined(swapchainImageIndex)) { - trackedColorLayout = VK_IMAGE_LAYOUT_UNDEFINED; - } - trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { - .target = TrackedAttachmentTarget::SwapchainColor, - .swapchainImageIndex = swapchainImageIndex, - .finalLayout = desc.finalLayout, - }); - attachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; - textureResources.emplace_back(nullptr); - attachmentViews.emplace_back(swapchainViews[swapchainImageIndex]); - } else { - auto* textureResource = m_textureManager.SyncTextureAndGetDescriptor(*texture); - if (textureResource == nullptr) { - // SyncTextureResource legitimately declines - an unsupported format, - // sample count or image-flag combination, or a vkCreateImage the driver - // refused. There is no image to attach, so there is no render pass. - MGLOG_E_ONCE("GetOrCreateRenderPass: textureId=%d could not be backed for color " - "attachment %u on FBO %u; declining the render pass", - texture->GetExternalIndex(), i, fbo.GetExternalIndex()); - return nullptr; - } - textureResources.emplace_back(textureResource); - desc.format = ResolveSrgbAttachmentWriteFormat( - textureResource->format, - MG_Pipe::gPipeInputs.IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb)); - attachmentSampleCount = textureResource->sampleCount; - trackedColorLayout = textureResource->layout; - trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { - .target = TrackedAttachmentTarget::Texture, - .texture = att.GetTexture(), - .textureRaw = att.GetTexture().get(), - .textureMipLevel = attachmentMipLevel, - .finalLayout = desc.finalLayout, - }); - const Uint32 baseArrayLayer = ResolveAttachmentBaseArrayLayer(att); - const Uint32 layerCount = ResolveAttachmentLayerCount(att); - framebufferLayers = std::max(framebufferLayers, layerCount); - const VkImageViewType attachmentViewType = ResolveAttachmentViewType(att, *textureResource); - attachmentViews.emplace_back( - m_textureManager.GetOrCreateAttachmentViewAtMipLevel( - *texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType)); - if (attachmentViews.back() == VK_NULL_HANDLE) { - MGLOG_E_ONCE("GetOrCreateRenderPass: no attachment view for textureId=%d mip=%u layers " - "[%u, %u) viewType=%d at color attachment %u on FBO %u; declining the " - "render pass", - texture->GetExternalIndex(), attachmentMipLevel, baseArrayLayer, - baseArrayLayer + layerCount, static_cast(attachmentViewType), i, - fbo.GetExternalIndex()); - return nullptr; - } - } - desc.samples = attachmentSampleCount; - adoptRenderPassSampleCount(attachmentSampleCount, "color", texture->GetExternalIndex()); - - if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_W_ONCE("GetOrCreateRenderPass: color attachment textureId=%d starts with undefined layout and no clear; " - "using LOAD_OP_DONT_CARE", - texture->GetExternalIndex()); - desc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - } - desc.initialLayout = (hasClear || trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) ? - VK_IMAGE_LAYOUT_UNDEFINED : - trackedColorLayout; - - break; - } - default: - MOBILEGL_ASSERT(false, "Unsupported texture target"); - } - - // Attachment reference - VkAttachmentReference& attachmentRef = colorAttachmentRefs[i]; - attachmentRef.attachment = attachmentIndex; - attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - } - - // Depth/stencil attachment description - auto& depthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth); - auto& stencilAtt = fbo.GetAttachment(FramebufferAttachmentType::Stencil); - VkAttachmentDescription depthAttachmentDescription; - VkAttachmentReference depthAttachmentRef; - depthAttachmentRef.attachment = VK_ATTACHMENT_UNUSED; - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - VkTextureManager::TextureResource* depthTextureResource = nullptr; - RenderbufferResource* depthRenderbufferResource = nullptr; - const auto isUsableDepthStencilAttachment = [](const auto& attachment) { - return attachment.IsComplete() && (attachment.IsTexture() || attachment.IsRenderbuffer()); - }; - const auto sameDepthStencilAttachmentObject = [](const auto& a, const auto& b) { - if (a.IsTexture() && b.IsTexture()) { - return a.GetTexture().get() == b.GetTexture().get() && - a.GetTextureUploadTarget() == b.GetTextureUploadTarget() && - ToStorageMipLevel(a.GetTexture().get(), a.GetTextureLevel()) == - ToStorageMipLevel(b.GetTexture().get(), b.GetTextureLevel()); - } - if (a.IsRenderbuffer() && b.IsRenderbuffer()) { - return a.GetRenderbuffer().get() == b.GetRenderbuffer().get(); - } - return false; - }; - const auto* selectedDepthStencilAttachment = isUsableDepthStencilAttachment(depthAtt) ? &depthAtt : - (isUsableDepthStencilAttachment(stencilAtt) ? &stencilAtt : nullptr); - // Depth-less default-FBO flavor: nothing in this pass touches depth/stencil - // and their content is undefined anyway (EGL swap), so drop the attachment - // and its whole tile load + store. - if (isDefaultFbo && !includeDefaultFboDepthStencil) { - selectedDepthStencilAttachment = nullptr; - } - const Bool hasDistinctDepthAndStencilAttachments = - isUsableDepthStencilAttachment(depthAtt) && isUsableDepthStencilAttachment(stencilAtt) && - !sameDepthStencilAttachmentObject(depthAtt, stencilAtt); - if (hasDistinctDepthAndStencilAttachments) { - MGLOG_E_ONCE("GetOrCreateRenderPass: separate depth/stencil attachments are not supported yet; using the depth attachment and ignoring the standalone stencil attachment for framebuffer %u", - fbo.GetExternalIndex()); - } - if (selectedDepthStencilAttachment != nullptr) { - const Uint32 depthAttachmentIndex = static_cast(attachmentDescriptions.size()); - ClearAttachmentPayload clearPayload{}; - Bool hasClear = selectedDepthStencilAttachment->IsTexture() - ? m_clearManager.GetPendingClear(*selectedDepthStencilAttachment, clearPayload) - : GetPendingRenderbufferClear(selectedDepthStencilAttachment->GetRenderbuffer().get(), clearPayload); - Bool clearDepth = hasClear && (clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0; - Bool clearStencil = hasClear && (clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0; - VkImageLayout trackedDepthLayout = isDefaultFbo ? - m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex) : - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - // EGL 1.5 §3.10.1: every ancillary (depth/stencil) buffer's content is - // undefined after a swap, so the first default-FBO pass of a frame can - // skip the depth/stencil tile load outright. - if (isDefaultFbo && !m_swapchainObject.IsDepthStencilContentDefined(swapchainImageIndex)) { - trackedDepthLayout = VK_IMAGE_LAYOUT_UNDEFINED; - } - depthAttachmentDescription.flags = 0; - VkSampleCountFlagBits depthAttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; - Int depthAttachmentId = 0; - IntVec2 attachmentExtent = {0, 0}; - if (isDefaultFbo) { - depthAttachmentDescription.format = m_swapchainObject.GetDepthStencilFormat(); - depthAttachmentId = 0; - } else if (selectedDepthStencilAttachment->IsTexture()) { - auto& texture = *selectedDepthStencilAttachment->GetTexture(); - depthTextureResource = m_textureManager.SyncTextureAndGetDescriptor(texture); - if (depthTextureResource == nullptr) { - MGLOG_E_ONCE("GetOrCreateRenderPass: textureId=%d could not be backed for the depth/stencil " - "attachment of FBO %u; declining the render pass", - texture.GetExternalIndex(), fbo.GetExternalIndex()); - return nullptr; - } - trackedDepthLayout = depthTextureResource->layout; - depthAttachmentDescription.format = depthTextureResource->format; - depthAttachmentSampleCount = depthTextureResource->sampleCount; - depthAttachmentId = static_cast(texture.GetExternalIndex()); - attachmentExtent = ResolveRenderPassFramebufferExtent( - isDefaultFbo, - ToVulkanLevelExtent(texture.GetTarget(), selectedDepthStencilAttachment->GetSize()), - swapchainExtent); - } else { - const auto& renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer(); - depthRenderbufferResource = GetOrCreateRenderbufferResource(renderbuffer); - if (depthRenderbufferResource == nullptr) { - MGLOG_E_ONCE("GetOrCreateRenderPass: renderbuffer %u could not be backed for the depth/stencil " - "attachment of FBO %u; declining the render pass", - renderbuffer->GetExternalIndex(), fbo.GetExternalIndex()); - return nullptr; - } - trackedDepthLayout = depthRenderbufferResource->layout; - depthAttachmentDescription.format = depthRenderbufferResource->format; - depthAttachmentSampleCount = depthRenderbufferResource->sampleCount; - depthAttachmentId = static_cast(renderbuffer->GetExternalIndex()); - attachmentExtent = {static_cast(depthRenderbufferResource->extent.width), - static_cast(depthRenderbufferResource->extent.height)}; - } - depthAttachmentDescription.samples = depthAttachmentSampleCount; - adoptRenderPassSampleCount(depthAttachmentSampleCount, "depth/stencil", depthAttachmentId); - const auto loadInfo = - ResolveDepthStencilAttachmentLoadInfo(trackedDepthLayout, clearDepth, clearStencil); - depthAttachmentDescription.loadOp = loadInfo.depthLoadOp; - depthAttachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - depthAttachmentDescription.stencilLoadOp = loadInfo.stencilLoadOp; - depthAttachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; - depthAttachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthAttachmentDescription.initialLayout = loadInfo.initialLayout; - if (trackedDepthLayout == VK_IMAGE_LAYOUT_UNDEFINED && (!clearDepth || !clearStencil)) { - MGLOG_W_ONCE("GetOrCreateRenderPass: depth/stencil attachment id=%d starts with undefined layout " - "and partial/no clear; using DONT_CARE for uncleared aspects", - depthAttachmentId); - } - if (hasClear) { - if (selectedDepthStencilAttachment->IsTexture()) { - pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { - .attachmentIndex = depthAttachmentIndex, - .key = VkClearManager::MakePendingClearKey(*selectedDepthStencilAttachment) - }); - } else { - pendingClearAttachments.emplace_back(PendingClearAttachmentInfo { - .attachmentIndex = depthAttachmentIndex, - .renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer().get(), - .hasInlinePayload = true, - .inlinePayload = clearPayload - }); - } - } - if (isDefaultFbo) { - trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { - .target = TrackedAttachmentTarget::SwapchainDepthStencil, - .swapchainImageIndex = swapchainImageIndex, - .finalLayout = depthAttachmentDescription.finalLayout, - }); - attachmentViews.emplace_back(m_swapchainObject.GetDepthStencilImageView(swapchainImageIndex)); - } else if (selectedDepthStencilAttachment->IsTexture()) { - auto& texture = *selectedDepthStencilAttachment->GetTexture(); - const Uint32 attachmentMipLevel = - ToStorageMipLevel(selectedDepthStencilAttachment->GetTexture().get(), - selectedDepthStencilAttachment->GetTextureLevel()); - MOBILEGL_ASSERT(depthTextureResource->layout != VK_IMAGE_LAYOUT_UNDEFINED || - depthAttachmentDescription.loadOp != VK_ATTACHMENT_LOAD_OP_LOAD, - "GetOrCreateRenderPass: depth attachment textureId=%d has undefined tracked layout with LOAD_OP_LOAD", - texture.GetExternalIndex()); - MOBILEGL_ASSERT(depthTextureResource->layout != VK_IMAGE_LAYOUT_UNDEFINED || - depthAttachmentDescription.stencilLoadOp != VK_ATTACHMENT_LOAD_OP_LOAD, - "GetOrCreateRenderPass: stencil attachment textureId=%d has undefined tracked layout with LOAD_OP_LOAD", - texture.GetExternalIndex()); - trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { - .target = TrackedAttachmentTarget::Texture, - .texture = selectedDepthStencilAttachment->GetTexture(), - .textureRaw = selectedDepthStencilAttachment->GetTexture().get(), - .textureMipLevel = attachmentMipLevel, - .finalLayout = depthAttachmentDescription.finalLayout, - }); - textureResources.emplace_back(depthTextureResource); - const Uint32 baseArrayLayer = ResolveAttachmentBaseArrayLayer(*selectedDepthStencilAttachment); - const Uint32 layerCount = ResolveAttachmentLayerCount(*selectedDepthStencilAttachment); - framebufferLayers = std::max(framebufferLayers, layerCount); - const VkImageViewType attachmentViewType = - ResolveAttachmentViewType(*selectedDepthStencilAttachment, *depthTextureResource); - attachmentViews.emplace_back( - m_textureManager.GetOrCreateAttachmentViewAtMipLevel( - texture, attachmentMipLevel, baseArrayLayer, layerCount, attachmentViewType)); - if (attachmentViews.back() == VK_NULL_HANDLE) { - MGLOG_E_ONCE("GetOrCreateRenderPass: no attachment view for textureId=%d mip=%u layers [%u, %u) " - "viewType=%d at the depth/stencil attachment of FBO %u; declining the render pass", - texture.GetExternalIndex(), attachmentMipLevel, baseArrayLayer, - baseArrayLayer + layerCount, static_cast(attachmentViewType), - fbo.GetExternalIndex()); - return nullptr; - } - if (width == 0 || height == 0) { - width = attachmentExtent.x(); - height = attachmentExtent.y(); - } - } else { - const auto& renderbuffer = selectedDepthStencilAttachment->GetRenderbuffer(); - MOBILEGL_ASSERT(depthRenderbufferResource->layout != VK_IMAGE_LAYOUT_UNDEFINED || - depthAttachmentDescription.loadOp != VK_ATTACHMENT_LOAD_OP_LOAD, - "GetOrCreateRenderPass: depth renderbuffer %u has undefined tracked layout with LOAD_OP_LOAD", - renderbuffer->GetExternalIndex()); - MOBILEGL_ASSERT(depthRenderbufferResource->layout != VK_IMAGE_LAYOUT_UNDEFINED || - depthAttachmentDescription.stencilLoadOp != VK_ATTACHMENT_LOAD_OP_LOAD, - "GetOrCreateRenderPass: stencil renderbuffer %u has undefined tracked layout with LOAD_OP_LOAD", - renderbuffer->GetExternalIndex()); - trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { - .target = TrackedAttachmentTarget::Renderbuffer, - .renderbuffer = renderbuffer, - .finalLayout = depthAttachmentDescription.finalLayout, - }); - textureResources.emplace_back(nullptr); - attachmentViews.emplace_back(depthRenderbufferResource->view); - if (attachmentViews.back() == VK_NULL_HANDLE) { - MGLOG_E_ONCE("GetOrCreateRenderPass: renderbuffer %u has no usable view for the depth/stencil " - "attachment of FBO %u; declining the render pass", - renderbuffer->GetExternalIndex(), fbo.GetExternalIndex()); - return nullptr; - } - if (width == 0 || height == 0) { - width = attachmentExtent.x(); - height = attachmentExtent.y(); - } - } - attachmentDescriptions.emplace_back(depthAttachmentDescription); - depthAttachmentRef.attachment = depthAttachmentIndex; - } - const Bool hasDepthStencilAttachment = depthAttachmentRef.attachment != VK_ATTACHMENT_UNUSED; - - // Declare only the used colour-reference span. The GL draw-buffer array - // always spans 8 slots, so passes used to declare colorAttachmentCount=8 - // with trailing VK_ATTACHMENT_UNUSED holes - and Adreno configures its - // per-pixel render-backend/export path from the DECLARED count, so every - // fragment of every pass paid the 8-target export cost (measured on - // Adreno 650 / MC 26.2: 11.9 -> 7.5 ms of GPU time per frame, with the - // single-quad swapchain blit pass alone dropping 1.26 -> 0.40 ms). - // Interior GL_NONE holes keep their slots so fragment-output locations - // still line up; a fragment output at a location past the trimmed count - // is discarded, which is exactly GL's semantic for writing to a draw - // buffer set to GL_NONE. - while (!colorAttachmentRefs.empty() && - colorAttachmentRefs.back().attachment == VK_ATTACHMENT_UNUSED) { - colorAttachmentRefs.pop_back(); - } - - // Subpass - VkSubpassDescription subpassDesc; - subpassDesc.flags = 0; - subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpassDesc.inputAttachmentCount = 0; - subpassDesc.pInputAttachments = nullptr; - subpassDesc.colorAttachmentCount = colorAttachmentRefs.size(); - subpassDesc.pColorAttachments = colorAttachmentRefs.data(); - subpassDesc.pResolveAttachments = nullptr; - subpassDesc.pDepthStencilAttachment = hasDepthStencilAttachment ? &depthAttachmentRef : VK_NULL_HANDLE; - subpassDesc.preserveAttachmentCount = 0; - subpassDesc.pPreserveAttachments = nullptr; - - // External subpass dependencies. Without them there is NO execution/memory - // dependency between consecutive render passes (or a pass and a transfer) - // touching the same attachments when the image layout does not change — the - // layout-transition helper no-ops on identical layouts and no other barrier - // exists. Tile-based GPUs then race tile loads against the previous pass's - // stores (multi-pass FBO chains like MC 26.3's OIT flicker on Adreno). - // Conservative both-ways dependencies: prior writes (attachment output, - // depth/stencil, transfer, shader) are made visible to this pass's loads, - // and this pass's attachment writes to subsequent sampling/transfer/loads. - VkSubpassDependency subpassDependencies[2]; - subpassDependencies[0] = {}; - subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; - subpassDependencies[0].dstSubpass = 0; - subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; - subpassDependencies[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | - VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - subpassDependencies[0].dstAccessMask = - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | - VK_ACCESS_SHADER_READ_BIT; - subpassDependencies[0].dependencyFlags = 0; - subpassDependencies[1] = {}; - subpassDependencies[1].srcSubpass = 0; - subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; - subpassDependencies[1].srcStageMask = - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - subpassDependencies[1].srcAccessMask = - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | - VK_PIPELINE_STAGE_TRANSFER_BIT; - subpassDependencies[1].dstAccessMask = - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - subpassDependencies[1].dependencyFlags = 0; - - // Render Pass - VkRenderPassCreateInfo renderPassCreateInfo; - renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassCreateInfo.pNext = VK_NULL_HANDLE; - renderPassCreateInfo.flags = 0; - renderPassCreateInfo.attachmentCount = attachmentDescriptions.size(); - renderPassCreateInfo.pAttachments = attachmentDescriptions.data(); - renderPassCreateInfo.subpassCount = 1; - renderPassCreateInfo.pSubpasses = &subpassDesc; - renderPassCreateInfo.dependencyCount = 2; - renderPassCreateInfo.pDependencies = subpassDependencies; - - // NOT VK_VERIFY. VkIncludes.h states the rule this function now lives by: VK_VERIFY is the - // INVARIANT check - a should-never-happen state, fatal-logged unlatched and trapped in a - // DEBUG build - and "a soft, recoverable failure must therefore NOT be routed through - // VK_VERIFY. Check the VkResult directly and report it with MGLOG_E_ONCE". A decline here - // is recoverable by construction: the caller drops the draw. Routing it through VK_VERIFY - // would have made the recovery dead code in a DEBUG build (the TRAP fires inside the macro, - // before the handle is ever examined) and, in an INFO build, printed an UNLATCHED fatal - // line on every draw for the life of the process - a decline caches nothing, so every - // later draw to the same framebuffer re-enters this path and fails again. - VkRenderPass renderPass = VK_NULL_HANDLE; - const VkResult renderPassResult = - vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &renderPass); - if (renderPassResult != VK_SUCCESS || renderPass == VK_NULL_HANDLE) { - MGLOG_E_ONCE("GetOrCreateRenderPass: vkCreateRenderPass failed (%s, %d) for FBO %u; declining the " - "render pass", - VkResultToString(renderPassResult), static_cast(renderPassResult), - fbo.GetExternalIndex()); - return nullptr; - } - - // Framebuffer - VkFramebufferCreateInfo framebufferCreateInfo; - framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferCreateInfo.pNext = nullptr; - framebufferCreateInfo.flags = 0; - framebufferCreateInfo.renderPass = renderPass; - framebufferCreateInfo.attachmentCount = attachmentViews.size(); - framebufferCreateInfo.pAttachments = attachmentViews.data(); - framebufferCreateInfo.width = width; - framebufferCreateInfo.height = height; - framebufferCreateInfo.layers = framebufferLayers; - // Direct VkResult check, for the same reason as vkCreateRenderPass above. - VkFramebuffer framebuffer = VK_NULL_HANDLE; - const VkResult framebufferResult = - vkCreateFramebuffer(m_device, &framebufferCreateInfo, nullptr, &framebuffer); - if (framebufferResult != VK_SUCCESS || framebuffer == VK_NULL_HANDLE) { - // The render pass has no entry to own it yet, so it is destroyed here rather than - // leaked - RenderPassEntry's destructor is the only other thing that would. - MGLOG_E_ONCE("GetOrCreateRenderPass: vkCreateFramebuffer failed (%s, %d) for FBO %u (%dx%d, " - "%u attachments, %u layers); declining the render pass", - VkResultToString(framebufferResult), static_cast(framebufferResult), - fbo.GetExternalIndex(), width, height, - static_cast(attachmentViews.size()), framebufferLayers); - vkDestroyRenderPass(m_device, renderPass, nullptr); - return nullptr; - } - IntVec2 extent = {width, height}; - RenderPassEntry renderPassEntry { - hash, - renderPass, - framebuffer, - compatibilityHash, - Move(pendingClearAttachments), - Move(trackedAttachmentLayouts), - static_cast(attachmentViews.size()), - static_cast(colorAttachmentRefs.size()), - hasDepthStencilAttachment, - renderPassSampleCount, - extent, - framebufferLayers }; - MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u samples=%d extent=%dx%d", - static_cast(hash), - static_cast(compatibilityHash), - renderPassEntry.attachmentCount, - renderPassEntry.colorAttachmentCount, - static_cast(renderPassEntry.sampleCount), - extent.x(), - extent.y()); - auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry)); - insertedIt->second.lastUsedFrame = m_frameCounter; - return &insertedIt->second; - } - void VkRenderPassManager::PurgeRenderPasses() { Vector destroyedRenderPasses; destroyedRenderPasses.reserve(m_renderPasses.size()); for (auto& [_, entry] : m_renderPasses) destroyedRenderPasses.push_back(entry.renderPass); m_renderPasses.clear(); - m_rpFastValid = false; if (!destroyedRenderPasses.empty() && m_evictionObserver != nullptr) { m_evictionObserver->OnRenderPassesDestroyed(destroyedRenderPasses); } @@ -1572,9 +227,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (auto it = m_renderPasses.begin(); it != m_renderPasses.end();) { const Bool isActive = s_hasActiveRenderPass.Get() && it->first == activeHash; if (!isActive && m_frameCounter - it->second.lastUsedFrame > kRetireAgeFrames) { - if (m_rpFastValid && m_rpFastRenderPassHash == it->first) { - m_rpFastValid = false; - } destroyedRenderPasses.push_back(it->second.renderPass); it = m_renderPasses.erase(it); } else { @@ -1602,48 +254,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { clearValue.color = {0.0f, 0.0f, 0.0f, 1.0f}; clearValue.depthStencil = {1.0f, 0}; } - for (const auto& pending: renderPassEntry.pendingClearAttachments) { - if (pending.attachmentIndex >= clearValues.size()) { - continue; - } - ClearAttachmentPayload clearPayload{}; - SharedPtr liveTexture; - if (pending.hasInlinePayload) { - // The inline payload was snapshotted when the entry was CREATED, but the - // clear VALUE is not part of the entry's hash - a cache hit with a newer - // glClear would replay the creation-time value and drop the new one (the - // texture path below is immune because it re-reads the live payload). - // Same defense as ClearAttachmentsOnActiveRenderPass: prefer the live - // pending clear, fall back to the snapshot only when none is queued. - if (s_renderPassManager.Get() != nullptr && - s_renderPassManager.Get()->GetPendingRenderbufferClear(pending.renderbuffer, clearPayload)) { - if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0 && pending.renderbuffer != nullptr && - MG_Util::GetBaseInternalFormatComponentCount(pending.renderbuffer->GetInternalFormat()) == - 3) { - // RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1. - ForceOpaqueClearAlpha(clearPayload); - } - } else { - clearPayload = pending.inlinePayload; - } - } else { - if (pending.key.texture == nullptr || - !s_clearManager.Get()->GetPendingClear(pending.key, clearPayload, liveTexture)) { - continue; - } - } - if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { - clearValues[pending.attachmentIndex].color = - MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(liveTexture.get())); - } - if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { - clearValues[pending.attachmentIndex].depthStencil.depth = clearPayload.depth; - } - if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { - clearValues[pending.attachmentIndex].depthStencil.stencil = clearPayload.stencil; - } - } - renderPassBeginInfo.clearValueCount = static_cast(clearValues.size()); renderPassBeginInfo.pClearValues = clearValues.data(); @@ -1659,15 +269,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } } - for (const auto& pending: renderPassEntry.pendingClearAttachments) { - if (pending.hasInlinePayload) { - if (s_renderPassManager.Get() != nullptr) { - s_renderPassManager.Get()->PopPendingRenderbufferClear(pending.renderbuffer); - } - } else { - s_clearManager.Get()->PopPendingClear(pending.key); - } - } s_activeRenderPass.Get().commandBuffer = commandBuffer; s_activeRenderPass.Get().hash = renderPassEntry.hash; s_activeRenderPass.Get().compatibilityHash = renderPassEntry.compatibilityHash; @@ -1690,11 +291,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // pass. Detach the ended pass first so that guard cannot recursively end it again. s_activeRenderPass.Get() = {}; s_hasActiveRenderPass.Get() = false; - // The fast-path memo reuses the ACTIVE render pass; once the pass ends it must not carry - // over (the next span may be a different FBO resolved before its render pass is begun). - if (s_renderPassManager.Get() != nullptr) { - s_renderPassManager.Get()->m_rpFastValid = false; - } if (!trackedAttachmentLayouts.empty()) { for (const auto& trackedAttachment : trackedAttachmentLayouts) { switch (trackedAttachment.target) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index e9c19c23..35db1b65 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -232,51 +232,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // targets): a default-framebuffer pass of one surface is not another's. void SetDefaultFramebufferTarget(Uint64 serial) { m_defaultFramebufferTarget = serial; - m_rpFastValid = false; } // Destroys every cached pass and framebuffer (not the renderbuffers). The device must be // idle and no pass may be open: a surface target's image views are going away. void PurgeRenderPasses(); - HashType ComputeHash( - const MG_State::GLState::FramebufferObject& fbo, - Uint32 swapchainImageIndex, - Bool includePendingClear = true, - Bool includeDefaultFboDepthStencil = true); - // drawUsesDepthStencil: whether the operation about to run inside the pass - // reads or writes the depth/stencil buffer (depth test or stencil test - // enabled, or a depth/stencil clear). Only consulted for the DEFAULT - // framebuffer: EGL undefines its ancillary buffers at every swap, so a - // default-FBO pass whose draws provably never touch depth/stencil is - // created WITHOUT the depth attachment - on a tiler that skips the whole - // depth tile load AND store. The flavor only escalates: once a pass with - // depth is active, later depth-less draws keep using it, and a depth-using - // draw against a depth-less active pass resolves to a new (incompatible) - // entry, which the caller's compatibility check turns into a pass split; - // the new pass's depth loads DONT_CARE (content was undefined all along). - // - // Returns NULLPTR when this framebuffer cannot be represented as a Vulkan render pass at - // all - a texture the texture manager declined to back (an unsupported format or sample - // count), or an attachment view it cannot construct (a layer span the image has no room - // for, a 3D image whose format was refused 2D-array compatibility). This used to be - // unrepresentable: the function returned a reference, so the only thing the two fallible - // calls it builds on could do was trip a MOBILEGL_ASSERT - which is compiled out of every - // INFO build - and then dereference the null resource, or hand VK_NULL_HANDLE to - // vkCreateFramebuffer. That took the whole process down (51 lost CTS records over 21 - // bodies, one runner restart each) where a declined draw is merely a wrong picture. - // - // EVERY caller must handle nullptr by dropping the operation, exactly as the draw path - // already drops a draw whose sampler descriptor could not be resolved - // (UniformManager::BindProgramUniformBuffers). The failure paths log MGLOG_E_ONCE - // themselves, so a caller needs no message of its own. - [[nodiscard]] RenderPassEntry* GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, - Uint32 swapchainImageIndex, - Bool drawUsesDepthStencil = true); - void QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, - const MG_State::GLState::FramebufferObject& drawFbo); - void QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload, - const MG_State::GLState::FramebufferAttachmentObject& attachment); - void PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer); // Frame boundary hook: ages the render-pass cache and evicts long-unused // entries (their command buffers retired many frames ago). void OnPresent(); @@ -308,28 +268,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { private: - // Per-draw fast-path memo for GetOrCreateRenderPass (dirty-flag state tracking): when the - // framebuffer state is provably unchanged since the last resolution, the active render pass - // is reused WITHOUT recomputing the expensive per-draw hash. Invalidated by FBO switch / - // version change, swapchain rotation, any attachment image recreation (the two epochs), - // or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed). Uint64 m_defaultFramebufferTarget = 0; - Bool m_rpFastValid = false; - const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr; - // The FBO's never-reused lifetime id joins the raw pointer + Uint16 version: - // a deleted FBO reallocated at the same address whose fresh setup performed - // the same number of version bumps would otherwise compare equal (both count - // from 0), serving the dead framebuffer's pass to the new object. - Uint64 m_rpFastFboLifetimeId = 0; - Uint16 m_rpFastFboVersion = 0; - Uint32 m_rpFastSwapchainIndex = 0; - Uint64 m_rpFastTexEpoch = 0; - Uint64 m_rpFastRbEpoch = 0; - Uint64 m_rpFastRenderPassHash = 0; - // Whether the memoized entry carries a depth/stencil attachment; a - // default-FBO resolution whose effective depth request differs must - // miss the memo (the depth-less/depth-full flavors hash differently). - Bool m_rpFastHadDepthStencil = false; public: struct RenderbufferResource { @@ -359,13 +298,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Destroy(VkDevice device, VmaAllocator allocator); }; - // Public so the renderer's blit/copy/readback bindings can source renderbuffer - // attachments the same way texture attachments go through the texture manager. - RenderbufferResource* GetOrCreateRenderbufferResource( - const SharedPtr& renderbuffer); - Bool GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer, - ClearAttachmentPayload& outPayload) const; - private: struct PendingRenderbufferClear { WeakPtr renderbuffer; @@ -414,8 +346,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // do not repeat vkGetPhysicalDeviceImageFormatProperties. UnorderedMap m_attachmentSampleCountsByFormat; - Bool HasPendingRenderbufferClear( - const MG_State::GLState::FramebufferAttachmentObject& attachment) const; void CollectRenderbufferGarbage(); // Frame-boundary margin after which a resource last referenced by a retired // GL object (or superseded backing) is provably past every in-flight frame. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp index 784a1757..b87a6198 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp @@ -440,12 +440,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return vkSampler; } - VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler, - const MG_State::GLState::ITextureObject& texture, - Bool forceNearestFiltering, Uint32 viewLevelCount) { - return GetOrCreateSamplerImpl(sampler, texture.GetFormat(), forceNearestFiltering, viewLevelCount); - } - VkSampler VkSamplerManager::GetOrCreateSamplerFromParameters(const SamplerParameters& parameters, TextureInternalFormat format, Bool forceNearestFiltering, Uint32 viewLevelCount) { return GetOrCreateSamplerImpl(SamplerParametersSource{parameters}, format, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h index e0870255..da5a4f97 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h @@ -40,12 +40,6 @@ public: Bool Initialize(const InitInfo& initInfo); void Shutdown(); - // viewLevelCount is the mip-level count of the image view this sampler will be paired - // with; 0 means "unknown, do not narrow". See GetOrCreateSampler for why it matters. - VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler, - const MG_State::GLState::ITextureObject& texture, - Bool forceNearestFiltering = false, - Uint32 viewLevelCount = 0); // The applier owns these values: effective bound/builtin sampler parameters and // the sampled texture view's format/range. No frontend object is constructed. VkSampler GetOrCreateSamplerFromParameters(const SamplerParameters& parameters, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 2f500d8f..e11198d1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -866,271 +866,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return resourcePtr; } - VkImageView VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) { - TextureResource* resource = SyncTextureAndGetDescriptor(texture); - if (resource == nullptr || resource->image == VK_NULL_HANDLE) { - return VK_NULL_HANDLE; - } - // A GL texture view shares this resource with the texture it views, so it must not touch - // perMipViews: that vector is indexed by mip level alone and holds views built with the - // STORAGE texture's format and full layer range. Route it through the keyed attachment - // cache instead, where its own window is part of the key. - if (texture.IsTextureView()) { - const TextureViewWindow window = ResolveTextureViewWindow(texture, *resource); - return GetOrCreateAttachmentViewAtMipLevel(texture, mipLevel, window.baseArrayLayer, window.layerCount, - window.viewType); - } - if (mipLevel >= resource->mipLevels) { - return VK_NULL_HANDLE; - } - - if (resource->perMipViews.size() != resource->mipLevels) { - resource->perMipViews.resize(resource->mipLevels, VK_NULL_HANDLE); - } - - VkImageView& perMipView = resource->perMipViews[mipLevel]; - if (perMipView != VK_NULL_HANDLE) { - return perMipView; - } - - perMipView = CreateImageView(resource->image, resource->format, resource->aspect, resource->viewType, - mipLevel, 1, 0, resource->arrayLayers); - if (perMipView == VK_NULL_HANDLE) { - MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u", __func__, texture.GetExternalIndex(), mipLevel); - return VK_NULL_HANDLE; - } - - return perMipView; - } - - VkImageView VkTextureManager::GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, - Uint32 mipLevel, Uint32 baseArrayLayer, - Uint32 layerCount, - VkImageViewType viewType) { - TextureResource* resource = SyncTextureAndGetDescriptor(texture); - if (resource == nullptr || resource->image == VK_NULL_HANDLE) { - return VK_NULL_HANDLE; - } - // mipLevel and baseArrayLayer arrive in STORAGE space - every caller runs them through - // ToStorageMipLevel / ToStorageArrayLayer at the GL attachment boundary. What a GL texture - // view still contributes here is its own internal format, which may reinterpret the - // storage's (GL 4.6 core table 8.21) and is what the attachment must actually be written - // through. - VkFormat viewFormatOverride = VK_FORMAT_UNDEFINED; - if (texture.IsTextureView()) { - viewFormatOverride = ResolveTextureViewWindow(texture, *resource).format; - } - if (mipLevel >= resource->mipLevels) { - return VK_NULL_HANDLE; - } - // A 3D image has arrayLayers == 1 and keeps its GL layers on the z axis, so an attachment - // view over it addresses SLICES through baseArrayLayer/layerCount: one slice for a - // non-layered attachment (a 2D view) and the whole span for a layered one (a 2D_ARRAY view, - // which is what a layered GL_TEXTURE_3D attachment plus a gl_Layer-writing geometry shader - // means). BOTH spellings are legal only on a 2D-array-compatible image - // (VUID-VkImageViewCreateInfo-image-04970 / -06723), which SyncTextureResource asks for and - // may have had refused per format. - // - // The span is validated against the MIP's slice count, never against arrayLayers: a 3D - // image's arrayLayers is 1 by construction, so measuring a layered span against it rejected - // every layered 3D attachment - the null view that used to reach vkCreateFramebuffer. - if (resource->viewType == VK_IMAGE_VIEW_TYPE_3D && - (viewType == VK_IMAGE_VIEW_TYPE_2D || viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY)) { - const Uint32 sliceCount = std::max(resource->depth >> mipLevel, 1u); - if ((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) == 0 || - layerCount == 0 || baseArrayLayer >= sliceCount || baseArrayLayer + layerCount > sliceCount) { - // Not an error line: the render-pass builder turns the null view into one - // MGLOG_E_ONCE and a skipped draw, which is the level this belongs at. - MGLOG_D("%s: cannot name slice span [%u, %u) of 3D textureId=%d as viewType=%d (mip %u has %u " - "slices, 2D-array-compatible=%d)", - __func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(), - static_cast(viewType), mipLevel, sliceCount, - (int)((resource->imageCreateFlags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0)); - return VK_NULL_HANDLE; - } - } else if (layerCount == 0 || baseArrayLayer >= resource->arrayLayers || - baseArrayLayer + layerCount > resource->arrayLayers) { - MGLOG_D("%s: invalid layer span [%u, %u) for textureId=%d arrayLayers=%u", - __func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(), - resource->arrayLayers); - return VK_NULL_HANDLE; - } - - const Bool framebufferSrgbEnabled = - MG_Pipe::gPipeInputs.IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb); - const VkFormat baseAttachmentFormat = - viewFormatOverride != VK_FORMAT_UNDEFINED ? viewFormatOverride : resource->format; - const VkFormat attachmentFormat = - ResolveSrgbAttachmentWriteFormat(baseAttachmentFormat, framebufferSrgbEnabled); - - // The shortcut back to the per-mip vector is only sound for the storage texture itself; - // for a view every field below is part of what distinguishes it from its parent. - if (viewFormatOverride == VK_FORMAT_UNDEFINED && attachmentFormat == resource->format && - baseArrayLayer == 0 && layerCount == resource->arrayLayers && viewType == resource->viewType) { - return GetOrCreateViewAtMipLevel(texture, mipLevel); - } - - const TextureResource::AttachmentViewKey key{ - .mipLevel = mipLevel, - .baseArrayLayer = baseArrayLayer, - .layerCount = layerCount, - .viewType = viewType, - .viewFormat = attachmentFormat, - }; - auto it = resource->attachmentViews.find(key); - if (it == resource->attachmentViews.end()) { - it = resource->attachmentViews.emplace(key, VK_NULL_HANDLE).first; - } - VkImageView& attachmentView = it->second; - if (attachmentView != VK_NULL_HANDLE) { - return attachmentView; - } - - attachmentView = CreateImageView(resource->image, attachmentFormat, resource->aspect, viewType, - mipLevel, 1, baseArrayLayer, layerCount); - if (attachmentView == VK_NULL_HANDLE) { - MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u baseArrayLayer=%u layerCount=%u viewType=%d", - __func__, texture.GetExternalIndex(), mipLevel, baseArrayLayer, layerCount, static_cast(viewType)); - resource->attachmentViews.erase(it); - return VK_NULL_HANDLE; - } - - return attachmentView; - } - - // Builds (and caches) one sampled VkImageView over `resource`'s image for an arbitrary - // window - the shared back end of every GL-texture-view sampled path. Keyed by the whole - // window, which is what keeps a D24S8's depth-aspect view and its stencil-aspect view apart - // in the same cache while both name the same image, the same levels and the same layers. - VkImageView VkTextureManager::GetOrCreateWindowedSampledView(MG_State::GLState::ITextureObject& texture, - TextureResource& resource, - const TextureViewWindow& window) { - const TextureResource::SampledImageViewKey key{ - .baseMipLevel = window.baseMipLevel, - .levelCount = window.levelCount, - .baseArrayLayer = window.baseArrayLayer, - .layerCount = window.layerCount, - .viewType = window.viewType, - .format = window.format, - .aspect = window.sampledAspect, - .componentSwizzle = PackComponentSwizzle(window.components), - }; - const auto existing = resource.alternateSampledViews.find(key); - if (existing != resource.alternateSampledViews.end()) { - return existing->second; - } - - if (window.format != resource.format && - (resource.imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) { - MGLOG_E_ONCE("%s: textureId=%d needs a mutable-format image to be viewed as format=%d " - "(image format=%d)", - __func__, texture.GetExternalIndex(), static_cast(window.format), - static_cast(resource.format)); - return VK_NULL_HANDLE; - } - - const VkImageView view = - CreateImageView(resource.image, window.format, window.sampledAspect, window.viewType, - window.baseMipLevel, window.levelCount, window.baseArrayLayer, window.layerCount, - &window.components); - if (view == VK_NULL_HANDLE) { - MGLOG_E_ONCE("%s: failed to create sampled view for textureId=%d format=%d aspect=0x%x " - "mips=[%u,%u) layers=[%u,%u)", - __func__, texture.GetExternalIndex(), static_cast(window.format), - static_cast(window.sampledAspect), window.baseMipLevel, - window.baseMipLevel + window.levelCount, window.baseArrayLayer, - window.baseArrayLayer + window.layerCount); - return VK_NULL_HANDLE; - } - resource.alternateSampledViews.emplace(key, view); - return view; - } - - VkImageView VkTextureManager::GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture, - VkFormat format) { - TextureResource* resource = SyncTextureAndGetDescriptor(texture); - if (resource == nullptr || resource->image == VK_NULL_HANDLE) { - return VK_NULL_HANDLE; - } - - // A GL texture view never has a sampledView of its own on this resource - that one - // belongs to the storage texture, with the storage texture's format, level range and - // depth/stencil aspect. The window is the view's whole identity, so it always goes to the - // keyed cache, even when the requested format happens to match the image's. - if (texture.IsTextureView()) { - TextureViewWindow window = ResolveTextureViewWindow(texture, *resource); - if (format != VK_FORMAT_UNDEFINED) { - window.format = format; - } - return GetOrCreateWindowedSampledView(texture, *resource, window); - } - - if (resource->sampledView == VK_NULL_HANDLE) { - return VK_NULL_HANDLE; - } - if (format == VK_FORMAT_UNDEFINED || format == resource->format) { - return resource->sampledView; - } - if (!AreSampledImageViewFormatsCompatible(resource->format, format)) { - MGLOG_E_ONCE("%s: incompatible sampled image view format=%d for textureId=%d imageFormat=%d", - __func__, static_cast(format), texture.GetExternalIndex(), - static_cast(resource->format)); - return VK_NULL_HANDLE; - } - if ((resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0) { - MGLOG_E_ONCE("%s: textureId=%d needs mutable image format=%d for sampled view format=%d", - __func__, texture.GetExternalIndex(), static_cast(resource->format), - static_cast(format)); - return VK_NULL_HANDLE; - } - - const TextureResource::SampledImageViewKey key{ - .baseMipLevel = resource->sampledBaseMipLevel, - .levelCount = resource->sampledLevelCount, - .baseArrayLayer = 0, - .layerCount = resource->arrayLayers, - .viewType = resource->viewType, - .format = format, - .aspect = VK_IMAGE_ASPECT_COLOR_BIT, - .componentSwizzle = PackComponentSwizzle( - ResolveSampledViewComponents(texture, ResolveTextureFormatInfo(texture.GetFormat()))), - }; - const auto existing = resource->alternateSampledViews.find(key); - if (existing != resource->alternateSampledViews.end()) { - return existing->second; - } - - VkFormatProperties formatProperties{}; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); - if ((formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0) { - MGLOG_E_ONCE("%s: sampled image view format=%d lacks VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT " - "for textureId=%d (available=0x%x)", - __func__, static_cast(format), texture.GetExternalIndex(), - static_cast(formatProperties.optimalTilingFeatures)); - return VK_NULL_HANDLE; - } - - const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); - const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo); - const VkImageView view = CreateImageView( - resource->image, format, VK_IMAGE_ASPECT_COLOR_BIT, resource->viewType, - resource->sampledBaseMipLevel, resource->sampledLevelCount, 0, resource->arrayLayers, - &sampledComponents, VK_IMAGE_USAGE_SAMPLED_BIT); - if (view == VK_NULL_HANDLE) { - MGLOG_E_ONCE("%s: failed to create sampled image view textureId=%d imageFormat=%d viewFormat=%d", - __func__, texture.GetExternalIndex(), static_cast(resource->format), - static_cast(format)); - return VK_NULL_HANDLE; - } - - resource->alternateSampledViews.emplace(key, view); - MGLOG_D("%s: created sampled image view textureId=%d imageFormat=%d viewFormat=%d mip=[%u,%u)", - __func__, texture.GetExternalIndex(), static_cast(resource->format), - static_cast(format), resource->sampledBaseMipLevel, - resource->sampledBaseMipLevel + resource->sampledLevelCount); - return view; - } - void VkTextureManager::StampTextureRecordingUse(MG_State::GLState::ITextureObject* texture) { if (texture == nullptr) { return; @@ -1141,32 +876,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - VkTextureManager::TextureResource* VkTextureManager::FindTextureResource( - MG_State::GLState::ITextureObject& textureOrView) { - auto it = m_textureResources.find(MakeTextureIdentity(&StorageTextureOf(textureOrView))); - return it != m_textureResources.end() ? &it->second : nullptr; - } - - Bool VkTextureManager::HasLiveImage(MG_State::GLState::ITextureObject& textureOrView) const { - auto it = m_textureResources.find(MakeTextureIdentity(&StorageTextureOf(textureOrView))); - return it != m_textureResources.end() && it->second.image != VK_NULL_HANDLE; - } - - Bool VkTextureManager::HasPendingTexelUpload(MG_State::GLState::ITextureObject& textureOrView) const { - auto& storage = StorageTextureOf(textureOrView); - auto it = m_textureResources.find(MakeTextureIdentity(&storage)); - if (it == m_textureResources.end() || it->second.image == VK_NULL_HANDLE) { - return false; - } - return it->second.syncedShapeVersion == storage.GetShapeVersion() && - it->second.syncedContentVersion != storage.GetContentVersion(); - } - - Bool VkTextureManager::WasTextureWrittenThisRecording(MG_State::GLState::ITextureObject& textureOrView) const { - auto it = m_textureResources.find(MakeTextureIdentity(&StorageTextureOf(textureOrView))); - return it != m_textureResources.end() && it->second.lastRecordingWriteGeneration == m_recordingGeneration; - } - void VkTextureManager::StampTextureRecordingWrite(MG_State::GLState::ITextureObject* texture) { if (texture == nullptr) { return; @@ -3598,90 +3307,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return storageOwner ? *storageOwner : texture; } - // The VkImageViewType a GL texture view's own target asks for. Deliberately derived from the - // GL target rather than inherited from the storage image: a 2D view of a 2D-array texture is - // a VK_IMAGE_VIEW_TYPE_2D over one layer, and a cube view of the same image is a - // VK_IMAGE_VIEW_TYPE_CUBE over six - which is the whole reason table 8.20 lists those pairs. - static VkImageViewType ResolveTextureViewImageViewType(TextureTarget target, - VkImageViewType storageViewType) { - switch (target) { - case TextureTarget::Texture1D: - return VK_IMAGE_VIEW_TYPE_1D; - case TextureTarget::Texture1DArray: - return VK_IMAGE_VIEW_TYPE_1D_ARRAY; - case TextureTarget::Texture2D: - case TextureTarget::TextureRectangle: - case TextureTarget::Texture2DMultisample: - return VK_IMAGE_VIEW_TYPE_2D; - case TextureTarget::Texture2DArray: - case TextureTarget::Texture2DMultisampleArray: - return VK_IMAGE_VIEW_TYPE_2D_ARRAY; - case TextureTarget::TextureCubeMap: - return VK_IMAGE_VIEW_TYPE_CUBE; - case TextureTarget::TextureCubeMapArray: - return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; - default: - return storageViewType; - } - } - - VkTextureManager::TextureViewWindow VkTextureManager::ResolveTextureViewWindow( - MG_State::GLState::ITextureObject& texture, const TextureResource& resource) const { - TextureViewWindow window{}; - window.format = resource.format; - window.viewType = resource.viewType; - window.baseArrayLayer = 0; - window.layerCount = resource.arrayLayers; - window.sampledAspect = - ResolveSampledImageViewAspectMask(resource.aspect, texture.GetDepthStencilTextureMode()); - window.components = ResolveSampledViewComponents(texture, ResolveTextureFormatInfo(texture.GetFormat())); - ResolveViewMipRange(texture, resource.mipLevels, window.baseMipLevel, window.levelCount); - if (!texture.IsTextureView()) { - return window; - } - - window.isTextureView = true; - // GL 4.6 core 8.18: the view's TEXTURE_BASE_LEVEL / TEXTURE_MAX_LEVEL are relative to the - // view, so ResolveViewMipRange above already clamped them against the view's own level - // count (TextureObjectView reports it); shifting by TEXTURE_VIEW_MIN_LEVEL puts them back - // into the storage image's numbering. - window.baseMipLevel += static_cast(texture.GetViewMinLevel()); - window.baseArrayLayer = static_cast(texture.GetViewMinLayer()); - window.layerCount = static_cast(texture.GetViewNumLayers()); - window.viewType = ResolveTextureViewImageViewType(texture.GetTarget(), resource.viewType); - // The view's OWN internalformat, which may reinterpret the storage's (table 8.21). - const VkFormat viewFormat = ResolveTextureFormatInfo(texture.GetFormat()).format; - if (viewFormat != VK_FORMAT_UNDEFINED) { - window.format = viewFormat; - } - // Recomputed against the view's own format: a depth/stencil storage viewed as - // depth/stencil still has to honour the VIEW's DEPTH_STENCIL_TEXTURE_MODE, which is the - // one parameter Better Clouds deliberately sets differently on the two names. - window.sampledAspect = - ResolveSampledImageViewAspectMask(GetAspectMaskForFormat(window.format) != VK_IMAGE_ASPECT_NONE - ? GetAspectMaskForFormat(window.format) - : resource.aspect, - texture.GetDepthStencilTextureMode()); - - // Clamp to what the image actually has; a malformed view must degrade to an empty range - // rather than reach vkCreateImageView with an out-of-bounds subresource. - if (window.baseMipLevel >= resource.mipLevels) { - window.baseMipLevel = resource.mipLevels - 1; - window.levelCount = 1; - } else { - window.levelCount = std::min(window.levelCount, resource.mipLevels - window.baseMipLevel); - } - if (window.levelCount == 0) window.levelCount = 1; - if (window.baseArrayLayer >= resource.arrayLayers) { - window.baseArrayLayer = resource.arrayLayers - 1; - window.layerCount = 1; - } else { - window.layerCount = std::min(window.layerCount, resource.arrayLayers - window.baseArrayLayer); - } - if (window.layerCount == 0) window.layerCount = 1; - return window; - } - // 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index e8c1ac5a..79e9553b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -565,14 +565,6 @@ public: Bool isTextureView = false; }; - // The four component swizzles packed into one value, for the sampled-view cache key. - static Uint32 PackComponentSwizzle(const VkComponentMapping& components) { - return (static_cast(components.r) & 0xFFu) | ((static_cast(components.g) & 0xFFu) << 8) | - ((static_cast(components.b) & 0xFFu) << 16) | - ((static_cast(components.a) & 0xFFu) << 24); - } - TextureViewWindow ResolveTextureViewWindow(MG_State::GLState::ITextureObject& texture, - const TextureResource& resource) const; // 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. @@ -583,11 +575,6 @@ public: // VkImageFormatListCreateInfo the image is created with. void AppendViewRequestedFormats(const MG_State::GLState::ITextureObject& storageTexture, Vector& outFormats) const; - // Builds (and caches, keyed by the whole window) one sampled VkImageView over a storage - // image. Shared back end of every GL-texture-view sampled path. - VkImageView GetOrCreateWindowedSampledView(MG_State::GLState::ITextureObject& texture, - TextureResource& resource, const TextureViewWindow& window); - TextureResource* SyncTextureAndGetDescriptor( MG_State::GLState::ITextureObject& texture); // P5f (fm): THE HANDLE-KEYED ARM of SyncTextureAndGetDescriptor. Under an active transport @@ -664,11 +651,6 @@ public: const auto it = m_wireTextureResources.find(key); return it != m_wireTextureResources.end() && it->second.image != VK_NULL_HANDLE ? &it->second : nullptr; } - VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel); - VkImageView GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, - Uint32 baseArrayLayer, Uint32 layerCount, - VkImageViewType viewType); - VkImageView GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture, VkFormat format); void UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject* texture, Uint32 writtenMipLevel, @@ -693,20 +675,6 @@ public: resource.lastRecordingWriteGeneration = m_recordingGeneration; } void StampTextureRecordingWrite(MG_State::GLState::ITextureObject* texture); - // Upload-ordering queries (VulkanRenderer::OrderPendingUploadAfterRecording). None of them - // syncs, so nothing pending is uploaded by asking. All answer for the STORAGE texture. - // - // Uploads are recorded into a batch submitted AHEAD of the frame's recording - // (FlushPendingUploads), so an upload into an image the open recording already WROTE lands - // before that write, which then overwrites the newer texels. (An image the recording only - // read keeps the old trade: its earlier reads see the newer texels.) - Bool HasLiveImage(MG_State::GLState::ITextureObject& textureOrView) const; - // The next sync would record client texels into the image that is live now: same shape (a - // shape change mints a new image instead) and content moved since the last sync. - Bool HasPendingTexelUpload(MG_State::GLState::ITextureObject& textureOrView) const; - Bool WasTextureWrittenThisRecording(MG_State::GLState::ITextureObject& textureOrView) const; - // The live resource, or null. - TextureResource* FindTextureResource(MG_State::GLState::ITextureObject& textureOrView); // `depthStencilTextureMode` is the texture's GL_DEPTH_STENCIL_TEXTURE_MODE; it only decides // anything for an image that carries both aspects. Defaulted so the call sites that have no // texture in hand keep the depth-aspect answer they have always given. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index fdca749c..44b1711c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -212,13 +212,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - // GL 4.6 core 15.2.3: a colour format with no alpha channel reads as if alpha were one. - // The substitution has to happen in the clear value's own type, so this reports the condition - // and MakeVkClearColorValue applies it to whichever union member the encoding selects. - static Bool ColorFormatLacksAlpha(const MG_State::GLState::ITextureObject* texture) { - return texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3; - } - static Bool IsQuarterTurnPreTransform(VkSurfaceTransformFlagBitsKHR preTransform) { return preTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || preTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; @@ -1127,86 +1120,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - static Bool ActiveRenderPassUsesTexture(const ActiveRenderPassInfo& activeRenderPass, - const MG_State::GLState::ITextureObject& texture) { - for (const auto& trackedAttachment : activeRenderPass.trackedAttachmentLayouts) { - if (trackedAttachment.target != TrackedAttachmentTarget::Texture) { - continue; - } - // Raw identity compare (see textureRaw): the caller's texture is - // live, so a dangling tracked pointer can never equal its address - // unless the allocator reused it - and that false positive merely - // ends the render pass early, never misses a genuine use. - if (trackedAttachment.textureRaw == &texture) { - return true; - } - } - return false; - } - - static void RecordClearBufferError(const char* func, ErrorCode code, const char* message) { - MG_Pipe::gPipeInputs.RecordError(code, MakeUnique("DirectVulkan", func, message)); - } - static void RecordTextureCopyError(const char* func, ErrorCode code, const char* message) { MG_Pipe::gPipeInputs.RecordError(code, MakeUnique("DirectVulkan", func, message)); } - static Bool HasDistinctCompleteDepthStencilTextureAttachments( - const MG_State::GLState::FramebufferObject& framebufferObject) { - if (framebufferObject.GetExternalIndex() == 0) { - return false; - } - - const auto& depthAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Depth); - const auto& stencilAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Stencil); - if (!depthAttachment.IsComplete() || !stencilAttachment.IsComplete() || - !depthAttachment.IsTexture() || !stencilAttachment.IsTexture()) { - return false; - } - - return depthAttachment.GetTexture().get() != stencilAttachment.GetTexture().get() || - depthAttachment.GetTextureUploadTarget() != stencilAttachment.GetTextureUploadTarget() || - ToStorageMipLevel(depthAttachment.GetTexture().get(), depthAttachment.GetTextureLevel()) != - ToStorageMipLevel(stencilAttachment.GetTexture().get(), stencilAttachment.GetTextureLevel()); - } - - static Bool HasUnsupportedCompleteRenderbufferAttachment( - const MG_State::GLState::FramebufferObject& framebufferObject) { - if (framebufferObject.GetExternalIndex() == 0) { - return false; - } - - const auto& depthAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Depth); - const auto& stencilAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Stencil); - if (!depthAttachment.IsComplete() || !stencilAttachment.IsComplete()) { - return false; - } - if (depthAttachment.IsRenderbuffer() && stencilAttachment.IsRenderbuffer()) { - return depthAttachment.GetRenderbuffer().get() != stencilAttachment.GetRenderbuffer().get(); - } - if ((depthAttachment.IsRenderbuffer() || stencilAttachment.IsRenderbuffer()) && - (depthAttachment.IsTexture() || stencilAttachment.IsTexture())) { - return true; - } - return false; - } - - static Bool IsUnsupportedFramebufferForDirectVulkan( - const MG_State::GLState::FramebufferObject& framebufferObject) { - // TODO: Revisit this gate when DirectVulkan has full color renderbuffer render/blit/readback support. - return HasDistinctCompleteDepthStencilTextureAttachments(framebufferObject) || - HasUnsupportedCompleteRenderbufferAttachment(framebufferObject); - } - - static void RecordUnsupportedFramebufferError(const char* func) { - MG_Pipe::gPipeInputs.RecordError( - ErrorCode::InvalidFramebufferOperation, - MakeUnique( - "DirectVulkan", func, - "DirectVulkan does not support this non-default framebuffer configuration.")); - } - namespace { static constexpr Uint32 kDescriptorSetsPerFrame = 64; @@ -1216,28 +1133,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { target <= TextureUploadTarget::CubeMapNegativeZ; } - static Uint32 ResolveAttachmentBaseArrayLayer(const MG_State::GLState::FramebufferAttachmentObject& attachment) { - const TextureUploadTarget uploadTarget = attachment.GetTextureUploadTarget(); - if (IsCubeMapFaceUploadTarget(uploadTarget)) { - // The face index IS the layer index, so it takes the same view shift as one that - // arrived through GetTextureLayer (see ToStorageArrayLayer). - const Int face = static_cast(uploadTarget) - - static_cast(TextureUploadTarget::CubeMapPositiveX); - return ToStorageArrayLayer(attachment.GetTexture().get(), face); - } - // Every other layered attachment names its layer directly. Returning 0 regardless made - // every blit, copy and ReadPixels against such an attachment read layer zero. - return ToStorageArrayLayer(attachment.GetTexture().get(), attachment.GetTextureLayer()); - } - - // A 3D image has arrayLayers == 1: its "layer" is a z slice, which has to travel as an - // image offset rather than a base array layer (VkBufferImageCopy requires baseArrayLayer 0 - // for VK_IMAGE_TYPE_3D). - static Bool AttachmentIsDepthSlice(const MG_State::GLState::FramebufferAttachmentObject& attachment) { - return attachment.IsTexture() && attachment.GetTexture() && - attachment.GetTexture()->GetTarget() == TextureTarget::Texture3D; - } - enum class BlitSurfaceTransform : Uint32 { Identity = 0, Rotate90 = 1, @@ -1383,260 +1278,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - static VkImageAspectFlags GetSwapchainDepthStencilAspectMask(const SwapchainObject& swapchainObject) { - VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - switch (swapchainObject.GetDepthStencilFormat()) { - case VK_FORMAT_D24_UNORM_S8_UINT: - case VK_FORMAT_D32_SFLOAT_S8_UINT: - aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - break; - default: - break; - } - return aspectMask; - } - - static FramebufferAttachmentType ResolveFramebufferCopyAttachmentType( - const MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, - VkImageAspectFlags aspectMask) { - if ((aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { - return isReadFramebuffer ? fbo.GetReadBuffer() : fbo.GetDrawBuffers()[0]; - } - if ((aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) { - return FramebufferAttachmentType::Depth; - } - if ((aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) { - return FramebufferAttachmentType::Stencil; - } - return FramebufferAttachmentType::None; - } - - static Bool ResolveColorBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, - Uint32 swapchainImageIndex, SwapchainObject& swapchainObject, - VkTextureManager& textureManager, - VkRenderPassManager& renderPassManager, BlitImageBinding& outBinding) { - const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); - const FramebufferAttachmentType attachmentType = - isReadFramebuffer ? fbo.GetReadBuffer() : fbo.GetDrawBuffers()[0]; - outBinding.label = isReadFramebuffer ? "read" : "draw"; - - if (isDefaultFbo) { - const Bool defaultColorAttachment = - attachmentType == FramebufferAttachmentType::Color0 || - (attachmentType >= FramebufferAttachmentType::FrontLeft && - attachmentType <= FramebufferAttachmentType::BackRight); - if (!defaultColorAttachment) { - MGLOG_E_ONCE("BlitFramebuffer skipped: default framebuffer color attachment %d is not supported", - static_cast(attachmentType)); - return false; - } - outBinding.image = swapchainObject.GetImage(swapchainImageIndex); - outBinding.trackedLayout = nullptr; - outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - outBinding.format = swapchainObject.GetSurfaceFormat().format; - const auto extent = swapchainObject.GetExtent(); - outBinding.extent = {static_cast(extent.width), static_cast(extent.height)}; - outBinding.mipLevel = 0; - outBinding.mipLevelCount = 1; - outBinding.baseArrayLayer = 0; - outBinding.layerCount = 1; - return true; - } - - if (attachmentType < FramebufferAttachmentType::Color0 || attachmentType > FramebufferAttachmentType::Color31) { - MGLOG_E_ONCE("BlitFramebuffer only supports color attachments right now (attachment=%d)", - static_cast(attachmentType)); - return false; - } - - const auto& attachment = fbo.GetAttachment(attachmentType); - if (!attachment.IsComplete()) { - MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer color attachment is incomplete", - isReadFramebuffer ? "read" : "draw"); - return false; - } - if (attachment.IsRenderbuffer()) { - const auto& renderbuffer = attachment.GetRenderbuffer(); - auto* rbResource = renderPassManager.GetOrCreateRenderbufferResource(renderbuffer); - if (rbResource == nullptr || (rbResource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer color renderbuffer %u is unsupported", - outBinding.label, renderbuffer->GetExternalIndex()); - return false; - } - outBinding.image = rbResource->image; - outBinding.trackedLayout = &rbResource->layout; - outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - outBinding.format = rbResource->format; - outBinding.sampleCount = rbResource->sampleCount; - outBinding.extent = {static_cast(rbResource->extent.width), - static_cast(rbResource->extent.height)}; - outBinding.mipLevel = 0; - outBinding.mipLevelCount = 1; - outBinding.baseArrayLayer = 0; - outBinding.layerCount = 1; - return true; - } - if (!attachment.IsTexture()) { - MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported framebuffer attachment type"); - return false; - } - - auto* texture = attachment.GetTexture().get(); - MOBILEGL_ASSERT(texture != nullptr, "ResolveColorBlitBinding: texture attachment is null"); - - auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); - if (resource == nullptr) { - MGLOG_E_ONCE("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d", - outBinding.label, texture->GetExternalIndex()); - return false; - } - if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is not a color image", - outBinding.label, texture->GetExternalIndex()); - return false; - } - if (!isReadFramebuffer) { - textureManager.StampResourceRecordingWrite(*resource); - } - - outBinding.image = resource->image; - outBinding.trackedLayout = &resource->layout; - outBinding.aspectMask = resource->aspect; - outBinding.format = resource->format; - outBinding.sampleCount = resource->sampleCount; - const auto attachmentExtent = attachment.GetSize(); - outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()}; - outBinding.mipLevel = ToStorageMipLevel(attachment.GetTexture().get(), attachment.GetTextureLevel()); - outBinding.mipLevelCount = resource->mipLevels; - if (AttachmentIsDepthSlice(attachment)) { - outBinding.depthOffset = ToStorageArrayLayer(attachment.GetTexture().get(), attachment.GetTextureLayer()); - outBinding.baseArrayLayer = 0; - } else { - outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment); - } - outBinding.layerCount = 1; - return true; - } - - static Bool ResolveFramebufferBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, - Uint32 swapchainImageIndex, SwapchainObject& swapchainObject, - VkTextureManager& textureManager, - VkRenderPassManager& renderPassManager, - VkImageAspectFlags requiredAspectMask, - BlitImageBinding& outBinding) { - const Bool isDefaultFbo = fbo.IsDefaultFramebuffer(); - const auto attachmentType = ResolveFramebufferCopyAttachmentType(fbo, isReadFramebuffer, requiredAspectMask); - if (attachmentType == FramebufferAttachmentType::None) { - MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported aspect mask=0x%x", - static_cast(requiredAspectMask)); - return false; - } - - outBinding.label = isReadFramebuffer ? "read" : "draw"; - if (isDefaultFbo) { - const auto extent = swapchainObject.GetExtent(); - outBinding.extent = {static_cast(extent.width), static_cast(extent.height)}; - outBinding.mipLevel = 0; - outBinding.mipLevelCount = 1; - outBinding.baseArrayLayer = 0; - outBinding.layerCount = 1; - outBinding.trackedLayout = nullptr; - if ((requiredAspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { - outBinding.image = swapchainObject.GetImage(swapchainImageIndex); - outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - return true; - } - - const VkImageAspectFlags swapchainAspectMask = GetSwapchainDepthStencilAspectMask(swapchainObject); - if ((swapchainAspectMask & requiredAspectMask) != requiredAspectMask) { - MGLOG_E_ONCE("BlitFramebuffer skipped: swapchain depth image missing required aspect mask=0x%x", - static_cast(requiredAspectMask)); - return false; - } - - outBinding.image = swapchainObject.GetDepthStencilImage(swapchainImageIndex); - outBinding.format = swapchainObject.GetDepthStencilFormat(); - outBinding.aspectMask = requiredAspectMask; - return true; - } - - const auto& attachment = fbo.GetAttachment(attachmentType); - if (!attachment.IsComplete()) { - MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer attachment is incomplete (fbo=%u attachmentType=%d " - "isTexture=%d isRenderbuffer=%d texId=%d)", - outBinding.label, fbo.GetExternalIndex(), static_cast(attachmentType), - attachment.IsTexture() ? 1 : 0, attachment.IsRenderbuffer() ? 1 : 0, - attachment.IsTexture() && attachment.GetTexture() ? static_cast(attachment.GetTexture()->GetExternalIndex()) : -1); - return false; - } - if (attachment.IsRenderbuffer()) { - const auto& renderbuffer = attachment.GetRenderbuffer(); - auto* rbResource = renderPassManager.GetOrCreateRenderbufferResource(renderbuffer); - if (rbResource == nullptr) { - MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer renderbuffer %u is unsupported", - outBinding.label, renderbuffer->GetExternalIndex()); - return false; - } - if ((rbResource->aspect & requiredAspectMask) != requiredAspectMask) { - MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer renderbuffer %u is missing aspect mask=0x%x", - outBinding.label, renderbuffer->GetExternalIndex(), - static_cast(requiredAspectMask)); - return false; - } - outBinding.image = rbResource->image; - outBinding.trackedLayout = &rbResource->layout; - outBinding.aspectMask = requiredAspectMask; - outBinding.format = rbResource->format; - outBinding.sampleCount = rbResource->sampleCount; - outBinding.extent = {static_cast(rbResource->extent.width), - static_cast(rbResource->extent.height)}; - outBinding.mipLevel = 0; - outBinding.mipLevelCount = 1; - outBinding.baseArrayLayer = 0; - outBinding.layerCount = 1; - return true; - } - if (!attachment.IsTexture()) { - MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported framebuffer attachment type"); - return false; - } - - auto* texture = attachment.GetTexture().get(); - MOBILEGL_ASSERT(texture != nullptr, "ResolveFramebufferBlitBinding: texture attachment is null"); - auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture); - if (resource == nullptr) { - MGLOG_E_ONCE("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d", - outBinding.label, texture->GetExternalIndex()); - return false; - } - if ((resource->aspect & requiredAspectMask) != requiredAspectMask) { - MGLOG_E_ONCE("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is missing aspect mask=0x%x", - outBinding.label, texture->GetExternalIndex(), static_cast(requiredAspectMask)); - return false; - } - if (!isReadFramebuffer) { - textureManager.StampResourceRecordingWrite(*resource); - } - - outBinding.image = resource->image; - outBinding.trackedLayout = &resource->layout; - outBinding.aspectMask = requiredAspectMask; - outBinding.format = resource->format; - outBinding.sampleCount = resource->sampleCount; - const auto attachmentExtent = attachment.GetSize(); - outBinding.extent = {attachmentExtent.x(), attachmentExtent.y()}; - outBinding.mipLevel = ToStorageMipLevel(attachment.GetTexture().get(), attachment.GetTextureLevel()); - outBinding.mipLevelCount = resource->mipLevels; - if (AttachmentIsDepthSlice(attachment)) { - outBinding.depthOffset = ToStorageArrayLayer(attachment.GetTexture().get(), attachment.GetTextureLayer()); - outBinding.baseArrayLayer = 0; - } else { - outBinding.baseArrayLayer = ResolveAttachmentBaseArrayLayer(attachment); - } - outBinding.layerCount = 1; - return true; - } - static BlitSurfaceTransform ToBlitSurfaceTransform(VkSurfaceTransformFlagBitsKHR preTransform) { switch (preTransform) { case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: @@ -1650,56 +1291,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - static Bool RequiresShaderBlitToDefaultFramebuffer(VkSurfaceTransformFlagBitsKHR preTransform) { - switch (preTransform) { - case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: - case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: - return true; - default: - return false; - } - } - - static void ApplyNativeBlitDefaultFramebufferTransform(VkSurfaceTransformFlagBitsKHR preTransform, - const BlitImageBinding& dstBinding, - VkImageBlit& blitRegion) { - switch (preTransform) { - case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR: - blitRegion.dstOffsets[0].y = dstBinding.extent.y() - blitRegion.dstOffsets[0].y; - blitRegion.dstOffsets[1].y = dstBinding.extent.y() - blitRegion.dstOffsets[1].y; - break; - case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: - blitRegion.dstOffsets[0].x = dstBinding.extent.x() - blitRegion.dstOffsets[0].x; - blitRegion.dstOffsets[1].x = dstBinding.extent.x() - blitRegion.dstOffsets[1].x; - break; - default: - break; - } - } - - // The same conversion on the READ side, which never had one: a blit whose source is the - // default framebuffer used raw GL offsets against a display-oriented image, so it sampled - // the mirrored band and wrote it upside down. Mapping BOTH endpoints inverts the offset - // pair, and an inverted pair is exactly how VkImageBlit spells "flip this axis" - so the - // band and the row order are corrected in one step. A full-extent blit is unchanged in - // band and gains the row flip it always needed. - static void ApplyNativeBlitDefaultFramebufferSourceTransform(VkSurfaceTransformFlagBitsKHR preTransform, - const BlitImageBinding& srcBinding, - VkImageBlit& blitRegion) { - switch (preTransform) { - case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR: - blitRegion.srcOffsets[0].y = srcBinding.extent.y() - blitRegion.srcOffsets[0].y; - blitRegion.srcOffsets[1].y = srcBinding.extent.y() - blitRegion.srcOffsets[1].y; - break; - case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: - blitRegion.srcOffsets[0].x = srcBinding.extent.x() - blitRegion.srcOffsets[0].x; - blitRegion.srcOffsets[1].x = srcBinding.extent.x() - blitRegion.srcOffsets[1].x; - break; - default: - break; - } - } - static Bool DecodeReadbackPixel(const Uint8* source, VkFormat sourceFormat, Float* rgba) { switch (sourceFormat) { case VK_FORMAT_R8G8B8A8_UNORM: @@ -4228,71 +3819,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { 1, &memoryBarrier, 0, nullptr, 0, nullptr); } - VulkanRenderer::ScissoredClearPrep VulkanRenderer::PrepareScissoredClear( - const MG_State::GLState::FramebufferObject& framebuffer, VkClearRect& outClearRect) { - auto& frame = m_frameContext.GetCurrent(); - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - - auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - auto* renderPassEntry = m_renderPassManager->GetOrCreateRenderPass( - framebuffer, DefaultFramebufferWriteIndex(framebuffer.IsDefaultFramebuffer())); - // A declined render pass is the same answer as an empty one for a clear: there is nothing - // attached that can be cleared inside a pass. The builder has already logged the reason. - if (renderPassEntry == nullptr || renderPassEntry->attachmentCount == 0 || - renderPassEntry->extent.x() <= 0 || renderPassEntry->extent.y() <= 0) { - return ScissoredClearPrep::NoOp; - } - - VkClearRect clearRect{}; - clearRect.rect = framebuffer.IsDefaultFramebuffer() - ? MakeDefaultFramebufferScissorRect(MG_Pipe::gPipeInputs.GetScissorBox(), - renderPassEntry->extent, - m_swapchainObject.GetPreTransform()) - : MakeClampedScissorRect(MG_Pipe::gPipeInputs.GetScissorBox(), renderPassEntry->extent); - clearRect.baseArrayLayer = 0; - // GL 3.3 §4.4.7: clearing a layered framebuffer clears every layer. - clearRect.layerCount = renderPassEntry->layers; - if (clearRect.rect.extent.width == 0 || clearRect.rect.extent.height == 0) { - return ScissoredClearPrep::NoOp; - } - // A scissor that covers the whole target is a whole-surface clear; the deferred loadOp - // path is equivalent and cheaper (no render pass churn, loadOp=CLEAR on tilers). - if (clearRect.rect.offset.x == 0 && clearRect.rect.offset.y == 0 && - clearRect.rect.extent.width == static_cast(renderPassEntry->extent.x()) && - clearRect.rect.extent.height == static_cast(renderPassEntry->extent.y())) { - return ScissoredClearPrep::NotNeeded; - } - - if (activeRenderPass && !activeRenderPass->CompatibleWith(*renderPassEntry)) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - activeRenderPass = nullptr; - // Re-resolve: ending the pass updates tracked attachment layouts, which feed the - // entry's load ops and initial layouts. - renderPassEntry = m_renderPassManager->GetOrCreateRenderPass( - framebuffer, DefaultFramebufferWriteIndex(framebuffer.IsDefaultFramebuffer())); - if (renderPassEntry == nullptr) { - return ScissoredClearPrep::NoOp; - } - } - // A still-active pass is necessarily compatible here: the block above ended any - // incompatible one and nothing since can change the active pass. - if (activeRenderPass) { - // Materialize any older whole-attachment clear before applying this - // ordered, scissored clear. - ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, *renderPassEntry); - } else { - const Bool began = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, *renderPassEntry); - MOBILEGL_ASSERT(began, "%s: BeginRenderPass failed", __func__); - if (!began) { - return ScissoredClearPrep::NoOp; - } - } - outClearRect = clearRect; - return ScissoredClearPrep::Ready; - } - // A tightly packed depth/stencil readback, one aspect or both, in the client's own width. // The two arms that read depth or stencil off a Vulkan image - the monolith // ReadDepthStencilImageToClient and the wire ReadTextureImageWire - copy their aspect into a @@ -4394,199 +3920,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } - void VulkanRenderer::QueueClearBufferPayloadForFramebuffer( - const MG_State::GLState::FramebufferObject& framebuffer, GLenum buffer, GLint drawbuffer, - const ClearAttachmentPayload& clearPayload) { - m_clearManager->CollectGarbage(); - // GL 3.3 §3.1: when RASTERIZER_DISCARD is enabled, Clear and ClearBuffer* are ignored. - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) { - return; - } - if (IsUnsupportedFramebufferForDirectVulkan(framebuffer)) { - RecordUnsupportedFramebufferError(__func__); - return; - } - - // Validate (buffer, drawbuffer) up front so GL errors fire regardless of which clear - // path is taken below. - switch (buffer) { - case GL_COLOR: - if (drawbuffer < 0 || - drawbuffer >= static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS)) { - RecordClearBufferError(__func__, ErrorCode::InvalidValue, "color drawbuffer index is out of range"); - return; - } - break; - case GL_DEPTH: - if (drawbuffer != 0) { - RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth clear requires drawbuffer 0"); - return; - } - break; - case GL_STENCIL: - if (drawbuffer != 0) { - RecordClearBufferError(__func__, ErrorCode::InvalidValue, "stencil clear requires drawbuffer 0"); - return; - } - break; - case GL_DEPTH_STENCIL: - if (drawbuffer != 0) { - RecordClearBufferError(__func__, ErrorCode::InvalidValue, "depth/stencil clear requires drawbuffer 0"); - return; - } - break; - default: - RecordClearBufferError(__func__, ErrorCode::InvalidEnum, "unsupported clear buffer target"); - return; - } - - // GL 3.3 §4.2.3: ClearBuffer* is clipped by GL_SCISSOR_TEST exactly like Clear. - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest)) { - VkClearRect clearRect{}; - switch (PrepareScissoredClear(framebuffer, clearRect)) { - case ScissoredClearPrep::NoOp: - return; - case ScissoredClearPrep::NotNeeded: - break; // full-coverage scissor: the deferred whole-surface path below is equivalent - case ScissoredClearPrep::Ready: - RecordScissoredClearBuffer(framebuffer, buffer, drawbuffer, clearPayload, clearRect); - return; - } - } - - auto queueAttachmentClear = [&](FramebufferAttachmentType attachmentType, - const ClearAttachmentPayload& payload) { - if (attachmentType == FramebufferAttachmentType::None || payload.mask == 0) { - return; - } - const auto& attachment = framebuffer.GetAttachment(attachmentType); - if (attachment.IsRenderbuffer()) { - m_renderPassManager->QueueRenderbufferClear(payload, attachment); - return; - } - if (!attachment.IsTexture()) { - return; - } - SettleTexelsBeforeQueuedClear(framebuffer, attachment); - m_clearManager->QueueClear(payload, attachment); - }; - - // GL 3.3 §4.2.3: ClearBuffer* honors the write masks like Clear. Deferred - // clears cannot express partial masks; warn and skip those. - const auto depthClearAllowed = [&]() -> Bool { return MG_Pipe::gPipeInputs.GetDepthMask(); }; - const auto stencilClearAllowed = [&]() -> Bool { - const Uint32 stencilWriteMask = MG_Pipe::gPipeInputs.GetStencilState(StencilFace::Front).WriteMask; - if ((stencilWriteMask & 0xFFu) == 0xFFu) { - return true; - } - if (stencilWriteMask != 0) { - MGLOG_W_ONCE("DirectVulkan: deferred glClearBuffer with a partial stencil write mask is not supported"); - } - return false; - }; - - switch (buffer) { - case GL_COLOR: { - const BoolVec4 colorMask = MG_Pipe::gPipeInputs.GetColorMaskIndexed(static_cast(drawbuffer)); - if (!colorMask.r() && !colorMask.g() && !colorMask.b() && !colorMask.a()) { - return; - } - if (!(colorMask.r() && colorMask.g() && colorMask.b() && colorMask.a())) { - MGLOG_W_ONCE("DirectVulkan: deferred glClearBuffer with a partial color mask is not supported"); - return; - } - queueAttachmentClear(framebuffer.GetDrawBuffers()[drawbuffer], clearPayload); - return; - } - case GL_DEPTH: - if (depthClearAllowed()) { - queueAttachmentClear(FramebufferAttachmentType::Depth, clearPayload); - } - return; - case GL_STENCIL: - if (stencilClearAllowed()) { - queueAttachmentClear(FramebufferAttachmentType::Stencil, clearPayload); - } - return; - case GL_DEPTH_STENCIL: { - ClearAttachmentPayload allowedPayload = clearPayload; - if (!depthClearAllowed()) { - allowedPayload.mask &= ~static_cast(GL_DEPTH_BUFFER_BIT); - } - if (!stencilClearAllowed()) { - allowedPayload.mask &= ~static_cast(GL_STENCIL_BUFFER_BIT); - } - if ((allowedPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { - queueAttachmentClear(FramebufferAttachmentType::Depth, allowedPayload); - } - if ((allowedPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { - queueAttachmentClear(FramebufferAttachmentType::Stencil, allowedPayload); - } - return; - } - default: - return; - } - } - - void VulkanRenderer::RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer, - GLenum buffer, GLint drawbuffer, - const ClearAttachmentPayload& clearPayload, - const VkClearRect& clearRect) { - VkClearAttachment clearAttachment{}; - - if (buffer == GL_COLOR) { - const auto attachmentType = framebuffer.GetDrawBuffers()[drawbuffer]; - if (attachmentType == FramebufferAttachmentType::None) { - return; - } - const auto& attachment = framebuffer.GetAttachment(attachmentType); - if (!attachment.IsComplete()) { - return; - } - const BoolVec4 colorMask = MG_Pipe::gPipeInputs.GetColorMaskIndexed(static_cast(drawbuffer)); - if (!colorMask.r() && !colorMask.g() && !colorMask.b() && !colorMask.a()) { - return; - } - if (!colorMask.r() || !colorMask.g() || !colorMask.b() || !colorMask.a()) { - MGLOG_W_ONCE("DirectVulkan: scissored glClearBuffer with a partial color mask is not supported"); - return; - } - MG_State::GLState::ITextureObject* colorTexture = nullptr; - if (attachment.IsTexture()) { - colorTexture = attachment.GetTexture().get(); - } - clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - clearAttachment.colorAttachment = static_cast(drawbuffer); - clearAttachment.clearValue.color = - MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(colorTexture)); - } else { - VkImageAspectFlags aspects = 0; - if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0 && MG_Pipe::gPipeInputs.GetDepthMask() && - framebuffer.GetAttachment(FramebufferAttachmentType::Depth).IsComplete()) { - aspects |= VK_IMAGE_ASPECT_DEPTH_BIT; - } - if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0 && - framebuffer.GetAttachment(FramebufferAttachmentType::Stencil).IsComplete()) { - // GL 3.3 §4.2.3: the clear is masked by the front stencil write mask (see Clear). - const Uint32 stencilWriteMask = - MG_Pipe::gPipeInputs.GetStencilState(StencilFace::Front).WriteMask; - if ((stencilWriteMask & 0xFFu) == 0xFFu) { - aspects |= VK_IMAGE_ASPECT_STENCIL_BIT; - } else if (stencilWriteMask != 0) { - MGLOG_W_ONCE("DirectVulkan: scissored glClearBuffer with a partial stencil write mask is not supported"); - } - } - if (aspects == 0) { - return; - } - clearAttachment.aspectMask = aspects; - clearAttachment.clearValue.depthStencil = {clearPayload.depth, clearPayload.stencil}; - } - - vkCmdClearAttachments(m_frameContext.GetCurrent().commandBuffer, 1, &clearAttachment, 1, &clearRect); - } - void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer, const ClearAttachmentPayload& clearPayload) { const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); @@ -4624,82 +3957,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { QueueClearBufferPayload(buffer, drawbuffer, payload); } - void VulkanRenderer::ClearNamedFramebufferfv( - const SharedPtr& framebuffer, GLenum buffer, GLint drawbuffer, - const GLfloat* value) { - if (!framebuffer || value == nullptr) { - return; - } - ClearAttachmentPayload payload{}; - switch (buffer) { - case GL_COLOR: - payload.mask = GL_COLOR_BUFFER_BIT; - payload.color = FloatVec4(value[0], value[1], value[2], value[3]); - break; - case GL_DEPTH: - payload.mask = GL_DEPTH_BUFFER_BIT; - payload.depth = std::clamp(value[0], 0.0f, 1.0f); - break; - default: - break; - } - QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload); - } - - // The integer clears carry the same payload as their target-based siblings; only the - // destination differs, so they queue against the named framebuffer rather than the bound one. - void VulkanRenderer::ClearNamedFramebufferiv( - const SharedPtr& framebuffer, GLenum buffer, GLint drawbuffer, - const GLint* value) { - if (!framebuffer || value == nullptr) { - return; - } - ClearAttachmentPayload payload{}; - switch (buffer) { - case GL_COLOR: - payload.mask = GL_COLOR_BUFFER_BIT; - payload.colorEncoding = ClearColorEncoding::Int; - payload.colorInt = IntVec4(value[0], value[1], value[2], value[3]); - break; - case GL_STENCIL: - payload.mask = GL_STENCIL_BUFFER_BIT; - payload.stencil = static_cast(std::max(value[0], 0)); - break; - default: - break; - } - QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload); - } - - void VulkanRenderer::ClearNamedFramebufferuiv( - const SharedPtr& framebuffer, GLenum buffer, GLint drawbuffer, - const GLuint* value) { - if (!framebuffer || value == nullptr) { - return; - } - ClearAttachmentPayload payload{}; - if (buffer == GL_COLOR) { - payload.mask = GL_COLOR_BUFFER_BIT; - payload.colorEncoding = ClearColorEncoding::Uint; - payload.colorUint = UintVec4(value[0], value[1], value[2], value[3]); - } - QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload); - } - - void VulkanRenderer::ClearNamedFramebufferfi( - const SharedPtr& framebuffer, GLenum buffer, GLint drawbuffer, - GLfloat depth, GLint stencil) { - if (!framebuffer) { - return; - } - ClearAttachmentPayload payload{}; - payload.mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; - // Vulkan clear values require depth in [0,1] (VUID-VkClearDepthStencilValue-depth-00022). - payload.depth = std::clamp(depth, 0.0f, 1.0f); - payload.stencil = static_cast(stencil); - QueueClearBufferPayloadForFramebuffer(*framebuffer, buffer, drawbuffer, payload); - } - void VulkanRenderer::ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) { if (value == nullptr) { return; @@ -4742,482 +3999,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { QueueClearBufferPayload(buffer, drawbuffer, payload); } - Bool VulkanRenderer::ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer, - MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, - Uint32 depthSlice, const VkClearValue& clearValue, - VkImageLayout finalLayout) { - auto* resource = m_textureManager->SyncTextureAndGetDescriptor(texture); - if (resource == nullptr || resource->image == VK_NULL_HANDLE) return false; - if (m_frameContext.GetCurrentFrameIndex() >= m_deferredDepthMipmapCleanup.size()) return false; - - // A 2D view over one z slice. Returns VK_NULL_HANDLE when the image is not - // 2D-array-compatible, which is the whole reason this can fail. - const VkImageView sliceView = m_textureManager->GetOrCreateAttachmentViewAtMipLevel( - texture, mipLevel, depthSlice, 1, VK_IMAGE_VIEW_TYPE_2D); - if (sliceView == VK_NULL_HANDLE) return false; - - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = resource->format; - // The image's own count, not a hardcoded one: a render-pass attachment must match the - // image it is given (VUID-VkFramebufferCreateInfo-pAttachments-00880), and this helper is - // now also the multisample path - a multisample image carries no TRANSFER_DST usage, so a - // load-op clear is the only legal way to clear it at all. - colorAttachment.samples = resource->sampleCount; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - // Hand the slice back in the layout the caller already tracks for the whole image, so its - // closing barrier stays truthful and resource->layout is never touched from in here. - colorAttachment.finalLayout = finalLayout; - - VkAttachmentReference colorRef{}; - colorRef.attachment = 0; - colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorRef; - - VkRenderPassCreateInfo renderPassInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - - VkRenderPass renderPass = VK_NULL_HANDLE; - if (vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) return false; - - const Uint32 levelWidth = std::max(resource->extent.width >> mipLevel, 1u); - const Uint32 levelHeight = std::max(resource->extent.height >> mipLevel, 1u); - - VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; - framebufferInfo.renderPass = renderPass; - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = &sliceView; - framebufferInfo.width = levelWidth; - framebufferInfo.height = levelHeight; - framebufferInfo.layers = 1; - - VkFramebuffer framebuffer = VK_NULL_HANDLE; - if (vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer) != VK_SUCCESS) { - vkDestroyRenderPass(m_device, renderPass, nullptr); - return false; - } - - VkRenderPassBeginInfo beginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - beginInfo.renderPass = renderPass; - beginInfo.framebuffer = framebuffer; - beginInfo.renderArea.extent = {levelWidth, levelHeight}; - beginInfo.clearValueCount = 1; - beginInfo.pClearValues = &clearValue; - // The load op is the whole operation: begin and end with nothing in between. - EndActiveRenderPassOn(commandBuffer); - vkCmdBeginRenderPass(commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdEndRenderPass(commandBuffer); - - // The image view is owned and memoised by the texture resource; only these two are throwaway. - auto& deferredCleanup = m_deferredDepthMipmapCleanup[m_frameContext.GetCurrentFrameIndex()]; - deferredCleanup.renderPasses.push_back(renderPass); - deferredCleanup.framebuffers.push_back(framebuffer); - return true; - } - - Bool VulkanRenderer::MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, - MG_State::GLState::ITextureObject& texture, - Bool uploadPendingTexels) { - Vector pendingClears; - if (!m_clearManager->GetPendingClears(&texture, pendingClears)) { - return true; - } - // A pass may stay open on the FRAME command buffer while this clear is - // recorded into the pre-pass stream (a different command buffer that - // executes strictly before the frame's commands). - MOBILEGL_ASSERT(VkRenderPassManager::GetActiveRenderPass() == nullptr || - commandBuffer != m_frameContext.GetCurrent().commandBuffer, - "MaterializePendingClearForTexture requires no active render pass on the target buffer"); - - auto* resource = uploadPendingTexels ? m_textureManager->SyncTextureAndGetDescriptor(texture) - : m_textureManager->FindTextureResource(texture); - if (resource == nullptr || resource->image == VK_NULL_HANDLE) { - // Declined sync (an incomplete texture, say). Nothing to clear into, and every line - // below dereferences this - the assert that used to stand here is compiled out of - // every build past DEBUG. - MGLOG_E_ONCE("MaterializePendingClearForTexture: no texture resource for textureId=%d; the queued clears " - "stay queued", - texture.GetExternalIndex()); - return false; - } - - // A multisample image is not a transfer target: SyncTextureResource deliberately withholds - // TRANSFER_DST/TRANSFER_SRC from every one of them, so the vkCmdClearColorImage below - - // and the TRANSFER_DST transition ahead of it - are invalid usage - // (VUID-vkCmdClearColorImage-image-00002) on exactly the shape a - // glClearBufferfv-then-sample sequence produces. Clear it the one way that is legal at - // any sample count instead: a throwaway render pass whose whole content is its load-op - // clear, which is also what the 3D-slice case below already does. - if (resource->sampleCount != VK_SAMPLE_COUNT_1_BIT) { - return MaterializeMultisamplePendingClear(commandBuffer, texture, *resource, pendingClears); - } - - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(resource->layout, srcStageMask, srcAccessMask); - - VkImageLayout clearLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - Bool ok = VkTextureManager::TransitionImageLayout( - commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, - resource->aspect, 0, resource->mipLevels); - MOBILEGL_ASSERT(ok, - "MaterializePendingClearForTexture: failed to transition textureId=%d to TRANSFER_DST", - texture.GetExternalIndex()); - - VkImageLayout sampledLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - for (const auto& pendingClear : pendingClears) { - MOBILEGL_ASSERT(pendingClear.key.mipLevel < resource->mipLevels, - "MaterializePendingClearForTexture: textureId=%d pending clear mip=%u out of range %u", - texture.GetExternalIndex(), pendingClear.key.mipLevel, resource->mipLevels); - // FIXME: a layered clear of a GL_TEXTURE_3D texture still reads back wrong. - // KHR-GL44/45/46.geometry_shader.layered_framebuffer.clear_call_support fails on - // DirectVulkan: it attaches a 4-deep 3D texture with glFramebufferTexture (layered), - // clears with glClearBufferiv, then reads each slice back through - // glFramebufferTextureLayer and gets zeros. Those cases exist only in the GL44+ lists, - // above the 4.0 this backend reports, so they are outside the current conformance - // claim - but the feature (layered attachment, GL 3.2) is not, so an application can - // reach this. - // - // Already ruled out by bisecting with temporary bypasses, so do not re-test these: - // - the per-slice render-pass clear below (disabling it changes nothing) - // - the per-target gate in FramebufferTextureLayer_State (it already permits - // Texture3D here; bypassing it changes nothing) - // - VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT on the 3D image (not requesting it - // changes nothing) - // What IS fixed here is the subresource range below: a layered GL clear queues - // layerCount = depth, which is illegal for a VK_IMAGE_TYPE_3D image, and the old code - // passed it straight through - running the case standalone against the previous build - // trips MOBILEGL_ASSERT(baseArrayLayer + layerCount <= arrayLayers) as 0 + 4 <= 1. - // - // Note when picking this up: the case does not reproduce standalone the way it behaves - // in a batch run (batch passed before this change, standalone asserted), so it depends - // on state left by earlier cases. Reproduce it inside a chunk, not on its own. - // - // A 3D image keeps its GL layers on the z axis (arrayLayers == 1), so the pending - // clear's "layer" is a slice index bounded by the mip level's depth. - const Bool clearAddressesDepthSlices = resource->viewType == VK_IMAGE_VIEW_TYPE_3D; - const Uint32 clearableLayers = clearAddressesDepthSlices - ? std::max(resource->depth >> pendingClear.key.mipLevel, 1u) - : resource->arrayLayers; - MOBILEGL_ASSERT(pendingClear.key.baseArrayLayer + pendingClear.key.layerCount <= clearableLayers, - "MaterializePendingClearForTexture: textureId=%d pending clear layer span [%u, %u) exceeds %u", - texture.GetExternalIndex(), pendingClear.key.baseArrayLayer, - pendingClear.key.baseArrayLayer + pendingClear.key.layerCount, clearableLayers); - // Whether this clear names a strict SUBSET of the level. A layered attachment - // (glFramebufferTexture) queues layerCount = the whole depth, a single-slice one - // (glFramebufferTextureLayer) queues 1 - so the key already distinguishes them, and it is - // the clear's span that decides, not the image's slice count. Reading the latter sent a - // layered clear of a 3D texture down the per-slice path, where it cleared slice zero and - // left the rest stale (geometry_shader.layered_framebuffer.clear_call_support). - const Bool clearsWholeLevel = - pendingClear.key.baseArrayLayer == 0 && pendingClear.key.layerCount >= clearableLayers; - if (clearAddressesDepthSlices && clearableLayers > 1 && !clearsWholeLevel) { - // vkCmdClearColorImage cannot clear a subset of a 3D image's slices: - // VUID-vkCmdClearColorImage-baseArrayLayer-01472 pins baseArrayLayer to 0 and - // layerCount to 1 for VK_IMAGE_TYPE_3D, i.e. the whole mip level. A render pass whose - // only content is its LOAD_OP_CLEAR does address exactly one slice, because its - // attachment is a 2D view over that slice. - auto clearPayload3D = pendingClear.payload; - PreCompensateSrgbClearColor(clearPayload3D, resource->format); - VkClearValue sliceClearValue{}; - sliceClearValue.color = MakeVkClearColorValue(clearPayload3D, ColorFormatLacksAlpha(&texture)); - if (!ClearDepthSliceWithRenderPass(commandBuffer, texture, pendingClear.key.mipLevel, - pendingClear.key.baseArrayLayer, sliceClearValue)) { - // The device or the format refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, so - // there is no way to name this slice. Leaving it uncleared is wrong pixels; - // asserting would abort a process that glFramebufferTextureLayer can reach at will. - MGLOG_W_ONCE("MaterializePendingClearForTexture: textureId=%d slice %u could not be cleared " - "(no 2D-array-compatible view)", - texture.GetExternalIndex(), pendingClear.key.baseArrayLayer); - } - continue; - } - - VkImageSubresourceRange subresourceRange{}; - subresourceRange.baseMipLevel = pendingClear.key.mipLevel; - subresourceRange.levelCount = 1; - // VUID-vkCmdClearColorImage-baseArrayLayer-01472: for a VK_IMAGE_TYPE_3D image the range - // must name baseArrayLayer 0 and layerCount 1, which Vulkan reads as "the whole mip - // level" - the z extent is not an array dimension. A layered GL clear queues - // layerCount = depth, which is the right GL answer and an illegal Vulkan one. - subresourceRange.baseArrayLayer = clearAddressesDepthSlices ? 0u : pendingClear.key.baseArrayLayer; - subresourceRange.layerCount = clearAddressesDepthSlices ? 1u : pendingClear.key.layerCount; - - auto clearPayload = pendingClear.payload; - if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - PreCompensateSrgbClearColor(clearPayload, resource->format); - const VkClearColorValue clearValue = - MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(&texture)); - vkCmdClearColorImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - &clearValue, 1, &subresourceRange); - } else { - VkImageAspectFlags clearAspectMask = 0; - if ((resource->aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 && - (clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { - clearAspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; - } - if ((resource->aspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 && - (clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { - clearAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - MOBILEGL_ASSERT(clearAspectMask != 0, - "MaterializePendingClearForTexture: textureId=%d has no matching depth/stencil clear mask", - texture.GetExternalIndex()); - subresourceRange.aspectMask = clearAspectMask; - VkClearDepthStencilValue clearValue{}; - clearValue.depth = clearPayload.depth; - clearValue.stencil = clearPayload.stencil; - vkCmdClearDepthStencilImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - &clearValue, 1, &subresourceRange); - sampledLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; - } - } - - ok = VkTextureManager::TransitionImageLayout( - commandBuffer, resource->image, clearLayout, sampledLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels); - MOBILEGL_ASSERT(ok, - "MaterializePendingClearForTexture: failed to transition textureId=%d to sampled layout", - texture.GetExternalIndex()); - resource->layout = sampledLayout; - // The clear is now a write in this recording, which is what orders a later upload into - // the image after it (OrderPendingUploadAfterRecording). - m_textureManager->StampResourceRecordingWrite(*resource); - - m_clearManager->PopPendingClear(&texture); - MGLOG_D("MaterializePendingClearForTexture: textureId=%d pending clear materialized", - texture.GetExternalIndex()); - return true; - } - - Bool VulkanRenderer::OrderPendingUploadAfterRecording(FrameContext::FrameData& frame, - MG_State::GLState::ITextureObject& texture) { - if (!m_textureManager->HasPendingTexelUpload(texture)) { - return false; - } - // An open pass drawing into the image stamps that write only when it ends. - if (const auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - activeRenderPass != nullptr && ActiveRenderPassUsesTexture(*activeRenderPass, texture)) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - const Bool clearFirst = m_clearManager->HasPendingClear(&texture); - if (!clearFirst && !m_textureManager->WasTextureWrittenThisRecording(texture)) { - // Nothing recorded writes the image: the upload may run ahead of the recording. - return false; - } - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - if (clearFirst && !MaterializePendingClearForTexture(frame.commandBuffer, texture, - /*uploadPendingTexels=*/false)) { - MGLOG_E_ONCE("OrderPendingUploadAfterRecording: textureId=%d kept its queued clear; it may land over " - "texels written after it", - texture.GetExternalIndex()); - } - if (!FlushPendingCommands()) { - MGLOG_E_ONCE("OrderPendingUploadAfterRecording: submit failed; textureId=%d's upload may run ahead of " - "earlier work on it", - texture.GetExternalIndex()); - return false; - } - m_frameContext.BeginCommandRecording(); - // New command buffer: the same reset SetupDraw applies when it begins one. - return true; - } - - void VulkanRenderer::SettleTexelsBeforeQueuedClear( - const MG_State::GLState::FramebufferObject& framebuffer, - const MG_State::GLState::FramebufferAttachmentObject& attachment) { - // The default framebuffer's attachments are placeholders: its clears land on the - // swapchain image (MaterializePendingClearForDefaultFramebuffer) and it has no texels. - if (framebuffer.IsDefaultFramebuffer() || !attachment.IsTexture() || attachment.IsRenderbuffer()) { - return; - } - const auto texture = attachment.GetTexture(); - if (!texture || (m_textureManager->HasLiveImage(*texture) && - !m_textureManager->HasPendingTexelUpload(*texture))) { - return; - } - OrderPendingUploadAfterRecording(m_frameContext.GetCurrent(), *texture); - m_textureManager->SyncTextureAndGetDescriptor(*texture); - } - - Bool VulkanRenderer::MaterializeMultisamplePendingClear(VkCommandBuffer commandBuffer, - MG_State::GLState::ITextureObject& texture, - VkTextureManager::TextureResource& resource, - const Vector& pendingClears) { - // Colour only. GL can queue a depth/stencil clear on a multisample texture too, and the - // load-op idiom would serve it just as well, but this helper attaches its view as a - // COLOUR attachment; declining is honest and leaves the queue intact for a later path. - if ((resource.aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - MGLOG_E_ONCE("MaterializeMultisamplePendingClear: textureId=%d is a multisample depth/stencil texture; " - "its queued clear cannot be materialised out of a render pass yet", - texture.GetExternalIndex()); - return false; - } - - Bool allCleared = true; - for (const auto& pendingClear : pendingClears) { - if (pendingClear.key.mipLevel >= resource.mipLevels) { - MGLOG_E_ONCE("MaterializeMultisamplePendingClear: textureId=%d pending clear mip=%u out of range %u", - texture.GetExternalIndex(), pendingClear.key.mipLevel, resource.mipLevels); - allCleared = false; - continue; - } - auto clearPayload = pendingClear.payload; - PreCompensateSrgbClearColor(clearPayload, resource.format); - VkClearValue clearValue{}; - clearValue.color = MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(&texture)); - - // A multisample texture has exactly one level and, for the 2D target, one layer; the - // array target's layers are cleared one at a time, which is what this helper's - // per-layer view gives us. - const Uint32 firstLayer = pendingClear.key.baseArrayLayer; - const Uint32 layerCount = std::max(pendingClear.key.layerCount, 1u); - for (Uint32 layer = firstLayer; layer < firstLayer + layerCount; ++layer) { - if (layer >= resource.arrayLayers) break; - // COLOR_ATTACHMENT_OPTIMAL, not TRANSFER_DST: the image never has transfer usage, - // and a render target is where it came from and where it is going. - if (!ClearDepthSliceWithRenderPass(commandBuffer, texture, pendingClear.key.mipLevel, layer, - clearValue, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)) { - MGLOG_E_ONCE("MaterializeMultisamplePendingClear: textureId=%d layer %u could not be cleared", - texture.GetExternalIndex(), layer); - allCleared = false; - } - } - } - if (!allCleared) { - return false; - } - - // The load-op clear left every touched layer in COLOR_ATTACHMENT_OPTIMAL (each pass's - // finalLayout), so that - not the tracked layout on entry - is what the closing barrier - // has to start from. - // TransitionImageLayout takes the tracked layout by reference and updates it, so seeding - // it is both how the barrier learns its source and how resource->layout ends up right. - resource.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - const Bool ok = VkTextureManager::TransitionImageLayout( - commandBuffer, resource.image, resource.layout, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - resource.aspect, 0, resource.mipLevels); - if (!ok) { - MGLOG_E_ONCE("MaterializeMultisamplePendingClear: failed to transition textureId=%d to the sampled layout", - texture.GetExternalIndex()); - return false; - } - - m_clearManager->PopPendingClear(&texture); - MGLOG_D("MaterializeMultisamplePendingClear: textureId=%d pending clear materialised through a load-op pass", - texture.GetExternalIndex()); - return true; - } - - Bool VulkanRenderer::MaterializePendingClearForRenderbuffer( - VkCommandBuffer commandBuffer, const SharedPtr& renderbuffer) { - if (renderbuffer == nullptr) { - return true; - } - ClearAttachmentPayload clearPayload{}; - if (!m_renderPassManager->GetPendingRenderbufferClear(renderbuffer.get(), clearPayload)) { - return true; - } - MOBILEGL_ASSERT(VkRenderPassManager::GetActiveRenderPass() == nullptr, - "MaterializePendingClearForRenderbuffer requires no active render pass"); - - auto* resource = m_renderPassManager->GetOrCreateRenderbufferResource(renderbuffer); - if (resource == nullptr) { - MGLOG_E_ONCE("MaterializePendingClearForRenderbuffer: no resource for renderbuffer %u", - renderbuffer->GetExternalIndex()); - return false; - } - - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(resource->layout, srcStageMask, srcAccessMask); - - Bool ok = VkTextureManager::TransitionImageLayout( - commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, - resource->aspect, 0, 1); - MOBILEGL_ASSERT(ok, - "MaterializePendingClearForRenderbuffer: failed to transition renderbuffer %u to TRANSFER_DST", - renderbuffer->GetExternalIndex()); - - VkImageSubresourceRange subresourceRange{}; - subresourceRange.baseMipLevel = 0; - subresourceRange.levelCount = 1; - subresourceRange.baseArrayLayer = 0; - subresourceRange.layerCount = 1; - - VkImageLayout steadyLayout; - if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - PreCompensateSrgbClearColor(clearPayload, resource->format); - // RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1. - const VkClearColorValue clearValue = MakeVkClearColorValue( - clearPayload, - MG_Util::GetBaseInternalFormatComponentCount(renderbuffer->GetInternalFormat()) == 3); - vkCmdClearColorImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - &clearValue, 1, &subresourceRange); - steadyLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - } else { - VkImageAspectFlags clearAspectMask = 0; - if ((resource->aspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 && - (clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { - clearAspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; - } - if ((resource->aspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 && - (clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { - clearAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - if (clearAspectMask != 0) { - subresourceRange.aspectMask = clearAspectMask; - VkClearDepthStencilValue clearValue{}; - clearValue.depth = clearPayload.depth; - clearValue.stencil = clearPayload.stencil; - vkCmdClearDepthStencilImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - &clearValue, 1, &subresourceRange); - } - steadyLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - } - - VkImageLayout clearLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - ok = VkTextureManager::TransitionImageLayout( - commandBuffer, resource->image, clearLayout, steadyLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | - VK_ACCESS_TRANSFER_READ_BIT, - resource->aspect, 0, 1); - MOBILEGL_ASSERT(ok, - "MaterializePendingClearForRenderbuffer: failed to transition renderbuffer %u to steady layout", - renderbuffer->GetExternalIndex()); - resource->layout = steadyLayout; - - m_renderPassManager->PopPendingRenderbufferClear(renderbuffer.get()); - MGLOG_D("MaterializePendingClearForRenderbuffer: renderbuffer %u pending clear materialized", - renderbuffer->GetExternalIndex()); - return true; - } - void VulkanRenderer::DestroyMultisampleResolveScratchImage() { if (m_msResolveScratch.image != VK_NULL_HANDLE) { vmaDestroyImage(m_allocator, m_msResolveScratch.image, m_msResolveScratch.allocation); @@ -5225,383 +4006,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_msResolveScratch = {}; } - Bool VulkanRenderer::AcquireMultisampleResolveScratchImage(VkCommandBuffer commandBuffer, VkFormat format, - VkExtent2D extent) { - if (extent.width == 0 || extent.height == 0 || format == VK_FORMAT_UNDEFINED) { - return false; - } - // Grow-only, and never shrink: these blits repeat at one or two sizes, so the steady state - // is one allocation for the whole process. - if (m_msResolveScratch.image == VK_NULL_HANDLE || m_msResolveScratch.format != format || - m_msResolveScratch.extent.width < extent.width || m_msResolveScratch.extent.height < extent.height) { - const VkExtent2D grown = {std::max(extent.width, m_msResolveScratch.extent.width), - std::max(extent.height, m_msResolveScratch.extent.height)}; - DestroyMultisampleResolveScratchImage(); - - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = format; - imageInfo.extent = {grown.width, grown.height, 1}; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VmaAllocationCreateInfo allocationInfo{}; - allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - if (vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &m_msResolveScratch.image, - &m_msResolveScratch.allocation, nullptr) != VK_SUCCESS) { - // Soft failure: the caller keeps the direct resolve, which is what shipped before. - MGLOG_E_ONCE("AcquireMultisampleResolveScratchImage: vmaCreateImage failed (format=%d %ux%u)", - static_cast(format), grown.width, grown.height); - m_msResolveScratch = {}; - return false; - } - m_msResolveScratch.format = format; - m_msResolveScratch.extent = grown; - m_msResolveScratch.layout = VK_IMAGE_LAYOUT_UNDEFINED; - } - - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(m_msResolveScratch.layout, srcStageMask, srcAccessMask); - if (!VkTextureManager::TransitionImageLayout(commandBuffer, m_msResolveScratch.image, - m_msResolveScratch.layout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, srcStageMask, - VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT)) { - return false; - } - return true; - } - - // The aspects a depth/stencil format actually carries. VkTextureManager keeps its own copy of - // this private, and the swapchain's depth/stencil image has no TextureResource to ask. - static VkImageAspectFlags GetDepthStencilAspectMaskForFormat(VkFormat format) { - switch (format) { - case VK_FORMAT_D16_UNORM: - case VK_FORMAT_X8_D24_UNORM_PACK32: - case VK_FORMAT_D32_SFLOAT: - return VK_IMAGE_ASPECT_DEPTH_BIT; - case VK_FORMAT_D16_UNORM_S8_UINT: - case VK_FORMAT_D24_UNORM_S8_UINT: - case VK_FORMAT_D32_SFLOAT_S8_UINT: - return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - case VK_FORMAT_S8_UINT: - return VK_IMAGE_ASPECT_STENCIL_BIT; - default: - return VK_IMAGE_ASPECT_NONE; - } - } - - // The depth/stencil half of MaterializePendingClearForDefaultFramebuffer. Separate only - // because the image, the aspects and the clear value are all different from the colour one; - // the reason it exists is the same - a readback with no intervening draw has no render pass - // to fold the parked clear into. - Bool VulkanRenderer::MaterializePendingDepthStencilClearForDefaultFramebuffer( - VkCommandBuffer commandBuffer, const MG_State::GLState::FramebufferAttachmentObject& attachment, - const ClearAttachmentPayload& payload) { - // The image the readback that triggered this materialization resolves; a parked clear - // must land where the read looks (see m_defaultFramebufferImageIndex). - const Uint32 swapchainImageIndex = DefaultFramebufferReadIndex(true); - const VkImage depthStencilImage = m_swapchainObject.GetDepthStencilImage(swapchainImageIndex); - if (depthStencilImage == VK_NULL_HANDLE) { - return false; - } - const VkImageAspectFlags imageAspects = - GetDepthStencilAspectMaskForFormat(m_swapchainObject.GetDepthStencilFormat()); - VkImageAspectFlags clearAspects = 0; - if ((payload.mask & GL_DEPTH_BUFFER_BIT) != 0) clearAspects |= (imageAspects & VK_IMAGE_ASPECT_DEPTH_BIT); - if ((payload.mask & GL_STENCIL_BUFFER_BIT) != 0) clearAspects |= (imageAspects & VK_IMAGE_ASPECT_STENCIL_BIT); - if (clearAspects == 0) { - // Nothing this image can express; drop the pending clear rather than leave it to a - // later render pass that would load it against an aspect that does not exist. - m_clearManager->PopPendingClear(attachment); - return true; - } - - VkImageLayout currentLayout = m_swapchainObject.GetDepthStencilImageLayout(swapchainImageIndex); - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(currentLayout, srcStageMask, srcAccessMask); - VkImageLayout clearLayout = currentLayout; - if (!VkTextureManager::TransitionImageLayout(commandBuffer, depthStencilImage, clearLayout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, srcStageMask, - VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, - VK_ACCESS_TRANSFER_WRITE_BIT, imageAspects)) { - return false; - } - - VkClearDepthStencilValue clearValue{}; - clearValue.depth = payload.depth; - clearValue.stencil = payload.stencil; - VkImageSubresourceRange range{}; - range.aspectMask = clearAspects; - range.baseMipLevel = 0; - range.levelCount = 1; - range.baseArrayLayer = 0; - range.layerCount = 1; - vkCmdClearDepthStencilImage(commandBuffer, depthStencilImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearValue, - 1, &range); - - VkImageLayout settledLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags dstAccessMask = 0; - GetImageTransitionDestinationState(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, dstStageMask, - dstAccessMask); - if (!VkTextureManager::TransitionImageLayout(commandBuffer, depthStencilImage, settledLayout, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, imageAspects)) { - return false; - } - m_swapchainObject.SetDepthStencilImageLayout(swapchainImageIndex, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - // The image now holds real values, so the next render pass must LOAD them rather than - // treat the attachment as undefined and discard the clear that just executed. - m_swapchainObject.SetDepthStencilContentDefined(swapchainImageIndex, true); - - m_clearManager->PopPendingClear(attachment); - MGLOG_D("MaterializePendingClearForDefaultFramebuffer: swapchain depth/stencil image %u pending clear " - "materialized (aspects=0x%x)", - swapchainImageIndex, static_cast(clearAspects)); - return true; - } - - // A glClear on the DEFAULT framebuffer is parked as a pending clear and folded into the next - // render pass's loadOp. With no draw in between there is no render pass, so a readback that - // followed such a clear blitted the untouched swapchain image and returned the PREVIOUS - // frame's colour - which is exactly what the whole KHR-GL40.draw_indirect.negative-* family - // sees (clear, an erroring draw that never executes, glReadPixels expecting zeroes). - // - // Materializing it means clearing the acquired swapchain image itself, which is why this - // cannot reuse MaterializePendingClearForTexture: the default FBO's colour attachment is a - // placeholder ITextureObject, and syncing it would allocate and clear an unrelated image. - Bool VulkanRenderer::MaterializePendingClearForDefaultFramebuffer(VkCommandBuffer commandBuffer, - MG_State::GLState::FramebufferObject& fbo, - FramebufferAttachmentType attachmentType) { - if (!fbo.IsDefaultFramebuffer() || attachmentType == FramebufferAttachmentType::None) { - return true; - } - const auto& attachment = fbo.GetAttachment(attachmentType); - if (!attachment.IsTexture() || attachment.IsRenderbuffer()) { - return true; - } - ClearAttachmentPayload payload{}; - if (!m_clearManager->GetPendingClear(attachment, payload)) { - return true; - } - MOBILEGL_ASSERT(VkRenderPassManager::GetActiveRenderPass() == nullptr || - commandBuffer != m_frameContext.GetCurrent().commandBuffer, - "MaterializePendingClearForDefaultFramebuffer requires no active render pass"); - - if ((payload.mask & GL_COLOR_BUFFER_BIT) == 0) { - return MaterializePendingDepthStencilClearForDefaultFramebuffer(commandBuffer, attachment, payload); - } - - // Same rule as the depth/stencil twin below: the parked clear has to land on the image - // the readback that reaches this resolves. - const Uint32 swapchainImageIndex = DefaultFramebufferReadIndex(true); - const VkImage swapchainImage = m_swapchainObject.GetImage(swapchainImageIndex); - if (swapchainImage == VK_NULL_HANDLE) { - return false; - } - VkImageLayout currentLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex); - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(currentLayout, srcStageMask, srcAccessMask); - VkImageLayout clearLayout = currentLayout; - if (!VkTextureManager::TransitionImageLayout(commandBuffer, swapchainImage, clearLayout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, srcStageMask, - VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT)) { - return false; - } - - // The clear colour goes in verbatim, alpha included. Forcing opaque alpha here is what - // makes a glClear(0,0,0,0) read back as (0,0,0,1) - the default framebuffer's placeholder - // attachment can describe an alpha-less format while the swapchain image it stands for - // has a real alpha channel. - VkClearColorValue clearColor{}; - clearColor.float32[0] = payload.color.x(); - clearColor.float32[1] = payload.color.y(); - clearColor.float32[2] = payload.color.z(); - clearColor.float32[3] = payload.color.w(); - VkImageSubresourceRange range{}; - range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - range.baseMipLevel = 0; - range.levelCount = 1; - range.baseArrayLayer = 0; - range.layerCount = 1; - vkCmdClearColorImage(commandBuffer, swapchainImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearColor, 1, - &range); - - VkImageLayout settledLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags dstAccessMask = 0; - GetImageTransitionDestinationState(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, dstStageMask, dstAccessMask); - if (!VkTextureManager::TransitionImageLayout(commandBuffer, swapchainImage, settledLayout, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, - VK_IMAGE_ASPECT_COLOR_BIT)) { - return false; - } - m_swapchainObject.SetImageLayout(swapchainImageIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - - // Popped, not left behind: the clear has executed, so letting the next render pass load - // it again as a loadOp would erase whatever is drawn between here and there. - m_clearManager->PopPendingClear(attachment); - MGLOG_D("MaterializePendingClearForDefaultFramebuffer: swapchain image %u pending clear materialized", - swapchainImageIndex); - return true; - } - - Bool VulkanRenderer::TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, - MG_State::GLState::FramebufferObject& readFbo, - MG_State::GLState::FramebufferObject& drawFbo, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLenum filter) { - const Bool drawIsDefaultFbo = drawFbo.IsDefaultFramebuffer(); - if (!drawIsDefaultFbo) { - return false; - } - - BlitImageBinding srcBinding{}; - BlitImageBinding dstBinding{}; - // The source reads the image the app last rendered into, the destination re-points the - // default framebuffer at the acquired one - see m_defaultFramebufferImageIndex. The - // source resolves first, so a default-FBO source still names the pre-present image. - const Uint32 readDefaultImageIndex = DefaultFramebufferReadIndex(readFbo.IsDefaultFramebuffer()); - const Uint32 drawDefaultImageIndex = DefaultFramebufferWriteIndex(drawIsDefaultFbo); - if (!ResolveColorBlitBinding(readFbo, true, readDefaultImageIndex, m_swapchainObject, *m_textureManager, - *m_renderPassManager, srcBinding) || - !ResolveColorBlitBinding(drawFbo, false, drawDefaultImageIndex, m_swapchainObject, *m_textureManager, - *m_renderPassManager, dstBinding)) { - return false; - } - if (srcBinding.trackedLayout == nullptr) { - MGLOG_E_ONCE("BlitFramebuffer skipped: shader blit to default framebuffer requires a texture-backed source framebuffer"); - return false; - } - - auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - if (activeRenderPass != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - - const auto& attachment = readFbo.GetAttachment(readFbo.GetReadBuffer()); - auto sourceTexture = attachment.GetTexture(); - MOBILEGL_ASSERT(sourceTexture != nullptr, "TryBlitToDefaultFramebufferWithShader: source texture is null"); - const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); - MOBILEGL_ASSERT(clearReady, - "TryBlitToDefaultFramebufferWithShader: failed to materialize pending clear for textureId=%d", - sourceTexture->GetExternalIndex()); - // P7 wave 2-B2 (CONTRACT-P7 §5.2, (B')): THE MONOLITH ARM ON THE BAKED MODULE. Below - // this line the pull build binds a hidden GL program, writes three default-block - // uniforms into its global UBO and hands the sampler to the application's descriptor - // binder. A disaggregated build has no such program on either arm: the same pass, with - // the same vertex shader (WireColorBlit.vert IS this one, ported), takes its rects on a - // push constant and its sampler from its own descriptor set. - // - // The pre-amble above stays shared because it is not about the program: resolving the - // two bindings, ending an open render pass and materializing a pending clear on the - // source are what makes the blit legal, whichever pass performs it. What the baked path - // does NOT need is the sampling transition, the descriptor sync and the cached sampled - // view - it makes its own view and moves the layouts through TransitionWireImage, whose - // tracker is the same `trackedLayout` these bindings carry. - { - const auto toWire = [](const BlitImageBinding& binding) { - WireImage out{}; - out.image = binding.image; - out.format = binding.format; - out.extent = {static_cast(binding.extent.x()), static_cast(binding.extent.y())}; - out.samples = binding.sampleCount; - out.aspect = binding.aspectMask; - out.level = binding.mipLevel; - out.levels = binding.mipLevelCount; - out.layer = binding.baseArrayLayer; - out.layers = binding.layerCount; - out.viewType = binding.layerCount > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; - out.trackedLayout = binding.trackedLayout; - out.layout = binding.trackedLayout ? *binding.trackedLayout : VK_IMAGE_LAYOUT_UNDEFINED; - return out; - }; - WireImage source = toWire(srcBinding); - WireImage destination = toWire(dstBinding); - // The default framebuffer's layout lives in the swapchain object, not in a - // per-resource slot, which is what ResolveWireImage does for the same surface and - // what TransitionWireImage writes back through. - destination.isDefault = true; - destination.swapchainImageIndex = drawDefaultImageIndex; - destination.trackedLayout = nullptr; - destination.layout = m_swapchainObject.GetImageLayout(drawDefaultImageIndex); - // `false` here means "not serviced by the shader pass", and the caller falls - // through to the native vkCmdBlitImage arm - the same contract the pull build's - // early returns above have. - return BlitWireColorToDefault(source, destination, srcX0, srcY0, srcX1, srcY1, - dstX0, dstY0, dstX1, dstY1, filter); - } - } - - // A single-aspect depth/stencil copy between two images of the same format. An image copy - // moves the format's own native representation, and for a packed depth-stencil format that - // representation is one word holding both aspects: a region that names only the depth aspect - // still carries the source's stencil byte into the destination, and the other way round, so a - // depth-only resolve silently replaces the destination's stencil (and a stencil-only resolve - // its depth). Staging the requested aspect through a buffer makes the region's aspectMask - // authoritative - a buffer copy names one aspect and moves exactly its texels - which is the - // same reason the wire resolve (WireFramebuffer.inc, ResolveWireDepthStencil) copies its - // resolved aspect through a buffer instead of patching the image copy. - // - // Both images are in a TRANSFER layout when this is called, so no transition is needed here. - Bool VulkanRenderer::CopyDepthStencilAspectThroughBuffer( - FrameContext::FrameData& frame, VkImage srcImage, VkImage dstImage, VkFormat format, Uint32 srcMipLevel, - Uint32 srcBaseArrayLayer, Uint32 dstMipLevel, Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX, - GLint dstY, GLsizei width, GLsizei height, VkImageAspectFlagBits aspect) { - // The aspect's own tightly defined copy layout: a stencil texel is one byte whatever the - // format, and the depth half of a packed format copies on its own at 2 or 4 bytes. - const VkDeviceSize texelSize = aspect == VK_IMAGE_ASPECT_STENCIL_BIT - ? 1 - : (format == VK_FORMAT_D16_UNORM || format == VK_FORMAT_D16_UNORM_S8_UINT ? 2 : 4); - const VkDeviceSize bytes = static_cast(width) * static_cast(height) * texelSize; - VkBufferObject staging; - if (!staging.Create({.allocator = m_allocator, .size = bytes, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE})) { - MGLOG_E_ONCE("CopyDepthStencilAspectThroughBuffer: failed to create the aspect staging buffer"); - return false; - } - VkBufferImageCopy region{}; - region.imageSubresource = {aspect, srcMipLevel, srcBaseArrayLayer, 1}; - region.imageOffset = {srcX, srcY, 0}; - region.imageExtent = {static_cast(width), static_cast(height), 1}; - vkCmdCopyImageToBuffer(frame.commandBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - staging.GetHandle(), 1, ®ion); - VkBufferMemoryBarrier barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.buffer = staging.GetHandle(); - barrier.size = bytes; - vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 1, &barrier, 0, nullptr); - region.imageSubresource = {aspect, dstMipLevel, dstBaseArrayLayer, 1}; - region.imageOffset = {dstX, dstY, 0}; - vkCmdCopyBufferToImage(frame.commandBuffer, staging.GetHandle(), dstImage, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - // The commands above still name this buffer and this frame's submission has not happened - // yet, so the buffer retires with the frame rather than with this scope. - m_bufferManager.DeferRelease(Move(staging)); - return true; - } - void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { @@ -5609,635 +4013,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } - void VulkanRenderer::BlitNamedFramebuffer(const SharedPtr& readFbo, - const SharedPtr& drawFbo, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLbitfield mask, GLenum filter) { - static constexpr GLbitfield kSupportedBlitMask = - GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; - if ((mask & ~kSupportedBlitMask) != 0) { - MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported mask bits=0x%x", static_cast(mask)); - return; - } - const Bool isColorBlit = (mask & GL_COLOR_BUFFER_BIT) != 0; - const Bool isDepthBlit = (mask & GL_DEPTH_BUFFER_BIT) != 0; - const Bool isStencilBlit = (mask & GL_STENCIL_BUFFER_BIT) != 0; - if (!isColorBlit && !isDepthBlit && !isStencilBlit) { - return; - } - if (filter != GL_NEAREST && filter != GL_LINEAR) { - MGLOG_E_ONCE("BlitFramebuffer skipped: unsupported filter=0x%x", static_cast(filter)); - return; - } - if ((isDepthBlit || isStencilBlit) && filter != GL_NEAREST) { - MGLOG_E_ONCE("BlitFramebuffer skipped: depth/stencil blits require GL_NEAREST"); - return; - } - - // The scissor test clips blit writes: intersect the destination rectangle with - // the scissor box and shrink the source proportionally. - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest)) { - const IntVec4& scissor = MG_Pipe::gPipeInputs.GetScissorBox(); - const auto clipAxis = [](GLint& d0, GLint& d1, GLint& s0, GLint& s1, GLint clipLo, GLint clipHi) -> Bool { - const Bool dstFlipped = d1 < d0; - GLint lo = dstFlipped ? d1 : d0; - GLint hi = dstFlipped ? d0 : d1; - const GLint newLo = std::max(lo, clipLo); - const GLint newHi = std::min(hi, clipHi); - if (newLo >= newHi) { - return false; - } - const double srcSpan = static_cast(s1 - s0); - const double dstSpan = static_cast(d1 - d0); - const double scale = dstSpan != 0.0 ? srcSpan / dstSpan : 0.0; - const GLint origD0 = d0; - const GLint clippedD0 = dstFlipped ? newHi : newLo; - const GLint clippedD1 = dstFlipped ? newLo : newHi; - s0 = s0 + static_cast(std::lround((clippedD0 - origD0) * scale)); - s1 = s0 + static_cast(std::lround((clippedD1 - clippedD0) * scale)); - d0 = clippedD0; - d1 = clippedD1; - return true; - }; - if (!clipAxis(dstX0, dstX1, srcX0, srcX1, scissor.x(), scissor.x() + scissor.z()) || - !clipAxis(dstY0, dstY1, srcY0, srcY1, scissor.y(), scissor.y() + scissor.w())) { - return; // fully scissored out - } - } - - MOBILEGL_ASSERT(readFbo != nullptr, "VulkanRenderer::BlitFramebuffer: read framebuffer is null"); - MOBILEGL_ASSERT(drawFbo != nullptr, "VulkanRenderer::BlitFramebuffer: draw framebuffer is null"); - if (IsUnsupportedFramebufferForDirectVulkan(*readFbo) || - IsUnsupportedFramebufferForDirectVulkan(*drawFbo)) { - RecordUnsupportedFramebufferError(__func__); - return; - } - - auto& frame = m_frameContext.GetCurrent(); - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - - auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - if (activeRenderPass != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - - const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer(); - const Bool drawIsDefaultFbo = drawFbo->IsDefaultFramebuffer(); - if (isColorBlit && drawIsDefaultFbo && - RequiresShaderBlitToDefaultFramebuffer(m_swapchainObject.GetPreTransform())) { - if (TryBlitToDefaultFramebufferWithShader(frame, *readFbo, *drawFbo, - srcX0, srcY0, srcX1, srcY1, - dstX0, dstY0, dstX1, dstY1, filter)) { - return; - } - MGLOG_E_ONCE("BlitFramebuffer skipped: rotated blit to default framebuffer requires a texture-backed source framebuffer"); - return; - } - - Vector depthStencilAspects; - if (isDepthBlit) depthStencilAspects.push_back(VK_IMAGE_ASPECT_DEPTH_BIT); - if (isStencilBlit) depthStencilAspects.push_back(VK_IMAGE_ASPECT_STENCIL_BIT); - for (const VkImageAspectFlagBits depthStencilAspect : depthStencilAspects) { - BlitImageBinding srcBinding{}; - BlitImageBinding dstBinding{}; - // A default-FBO source resolves the image the app last rendered into; a default-FBO - // destination re-points it at the image Present() acquired (see - // m_defaultFramebufferImageIndex). The source's index is taken first, so a blit - // whose two sides are both the default framebuffer still names one image. - const Uint32 srcImageIndex = DefaultFramebufferReadIndex(readIsDefaultFbo); - const Uint32 dstImageIndex = DefaultFramebufferWriteIndex(drawIsDefaultFbo); - if (!ResolveFramebufferBlitBinding(*readFbo, true, srcImageIndex, m_swapchainObject, - *m_textureManager, *m_renderPassManager, - depthStencilAspect, srcBinding) || - !ResolveFramebufferBlitBinding(*drawFbo, false, dstImageIndex, m_swapchainObject, - *m_textureManager, *m_renderPassManager, - depthStencilAspect, dstBinding)) { - // A buffer named in the mask but absent from either framebuffer copies - // nothing for that buffer; the other requested buffers still blit. - continue; - } - - if (srcX1 < srcX0 || srcY1 < srcY0 || dstX1 < dstX0 || dstY1 < dstY0) { - MGLOG_E_ONCE("BlitFramebuffer skipped: depth blits with flipped rectangles are not supported yet"); - continue; - } - - const Int srcWidth = srcX1 - srcX0; - const Int srcHeight = srcY1 - srcY0; - const Int dstWidth = dstX1 - dstX0; - const Int dstHeight = dstY1 - dstY0; - if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) { - MGLOG_E_ONCE("BlitFramebuffer skipped: degenerate depth blit rectangle"); - continue; - } - // A scaling depth blit is legal GL and vkCmdBlitImage scales natively; only a same-size - // pair can take the cheaper vkCmdCopyImage. - const Bool depthBlitScales = srcWidth != dstWidth || srcHeight != dstHeight; - - if (!readIsDefaultFbo) { - const auto sourceAttachmentType = ResolveFramebufferCopyAttachmentType(*readFbo, true, srcBinding.aspectMask); - const auto& sourceAttachment = readFbo->GetAttachment(sourceAttachmentType); - if (auto sourceTexture = sourceAttachment.GetTexture(); sourceTexture != nullptr) { - const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); - MOBILEGL_ASSERT(clearReady, - "BlitFramebuffer: failed to materialize pending clear for depth source textureId=%d", - sourceTexture->GetExternalIndex()); - } else if (sourceAttachment.IsRenderbuffer()) { - const Bool clearReady = MaterializePendingClearForRenderbuffer(frame.commandBuffer, - sourceAttachment.GetRenderbuffer()); - MOBILEGL_ASSERT(clearReady, - "BlitFramebuffer: failed to materialize pending clear for depth source renderbuffer %u", - sourceAttachment.GetRenderbuffer()->GetExternalIndex()); - } - } - - const auto destAttachmentType = - ResolveFramebufferCopyAttachmentType(*drawFbo, false, dstBinding.aspectMask); - if (drawIsDefaultFbo) { - // Same ordering rule for the default framebuffer's depth/stencil - see the - // colour twin below. - const Bool dstClearReady = MaterializePendingClearForDefaultFramebuffer( - frame.commandBuffer, *drawFbo, destAttachmentType); - MOBILEGL_ASSERT(dstClearReady, - "BlitFramebuffer: failed to materialize the default framebuffer's pending " - "depth/stencil clear"); - } else { - // A clear queued for the destination predates this blit in API order; - // execute it now, or its deferred materialization would later stomp the - // copied contents (MC 26.3 OIT clears cloud_depth, then blits the main - // depth into it - the stale loadOp=CLEAR erased the copy). - const auto& destAttachment = drawFbo->GetAttachment(destAttachmentType); - if (auto destTexture = destAttachment.GetTexture(); destTexture != nullptr) { - const Bool dstClearReady = MaterializePendingClearForTexture(frame.commandBuffer, *destTexture); - MOBILEGL_ASSERT(dstClearReady, - "BlitFramebuffer: failed to materialize pending clear for depth destination textureId=%d", - destTexture->GetExternalIndex()); - } else if (destAttachment.IsRenderbuffer()) { - const Bool dstClearReady = MaterializePendingClearForRenderbuffer(frame.commandBuffer, - destAttachment.GetRenderbuffer()); - MOBILEGL_ASSERT(dstClearReady, - "BlitFramebuffer: failed to materialize pending clear for depth destination renderbuffer %u", - destAttachment.GetRenderbuffer()->GetExternalIndex()); - } - } - - const VkImageLayout srcOriginalLayout = readIsDefaultFbo - ? m_swapchainObject.GetDepthStencilImageLayout(srcImageIndex) - : *srcBinding.trackedLayout; - if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E_ONCE("BlitFramebuffer skipped: depth source image layout is undefined"); - continue; - } - - const VkImageLayout dstOriginalLayout = drawIsDefaultFbo - ? m_swapchainObject.GetDepthStencilImageLayout(dstImageIndex) - : *dstBinding.trackedLayout; - const VkImageLayout dstRestoreLayout = dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED - ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL - : dstOriginalLayout; - - // vkCmdCopyImage requires identical depth formats; a mismatched pair (e.g. - // a D24S8 renderbuffer into a DEPTH_COMPONENT24 texture backed by the - // D32_SFLOAT fallback) round-trips the region through the host with a - // per-texel re-encode instead. - if (srcBinding.format != dstBinding.format) { - if (readIsDefaultFbo || drawIsDefaultFbo) { - MGLOG_E_ONCE("BlitFramebuffer skipped: cross-format depth/stencil blit with the default framebuffer"); - continue; - } - if (!BlitDepthAcrossFormats(frame, srcBinding.image, srcBinding.format, srcBinding.trackedLayout, - srcBinding.mipLevel, srcBinding.baseArrayLayer, dstBinding.image, - dstBinding.format, dstBinding.trackedLayout, dstBinding.mipLevel, - dstBinding.baseArrayLayer, srcX0, srcY0, dstX0, dstY0, srcX1 - srcX0, - srcY1 - srcY0, srcOriginalLayout, dstRestoreLayout, - depthStencilAspect == VK_IMAGE_ASPECT_STENCIL_BIT)) { - continue; - } - continue; - } - - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask); - // Both blit regions below name `baseArrayLayer` from their binding, and a layered depth - // attachment puts that above 0. These barriers carry a mip range only - their layer - // range is every layer (see VkTextureManager::TransitionImageLayout). - if (readIsDefaultFbo) { - VkImageLayout srcTrackedLayout = srcOriginalLayout; - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, - srcBinding.mipLevel, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain depth source image", __func__); - m_swapchainObject.SetDepthStencilImageLayout(srcImageIndex, srcTrackedLayout); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, - srcBinding.mipLevel, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to transition depth source image", __func__); - } - - VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags dstAccessMask = 0; - GetImageTransitionSourceState(dstOriginalLayout, dstStageMask, dstAccessMask); - if (drawIsDefaultFbo) { - VkImageLayout dstTrackedLayout = dstOriginalLayout; - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, dstTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask, - dstBinding.mipLevel, dstBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain depth destination image", __func__); - m_swapchainObject.SetDepthStencilImageLayout(dstImageIndex, dstTrackedLayout); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask, - dstBinding.mipLevel, dstBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to transition depth destination image", __func__); - } - - // The default framebuffer is stored display-side-up, so a rect aimed at it (or read - // from it) has to be converted out of GL's bottom-origin space - the same conversion - // the colour blit below applies. vkCmdCopyImage cannot express it (it has no second - // offset to invert), so a default-framebuffer side forces the vkCmdBlitImage form even - // at equal size. Without this a scissored depth blit into the default framebuffer - // wrote the MIRRORED band: KHR-GL*.framebuffer_blit.scissor_blit clips to the lower - // left quadrant, and the depth landed in the upper one. - const Bool depthBlitNeedsOrientation = readIsDefaultFbo || drawIsDefaultFbo; - if (depthBlitScales || depthBlitNeedsOrientation) { - // vkCmdCopyImage cannot resize; NEAREST is the only filter Vulkan allows for a - // depth/stencil blit anyway, and the GL front end already rejects the others. - VkImageBlit blitRegion{}; - blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask; - blitRegion.srcSubresource.mipLevel = srcBinding.mipLevel; - blitRegion.srcSubresource.baseArrayLayer = srcBinding.baseArrayLayer; - blitRegion.srcSubresource.layerCount = srcBinding.layerCount; - blitRegion.srcOffsets[0] = {srcX0, srcY0, 0}; - blitRegion.srcOffsets[1] = {srcX1, srcY1, 1}; - blitRegion.dstSubresource.aspectMask = dstBinding.aspectMask; - blitRegion.dstSubresource.mipLevel = dstBinding.mipLevel; - blitRegion.dstSubresource.baseArrayLayer = dstBinding.baseArrayLayer; - blitRegion.dstSubresource.layerCount = dstBinding.layerCount; - blitRegion.dstOffsets[0] = {dstX0, dstY0, 0}; - blitRegion.dstOffsets[1] = {dstX1, dstY1, 1}; - if (readIsDefaultFbo) { - ApplyNativeBlitDefaultFramebufferSourceTransform(m_swapchainObject.GetPreTransform(), srcBinding, - blitRegion); - } - if (drawIsDefaultFbo) { - ApplyNativeBlitDefaultFramebufferTransform(m_swapchainObject.GetPreTransform(), dstBinding, - blitRegion); - } - vkCmdBlitImage(frame.commandBuffer, - srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &blitRegion, VK_FILTER_NEAREST); - } else if (isDepthBlit != isStencilBlit && srcBinding.layerCount == 1 && dstBinding.layerCount == 1 && - (GetDepthStencilAspectMaskForFormat(srcBinding.format) & - ~static_cast(depthStencilAspect)) != 0) { - // Exactly one aspect of a packed depth-stencil format is being copied into a - // destination that carries the other one as well. A whole-image copy moves the - // packed native word and would replace that other aspect with the source's; see - // CopyDepthStencilAspectThroughBuffer. Same format, same size, one layer - the - // shapes the buffer round trip can express. A scaled or display-oriented copy - // takes the blit above, and a multi-layer one keeps the image copy. - (void)CopyDepthStencilAspectThroughBuffer(frame, srcBinding.image, dstBinding.image, - srcBinding.format, srcBinding.mipLevel, - srcBinding.baseArrayLayer, dstBinding.mipLevel, - dstBinding.baseArrayLayer, srcX0, srcY0, dstX0, dstY0, - srcWidth, srcHeight, depthStencilAspect); - } else { - VkImageCopy copyRegion{}; - copyRegion.srcSubresource.aspectMask = srcBinding.aspectMask; - copyRegion.srcSubresource.mipLevel = srcBinding.mipLevel; - copyRegion.srcSubresource.baseArrayLayer = srcBinding.baseArrayLayer; - copyRegion.srcSubresource.layerCount = srcBinding.layerCount; - copyRegion.srcOffset = {srcX0, srcY0, 0}; - copyRegion.dstSubresource.aspectMask = dstBinding.aspectMask; - copyRegion.dstSubresource.mipLevel = dstBinding.mipLevel; - copyRegion.dstSubresource.baseArrayLayer = dstBinding.baseArrayLayer; - copyRegion.dstSubresource.layerCount = dstBinding.layerCount; - copyRegion.dstOffset = {dstX0, dstY0, 0}; - copyRegion.extent = {static_cast(srcWidth), static_cast(srcHeight), 1}; - - vkCmdCopyImage(frame.commandBuffer, - srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ©Region); - } - - VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcRestoreAccessMask = 0; - GetImageTransitionDestinationState(srcOriginalLayout, srcRestoreStageMask, srcRestoreAccessMask); - if (readIsDefaultFbo) { - VkImageLayout srcTrackedLayout = m_swapchainObject.GetDepthStencilImageLayout(srcImageIndex); - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, srcTrackedLayout, srcOriginalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, - VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask, - srcBinding.mipLevel, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain depth source image layout", __func__); - m_swapchainObject.SetDepthStencilImageLayout(srcImageIndex, srcTrackedLayout); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, srcOriginalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, - VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask, - srcBinding.mipLevel, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to restore depth source image layout", __func__); - } - - VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags dstRestoreAccessMask = 0; - GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask); - if (drawIsDefaultFbo) { - VkImageLayout dstTrackedLayout = m_swapchainObject.GetDepthStencilImageLayout(dstImageIndex); - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, dstTrackedLayout, dstRestoreLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask, - dstBinding.mipLevel, dstBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain depth destination image layout", __func__); - m_swapchainObject.SetDepthStencilImageLayout(dstImageIndex, dstTrackedLayout); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, dstRestoreLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask, - dstBinding.mipLevel, dstBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to restore depth destination image layout", __func__); - } - } - if (!isColorBlit) { - return; - } - - BlitImageBinding srcBinding{}; - BlitImageBinding dstBinding{}; - // Same split as the depth/stencil loop above, and the same ordering rule: the source's - // index is read before the destination's write re-points it. - const Uint32 srcImageIndex = DefaultFramebufferReadIndex(readIsDefaultFbo); - const Uint32 dstImageIndex = DefaultFramebufferWriteIndex(drawIsDefaultFbo); - if (!ResolveColorBlitBinding(*readFbo, true, srcImageIndex, m_swapchainObject, *m_textureManager, - *m_renderPassManager, srcBinding) || - !ResolveColorBlitBinding(*drawFbo, false, dstImageIndex, m_swapchainObject, *m_textureManager, - *m_renderPassManager, dstBinding)) { - return; - } - - if (!readIsDefaultFbo) { - const auto& sourceAttachment = readFbo->GetAttachment(readFbo->GetReadBuffer()); - auto sourceTexture = sourceAttachment.GetTexture(); - if (sourceTexture != nullptr) { - const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); - MOBILEGL_ASSERT(clearReady, - "BlitFramebuffer: failed to materialize pending clear for source textureId=%d", - sourceTexture->GetExternalIndex()); - } else if (sourceAttachment.IsRenderbuffer()) { - const Bool clearReady = - MaterializePendingClearForRenderbuffer(frame.commandBuffer, sourceAttachment.GetRenderbuffer()); - MOBILEGL_ASSERT(clearReady, - "BlitFramebuffer: failed to materialize pending clear for source renderbuffer %u", - sourceAttachment.GetRenderbuffer()->GetExternalIndex()); - } - } - - if (drawIsDefaultFbo) { - // The default framebuffer needs the same ordering, and needed it before anything - // consumed its parked clear: Minecraft clears the default framebuffer, renders the - // world into its own framebuffer and BLITS the result out, so nothing between the - // clear and the blit ever opens a render pass on the default framebuffer to fold the - // clear in as a loadOp. The clear therefore stayed pending across the whole frame, - // and the first path that did materialize it - the readback - executed it AFTER the - // blit and handed back a blank frame (every DirectVulkan retrace, ssim 0.000005). - const Bool dstClearReady = MaterializePendingClearForDefaultFramebuffer( - frame.commandBuffer, *drawFbo, drawFbo->GetDrawBuffers()[0]); - MOBILEGL_ASSERT(dstClearReady, - "BlitFramebuffer: failed to materialize the default framebuffer's pending clear"); - } else { - // A clear queued for the destination predates this blit in API order; execute - // it now, or its deferred materialization would later stomp the blitted color. - const auto& destAttachment = drawFbo->GetAttachment(drawFbo->GetDrawBuffers()[0]); - auto destTexture = destAttachment.GetTexture(); - if (destTexture != nullptr) { - const Bool dstClearReady = MaterializePendingClearForTexture(frame.commandBuffer, *destTexture); - MOBILEGL_ASSERT(dstClearReady, - "BlitFramebuffer: failed to materialize pending clear for destination textureId=%d", - destTexture->GetExternalIndex()); - } else if (destAttachment.IsRenderbuffer()) { - const Bool dstClearReady = - MaterializePendingClearForRenderbuffer(frame.commandBuffer, destAttachment.GetRenderbuffer()); - MOBILEGL_ASSERT(dstClearReady, - "BlitFramebuffer: failed to materialize pending clear for destination renderbuffer %u", - destAttachment.GetRenderbuffer()->GetExternalIndex()); - } - } - - VkImageLayout srcLayout = readIsDefaultFbo - ? m_swapchainObject.GetImageLayout(srcImageIndex) - : *srcBinding.trackedLayout; - VkImageLayout dstLayout = drawIsDefaultFbo - ? m_swapchainObject.GetImageLayout(dstImageIndex) - : *dstBinding.trackedLayout; - const VkImageLayout srcOriginalLayout = srcLayout; - const VkImageLayout dstOriginalLayout = dstLayout; - const VkImageLayout dstRestoreLayout = dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED - ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL - : dstOriginalLayout; - - if (readIsDefaultFbo && srcLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E_ONCE("BlitFramebuffer skipped: swapchain source image layout is undefined"); - return; - } - if (srcLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E_ONCE("BlitFramebuffer skipped: source image layout is undefined"); - return; - } - - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(srcLayout, srcStageMask, srcAccessMask); - if (readIsDefaultFbo) { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, srcLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask); - MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain source image", __func__); - m_swapchainObject.SetImageLayout(srcImageIndex, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, 0, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__); - } - - VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags dstAccessMask = 0; - GetImageTransitionSourceState(dstLayout, dstStageMask, dstAccessMask); - if (drawIsDefaultFbo) { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, dstLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask); - MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain destination image", __func__); - m_swapchainObject.SetImageLayout(dstImageIndex, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask, 0, dstBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to transition destination image", __func__); - } - - VkImageBlit blitRegion{}; - blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask; - blitRegion.srcSubresource.mipLevel = srcBinding.mipLevel; - blitRegion.srcSubresource.baseArrayLayer = srcBinding.baseArrayLayer; - blitRegion.srcSubresource.layerCount = srcBinding.layerCount; - blitRegion.srcOffsets[0] = {srcX0, srcY0, 0}; - blitRegion.srcOffsets[1] = {srcX1, srcY1, 1}; - blitRegion.dstSubresource.aspectMask = dstBinding.aspectMask; - blitRegion.dstSubresource.mipLevel = dstBinding.mipLevel; - blitRegion.dstSubresource.baseArrayLayer = dstBinding.baseArrayLayer; - blitRegion.dstSubresource.layerCount = dstBinding.layerCount; - blitRegion.dstOffsets[0] = {dstX0, dstY0, 0}; - blitRegion.dstOffsets[1] = {dstX1, dstY1, 1}; - if (readIsDefaultFbo) { - ApplyNativeBlitDefaultFramebufferSourceTransform(m_swapchainObject.GetPreTransform(), srcBinding, - blitRegion); - } - if (drawIsDefaultFbo) { - ApplyNativeBlitDefaultFramebufferTransform(m_swapchainObject.GetPreTransform(), dstBinding, blitRegion); - } - - if (srcBinding.sampleCount != VK_SAMPLE_COUNT_1_BIT && dstBinding.sampleCount == VK_SAMPLE_COUNT_1_BIT) { - // GL multisample resolve blits are 1:1 by spec; vkCmdBlitImage cannot read a - // multisampled source, so the samples have to come down through vkCmdResolveImage. - const Uint32 resolveWidth = static_cast(std::abs(srcX1 - srcX0)); - const Uint32 resolveHeight = static_cast(std::abs(srcY1 - srcY0)); - - // vkCmdResolveImage takes ONE offset per side, so it cannot express the axis inversion - // that a default-framebuffer rect needs - it would land the mirrored band. When the - // transforms above actually moved the region, split the operation: resolve into a - // single-sample scratch image at raw offsets, then blit THAT into the destination with - // the (already transformed) region, which vkCmdBlitImage can invert. - const Bool regionWasTransformed = - (readIsDefaultFbo || drawIsDefaultFbo) && - (blitRegion.srcOffsets[0].x != srcX0 || blitRegion.srcOffsets[0].y != srcY0 || - blitRegion.srcOffsets[1].x != srcX1 || blitRegion.srcOffsets[1].y != srcY1 || - blitRegion.dstOffsets[0].x != dstX0 || blitRegion.dstOffsets[0].y != dstY0 || - blitRegion.dstOffsets[1].x != dstX1 || blitRegion.dstOffsets[1].y != dstY1); - const Bool useScratchResolve = - regionWasTransformed && resolveWidth > 0 && resolveHeight > 0 && - AcquireMultisampleResolveScratchImage(frame.commandBuffer, srcBinding.format, - {resolveWidth, resolveHeight}); - - VkImageResolve resolveRegion{}; - resolveRegion.srcSubresource = blitRegion.srcSubresource; - resolveRegion.dstSubresource = blitRegion.dstSubresource; - resolveRegion.extent = {resolveWidth, resolveHeight, 1}; - if (useScratchResolve) { - // The scratch copy is a plain single-layer colour image, and the resolve reads the - // SOURCE band the (possibly inverted) transformed region names - taking its min so - // an inverted pair still describes the same band. - resolveRegion.srcOffset = {std::min(blitRegion.srcOffsets[0].x, blitRegion.srcOffsets[1].x), - std::min(blitRegion.srcOffsets[0].y, blitRegion.srcOffsets[1].y), 0}; - resolveRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - resolveRegion.dstSubresource.mipLevel = 0; - resolveRegion.dstSubresource.baseArrayLayer = 0; - resolveRegion.dstSubresource.layerCount = 1; - resolveRegion.dstOffset = {0, 0, 0}; - vkCmdResolveImage(frame.commandBuffer, - srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - m_msResolveScratch.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &resolveRegion); - - VkImageLayout scratchLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - const Bool scratchReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, m_msResolveScratch.image, scratchLayout, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - MOBILEGL_ASSERT(scratchReady, "%s: failed to transition the resolve scratch image", __func__); - m_msResolveScratch.layout = scratchLayout; - - // Second leg: the scratch image holds the resolved band at its own origin, so the - // source side of the region becomes the whole scratch rect and only the - // destination keeps the transform. - VkImageBlit scratchBlit = blitRegion; - scratchBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - scratchBlit.srcSubresource.mipLevel = 0; - scratchBlit.srcSubresource.baseArrayLayer = 0; - scratchBlit.srcSubresource.layerCount = 1; - scratchBlit.srcOffsets[0] = {0, 0, 0}; - scratchBlit.srcOffsets[1] = {static_cast(resolveWidth), static_cast(resolveHeight), 1}; - vkCmdBlitImage(frame.commandBuffer, - m_msResolveScratch.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &scratchBlit, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); - } else { - resolveRegion.srcOffset = {std::min(srcX0, srcX1), std::min(srcY0, srcY1), 0}; - resolveRegion.dstOffset = {std::min(dstX0, dstX1), std::min(dstY0, dstY1), 0}; - vkCmdResolveImage(frame.commandBuffer, - srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &resolveRegion); - } - } else { - vkCmdBlitImage(frame.commandBuffer, - srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &blitRegion, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); - } - - VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcRestoreAccessMask = 0; - GetImageTransitionDestinationState(srcOriginalLayout, srcRestoreStageMask, srcRestoreAccessMask); - if (readIsDefaultFbo) { - VkImageLayout srcTrackedLayout = m_swapchainObject.GetImageLayout(srcImageIndex); - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, srcTrackedLayout, srcOriginalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, - VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask); - MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain source image layout", __func__); - m_swapchainObject.SetImageLayout(srcImageIndex, srcTrackedLayout); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, srcOriginalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, - VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, srcBinding.aspectMask, 0, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to restore source image layout", __func__); - } - - VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags dstRestoreAccessMask = 0; - GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask); - if (drawIsDefaultFbo) { - VkImageLayout dstTrackedLayout = m_swapchainObject.GetImageLayout(dstImageIndex); - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, dstTrackedLayout, dstRestoreLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask); - MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain destination image layout", __func__); - m_swapchainObject.SetImageLayout(dstImageIndex, dstTrackedLayout); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, dstRestoreLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, dstBinding.aspectMask, 0, dstBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to restore destination image layout", __func__); - } - } - void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { CopyWireFramebufferToTexture(target,level,xoffset,yoffset,x,y,width,height); @@ -6794,18 +4569,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } - Bool VulkanRenderer::FinishPendingGpuWork() { - MakeXfbWritesVisible(); - auto& frame = m_frameContext.GetCurrent(); - if (!frame.isCommandRecording) { - return true; - } - if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - return SubmitReadbackCommandsAndWait(frame); - } - Bool VulkanRenderer::SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame) { if (frame.isCommandRecording) { m_frameContext.EndCommandRecording(); @@ -7277,252 +5040,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - void VulkanRenderer::GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { - const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); - const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); - auto& activeUnit = MG_Pipe::gPipeInputs.GetTextureUnitObject(MG_Pipe::gPipeInputs.GetActiveTextureUnit()); - auto textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject(); - GetTextureImage(textureObject, textureUploadTarget, level, format, type, -1, pixels); - } - - void VulkanRenderer::GetTextureImage(const SharedPtr& textureObject, - TextureUploadTarget textureUploadTarget, GLint level, GLenum format, - GLenum type, GLsizei bufSize, GLvoid* pixels) { - if (textureObject == nullptr || textureObject->GetStorageType() != TextureStorageType::Mipmap) { - return; - } - - auto* textureMipmapObject = static_cast(textureObject.get()); - if (level < 0 || static_cast(level) >= textureMipmapObject->GetMipmapLevelCount()) { - MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: level %d is out of range", level); - return; - } - - // The sync below uploads pending texels, which must land after work already recorded - // against this image (see OrderPendingUploadAfterRecording). - OrderPendingUploadAfterRecording(m_frameContext.GetCurrent(), *textureObject); - auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*textureObject); - // Two shapes end up in the same place, and for the same reason: the GL level being read has - // no GPU storage, so UploadDirtyMipLevels never wrote it and the CPU shadow is the ONLY copy - // of its bytes - which makes the shadow both the safe answer and the correct one. - // - // (a) No VkImage at all. A mutable texture whose GL level 0 was never defined - - // glTexImage2D(GL_TEXTURE_2D, 5, ...) and nothing else, exactly what the - // clear_tex_image conformance cases build. VkTextureManager takes storage mip 0 as the - // physical image extent (CheckMipmapCompleteness), so it refuses to back the texture. - // (b) A VkImage with FEWER mip levels than the GL level count. GetUploadMipLevelCount - // breaks at the first level with a zero extent, so "level 0 defined, a gap, level 3 - // defined" produces a one-mip image while GL_TEXTURE_MAX_LEVEL-style state still - // reports four levels. The same clamp also fires on a base level small enough that the - // full chain is shorter than the levels the application defined. - // - // (b) is the dangerous one and is why the level is bounded against the RESOURCE and not only - // against the GL-side count above: writing that level into imageSubresource.mipLevel is an - // out-of-range subresource, which is the promise the driver takes at face value. The - // glCopyImageSubData path two functions up carries the same guard for the same reason, added - // after it SIGSEGV'd inside the Adreno driver; the readback never had one. - const Bool hasImage = resource != nullptr && resource->image != VK_NULL_HANDLE; - const Bool levelIsBacked = - hasImage && ToStorageMipLevel(textureObject.get(), level) < resource->mipLevels; - if (!levelIsBacked) { - // Never gated on "syncing was inconvenient": a blanket shadow answer would silently - // return stale bytes for every render-to-texture result. - MGLOG_D("DirectVulkan::GetTexImage: textureId=%u level %d has no GPU storage (%s); answering " - "from the CPU shadow", - textureObject->GetExternalIndex(), level, - hasImage ? "the image has fewer mip levels" : "the texture has no VkImage"); - MG_Impl::GLImpl::CopyTextureImageToClientOrPBO_State(textureObject, textureUploadTarget, level, format, - type, bufSize, pixels, - "DirectVulkan::GetTextureImage"); - return; - } - - auto& frame = m_frameContext.GetCurrent(); - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *textureObject); - MOBILEGL_ASSERT(clearReady, - "GetTexImage: failed to materialize pending clear for textureId=%d", - textureObject->GetExternalIndex()); - - // WHICH FACE the caller asked for. glGetTexImage names one face of a cube map through the - // TARGET token (GL_TEXTURE_CUBE_MAP_NEGATIVE_X and friends, GL 4.6 core 8.11), and a cube - // map's six faces are its VkImage's six ARRAY LAYERS - so unless the token is turned into a - // baseArrayLayer, every face token reads layer 0 and the whole cube answers as +X. The - // image's own target cannot supply this: a plain GL_TEXTURE_CUBE_MAP is not an array target, - // so the layer arithmetic below leaves it at one layer starting at zero, which is precisely - // the layer this face index has to displace. Same conversion, same reason, as - // VkClearManager's / VkRenderPassManager's ResolveAttachmentBaseArrayLayer, which resolve an - // ATTACHMENT's face; this is the readback's copy of it. Zero for every other target, - // including a cube map ARRAY - that one arrives as TextureUploadTarget::CubeMapArray with - // its layer-faces already counted in the level's z, not as a face token. - const Bool isCubeFaceTarget = textureUploadTarget >= TextureUploadTarget::CubeMapPositiveX && - textureUploadTarget <= TextureUploadTarget::CubeMapNegativeZ; - const Int glCubeFaceLayer = isCubeFaceTarget - ? static_cast(textureUploadTarget) - static_cast(TextureUploadTarget::CubeMapPositiveX) - : 0; - // The storage-image layer a copy starts at: the view's layer origin plus, for the one shape - // that names a face, the face the target token chose. Every copy below asks here, so the - // depth branch and the colour path cannot disagree about which layer was read. - const Uint32 baseArrayLayer = ToStorageArrayLayer(textureObject.get(), glCubeFaceLayer); - - if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { - if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL || format == GL_STENCIL_INDEX) { - const auto levelSize = - textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast(level)); - // Storage space: `resource` is the storage texture's, so a view's level and - // layer have to be shifted into its numbering (see ToStorageMipLevel). - const Uint32 arrayLayer = baseArrayLayer; - const Uint32 storageLevel = ToStorageMipLevel(textureObject.get(), level); - // A 1D array's levelSize.y() is its LAYER count, and those layers are the rows - // GL wants back - but in Vulkan they are array layers of a one-row image, not - // rows of layer 0, so the read has to be told which of the two it is looking at. - const Uint32 sourceLayers = - textureObject->GetTarget() == TextureTarget::Texture1DArray - ? static_cast(std::max(levelSize.y(), 1)) - : 1u; - ReadDepthStencilImageToClient(resource->image, resource->format, &resource->layout, resource->aspect, - storageLevel, arrayLayer, 0, 0, levelSize.x(), - levelSize.y(), format, type, pixels, - /*defaultFramebufferOrientation=*/false, sourceLayers); - } else { - MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: color query of a non-color texture"); - } - return; - } - - const auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast(level)); - const GLsizei width = texelSize.x(); - const GLsizei height = texelSize.y(); - if (width <= 0 || height <= 0) { - return; - } - // GetTexImage returns every slice of a 3D level and every layer of an array - // level; GL_PACK_IMAGE_HEIGHT / GL_PACK_SKIP_IMAGES apply to the 3D/array - // destination layout (GL 3.3 section 6.1.4). - const auto imageTextureTarget = textureObject->GetTarget(); - const Bool is3dImage = imageTextureTarget == TextureTarget::Texture3D; - const Bool is1dArrayImage = imageTextureTarget == TextureTarget::Texture1DArray; - const Bool isArrayImage = is1dArrayImage || - imageTextureTarget == TextureTarget::Texture2DArray || - imageTextureTarget == TextureTarget::TextureCubeMapArray; - const GLsizei depthSlices = is3dImage ? std::max(texelSize.z(), 1) : 1; - // How many layers of the CALLER's texture a copy has to cover. `texelSize` above is already - // measured in the caller's space - a view's layer axis is the VIEW's, and the destination - // size the frontend validates a by-name readback against is built from the same extents - - // so the copy has to cover exactly those layers. resource->arrayLayers is the STORAGE - // owner's count: reading that many layers through a layer-windowed view covers layers the - // view does not name and, because the copy starts at the view's layer origin, runs past the - // last layer the image has. - const Uint32 storageArrayLayers = static_cast(resource->arrayLayers); - const Uint32 layersFromOrigin = - baseArrayLayer < storageArrayLayers ? storageArrayLayers - baseArrayLayer : 0u; - const GLsizei arrayLayers = static_cast( - isArrayImage ? (textureObject->IsTextureView() - ? std::min(static_cast(textureObject->GetViewNumLayers()), layersFromOrigin) - : storageArrayLayers) - : 1u); - // A 1D array level comes back as ONE two-dimensional image whose rows are its layers - // (GL 4.6 core 8.11.4), so its layers are already counted by `height` above and must not - // multiply the slice count the way a 2D-array's or a cube-array's do. Vulkan still keeps - // them in arrayLayers on a one-row image, which is what the copy region below says - the - // two describe the same tightly-packed bytes. - const GLsizei sliceCount = - std::max(depthSlices * (is1dArrayImage ? 1 : arrayLayers), 1); - if (bufSize >= 0) { - const Int dstChannels = GetReadbackChannelCount(format); - if ((type == GL_UNSIGNED_BYTE || type == GL_FLOAT) && dstChannels > 0) { - const SizeT dstComponentSize = type == GL_FLOAT ? sizeof(Float) : sizeof(Uint8); - const SizeT minSize = static_cast(width) * static_cast(height) * - static_cast(dstChannels) * dstComponentSize; - if (static_cast(bufSize) < minSize) { - MGLOG_E_ONCE("DirectVulkan::GetTextureImage skipped: destination buffer is too small"); - return; - } - } - } - - const SizeT sourceTexelSize = GetReadbackTexelSize(resource->format); - if (sourceTexelSize == 0) { - MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: unsupported source format=%d", - static_cast(resource->format)); - return; - } - const VkDeviceSize readbackSize = static_cast(width) * - static_cast(height) * - static_cast(sliceCount) * sourceTexelSize; - VkBufferObject readback; - if (!readback.Create({ - .allocator = m_allocator, - .size = readbackSize, - .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO, - .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, - })) { - MGLOG_E_ONCE("DirectVulkan::GetTexImage skipped: failed to create readback buffer"); - return; - } - - const VkImageLayout originalLayout = resource->layout; - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(originalLayout, srcStageMask, srcAccessMask); - // The copy below reads EVERY layer of the level, which is exactly the range - // TransitionImageLayout barriers cover. - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, resource->aspect, - ToStorageMipLevel(textureObject.get(), level), 1); - MOBILEGL_ASSERT(ok, "%s: failed to transition texture image", __func__); - - VkBufferImageCopy copyRegion{}; - copyRegion.imageSubresource.aspectMask = resource->aspect; - // Storage space, as above: a texture view reads its own level 0 out of whichever level - // and layer of the parent it opened onto. - copyRegion.imageSubresource.mipLevel = ToStorageMipLevel(textureObject.get(), level); - // baseArrayLayer, not 0: the cube face the target token named, plus the view's layer origin - // (see above). The face is non-zero for exactly one shape - a plain cube map read one face - // at a time - and layerCount is 1 there, so the copy stays inside the six layers the image - // has. - copyRegion.imageSubresource.baseArrayLayer = baseArrayLayer; - copyRegion.imageSubresource.layerCount = static_cast(arrayLayers); - copyRegion.imageExtent = {static_cast(width), - is1dArrayImage ? 1u : static_cast(height), - static_cast(depthSlices)}; - vkCmdCopyImageToBuffer(frame.commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - readback.GetHandle(), 1, ©Region); - - VkPipelineStageFlags restoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags restoreAccessMask = 0; - GetImageTransitionDestinationState(originalLayout, restoreStageMask, restoreAccessMask); - ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, resource->layout, originalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, restoreStageMask, - VK_ACCESS_TRANSFER_READ_BIT, restoreAccessMask, resource->aspect, - ToStorageMipLevel(textureObject.get(), level), 1); - MOBILEGL_ASSERT(ok, "%s: failed to restore texture image layout", __func__); - - if (!SubmitReadbackCommandsAndWait(frame)) { - return; - } - const auto* mapped = static_cast(readback.Map()); - if (mapped == nullptr) { - MGLOG_E_ONCE("DirectVulkan::GetTextureImage skipped: failed to map readback buffer"); - return; - } - if (!readback.Invalidate(readbackSize)) { - MGLOG_E_ONCE("DirectVulkan::GetTextureImage skipped: failed to invalidate readback buffer"); - return; - } - PackReadbackToClientOrPbo(mapped, resource->format, width, height, sliceCount, format, type, pixels, - /*applyPackImageParams=*/is3dImage || isArrayImage); - } - void VulkanRenderer::GenerateMipmap(GLenum target) { GenerateWireMipmap(); return; @@ -11835,84 +9352,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool VulkanRenderer::IsDrawIndirectCountExtensionEnabled() const { - return m_drawIndirectCountExtensionEnabled; - } - - void VulkanRenderer::ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, - const RenderPassEntry &compatibleRenderPassEntry) { - auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - MOBILEGL_ASSERT(activeRenderPass, "No render pass active"); - VkClearRect clearRect{}; - clearRect.rect.offset = {0, 0}; - clearRect.rect.extent = { - static_cast(activeRenderPass->extent.x()), - static_cast(activeRenderPass->extent.y()) - }; - clearRect.baseArrayLayer = 0; - // Compatible entries share the framebuffer layer count; layered attachments clear every layer. - clearRect.layerCount = compatibleRenderPassEntry.layers; - - for (const auto& pending : compatibleRenderPassEntry.pendingClearAttachments) { - if (!pending.hasInlinePayload && pending.key.texture == nullptr) { - continue; - } - - ClearAttachmentPayload clearPayload{}; - SharedPtr liveTexture; - if (pending.hasInlinePayload) { - // The inline payload is baked into the cached RenderPassEntry and outlives - // its consumption at pass begin (loadOp CLEAR). Replaying it here would - // wipe every draw already recorded in the pass, so only clear while the - // renderbuffer's clear is still actually pending, and take the live - // payload (a newer glClear may carry different values). - if (!m_renderPassManager->GetPendingRenderbufferClear(pending.renderbuffer, clearPayload)) { - continue; - } - if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0 && pending.renderbuffer != nullptr && - MG_Util::GetBaseInternalFormatComponentCount(pending.renderbuffer->GetInternalFormat()) == 3) { - // RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1. - clearPayload.color = FloatVec4(clearPayload.color.x(), clearPayload.color.y(), - clearPayload.color.z(), 1.0f); - } - } else { - if (!m_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { - continue; - } - } - - VkClearAttachment clearAttachment{}; - clearAttachment.clearValue.depthStencil = {1.0f, 0}; - if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0) { - clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - // VkClearAttachment::colorAttachment indexes the subpass pColorAttachments (draw-buffer - // slot space, with UNUSED holes), not the compacted attachment descriptions. - clearAttachment.colorAttachment = pending.colorAttachmentSlot; - clearAttachment.clearValue.color = - MakeVkClearColorValue(clearPayload, ColorFormatLacksAlpha(liveTexture.get())); - } else { - if ((clearPayload.mask & GL_DEPTH_BUFFER_BIT) != 0) { - clearAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; - clearAttachment.clearValue.depthStencil.depth = clearPayload.depth; - } - if ((clearPayload.mask & GL_STENCIL_BUFFER_BIT) != 0) { - clearAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - clearAttachment.clearValue.depthStencil.stencil = clearPayload.stencil; - } - if (clearAttachment.aspectMask == 0) { - continue; - } - } - - vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect); - if (pending.hasInlinePayload) { - m_renderPassManager->PopPendingRenderbufferClear(pending.renderbuffer); - } else { - m_clearManager->PopPendingClear(pending.key); - } - } - } - void VulkanRenderer::DestroyComputePipelines() { if (m_device != VK_NULL_HANDLE) { for (const auto& [hash, pipeline] : m_computePipelines) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 857af4de..e5fb470f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -243,38 +243,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView = nullptr); - void ClearAttachmentsOnActiveRenderPass(VkCommandBuffer commandBuffer, - const RenderPassEntry& compatibleRenderPassEntry); - enum class ScissoredClearPrep { NotNeeded, // scissor covers the whole target — take the deferred whole-surface path instead NoOp, // nothing to clear (degenerate target or empty scissor rect) Ready, // a render pass is active; record vkCmdClearAttachments with the returned rect }; - ScissoredClearPrep PrepareScissoredClear(const MG_State::GLState::FramebufferObject& framebuffer, - VkClearRect& outClearRect); - void Clear(GLbitfield mask); void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value); void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value); void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value); - void ClearNamedFramebufferfv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLfloat* value); - void ClearNamedFramebufferiv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLint* value); - void ClearNamedFramebufferuiv(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, const GLuint* value); - void ClearNamedFramebufferfi(const SharedPtr& framebuffer, - GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - void BlitNamedFramebuffer(const SharedPtr& readFbo, - const SharedPtr& drawFbo, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLbitfield mask, GLenum filter); void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); void CopyImageSubData(const CopyImageEndpoint& srcEndpoint, @@ -311,16 +292,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 dstMipLevel, Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX, GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout, VkImageLayout dstRestoreLayout, Bool stencilAspect); - // One depth/stencil copy whose source and destination are the same format and whose region - // is a same-size, same-layer rectangle: the aspect named by `aspect` is staged through a - // device-local buffer so the destination's other aspect cannot be overwritten by the - // packed native word a whole-image copy moves. - Bool CopyDepthStencilAspectThroughBuffer(FrameContext::FrameData& frame, VkImage srcImage, - VkImage dstImage, VkFormat format, Uint32 srcMipLevel, - Uint32 srcBaseArrayLayer, Uint32 dstMipLevel, - Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX, - GLint dstY, GLsizei width, GLsizei height, - VkImageAspectFlagBits aspect); static SizeT GetReadbackTexelSize(VkFormat sourceFormat); // Map a GL bottom-left-origin rectangle into the display-oriented swapchain image. // Quarter-turn surface transforms swap the copy extent's axes. @@ -338,10 +309,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLsizei width, GLsizei height, GLenum destinationFormat, GLenum destinationType, SizeT destinationRowStride, Uint8* destinationPixels); - void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); - void GetTextureImage(const SharedPtr& texture, - TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type, - GLsizei bufSize, GLvoid* pixels); void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); void DispatchComputeIndirect(GLintptr indirect); void MemoryBarrier(GLbitfield barriers); @@ -354,8 +321,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { const PhysicalDevice& GetPhysicalDevice() const; VkInstance GetInstance() const; - Bool IsDrawIndirectCountExtensionEnabled() const; - // GL fence support, expressed in queue-submission indices backed by // real VkFences. A GL fence captures GetSyncPointSubmitIndex() at // creation: the index of the submission that will carry the commands @@ -578,11 +543,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; }; MultisampleResolveScratchImage m_msResolveScratch; - // Returns a scratch image at least `extent` in size with exactly `format`, transitioned to - // TRANSFER_DST and ready to be resolved into. Null image on failure (the caller then falls - // back to the direct resolve). - Bool AcquireMultisampleResolveScratchImage(VkCommandBuffer commandBuffer, VkFormat format, - VkExtent2D extent); void DestroyMultisampleResolveScratchImage(); struct DeferredDepthMipmapCleanup { @@ -881,14 +841,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); - void QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer, - GLenum buffer, GLint drawbuffer, - const ClearAttachmentPayload& clearPayload); - void RecordScissoredClearBuffer(const MG_State::GLState::FramebufferObject& framebuffer, - GLenum buffer, GLint drawbuffer, - const ClearAttachmentPayload& clearPayload, - const VkClearRect& clearRect); - // ---- Submission fence tracking (GL sync objects) ---- // One record per vkQueueSubmit still in flight, in ascending submit // order. Present/readback submissions reference the frame slot's @@ -1588,74 +1540,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { void ShutdownDepthMipmapResources(); void CollectDeferredDepthMipmapCleanup(Uint32 frameIndex); void DestroyDeferredDepthMipmapCleanup(); - Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, - MG_State::GLState::FramebufferObject& readFbo, - MG_State::GLState::FramebufferObject& drawFbo, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, - GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLenum filter); - // Clears one layer of a colour image through a throwaway render pass whose entire content - // is its LOAD_OP_CLEAR. Two callers, both of which a transfer clear cannot serve: a z - // slice of a VK_IMAGE_TYPE_3D image (vkCmdClearColorImage cannot name one), and a - // MULTISAMPLE image (which carries no TRANSFER_DST usage at all). `finalLayout` is the - // layout the caller already tracks for the whole image, so this never has to touch - // resource->layout. - Bool ClearDepthSliceWithRenderPass(VkCommandBuffer commandBuffer, - MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, - Uint32 depthSlice, const VkClearValue& clearValue, - VkImageLayout finalLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - // Records the texture's queued clears. By default the texture is synced first, which - // uploads any pending texels BEFORE the clear - right only for texels written before it. - // `uploadPendingTexels = false` leaves them pending (OrderPendingUploadAfterRecording). - Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, - MG_State::GLState::ITextureObject& texture, - Bool uploadPendingTexels = true); - // Texture uploads run in a batch submitted ahead of the frame's recording, so the upload - // the next sync of `texture` records would land BEFORE a write already recorded into the - // same image (a render, clear, blit or copy), and before a clear still queued for it (such - // a clear predates the texels: SettleTexelsBeforeQueuedClear). When either holds, this - // records the queued clear without the texels and submits the recording, so the upload - // follows both. An image the recording only READ keeps the old trade: no submit, and its - // earlier reads see the newer texels. It may end the render pass and replace - // frame.commandBuffer (recording again on return), so call it only where the caller holds - // no render-pass or command-buffer state across it. Returns whether it submitted. - Bool OrderPendingUploadAfterRecording(FrameContext::FrameData& frame, - MG_State::GLState::ITextureObject& texture); - // Called for each texture a glClear/glClearBuffer* is about to queue a clear on: uploads - // whatever texels are still pending (and mints the image if there is none), so that any - // texel pending when the clear is materialized was written after it. - void SettleTexelsBeforeQueuedClear(const MG_State::GLState::FramebufferObject& framebuffer, - const MG_State::GLState::FramebufferAttachmentObject& attachment); - // The multisample arm of the above. Split out rather than branched inline because it - // shares none of the transfer path: a multisample image carries no TRANSFER_DST usage, so - // neither the TRANSFER_DST transition nor vkCmdClearColorImage is legal on one. - Bool MaterializeMultisamplePendingClear(VkCommandBuffer commandBuffer, - MG_State::GLState::ITextureObject& texture, - VkTextureManager::TextureResource& resource, - const Vector& pendingClears); - Bool MaterializePendingClearForRenderbuffer( - VkCommandBuffer commandBuffer, - const SharedPtr& renderbuffer); - // The default framebuffer's twin of the two above. It cannot go through - // MaterializePendingClearForTexture: the default FBO's colour attachment is a - // placeholder texture object, and syncing THAT would clear a texture image nobody - // presents instead of the acquired swapchain image. - Bool MaterializePendingClearForDefaultFramebuffer(VkCommandBuffer commandBuffer, - MG_State::GLState::FramebufferObject& fbo, - FramebufferAttachmentType attachmentType); - // Its depth/stencil half: a different image (the swapchain's depth/stencil twin), a - // different clear command and per-aspect masking. - Bool MaterializePendingDepthStencilClearForDefaultFramebuffer( - VkCommandBuffer commandBuffer, const MG_State::GLState::FramebufferAttachmentObject& attachment, - const ClearAttachmentPayload& payload); Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame); public: - // Submits whatever is recorded and waits for it. The CPU is about to read memory - // a shader wrote (a mapped shader storage buffer), and coherent host-visible - // storage only guarantees visibility once the work that produced it has retired. - Bool FinishPendingGpuWork(); - private: void ShutdownSwapchain(); diff --git a/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp b/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp index 77ea1623..ef7db9f9 100644 --- a/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp +++ b/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp @@ -1191,39 +1191,6 @@ namespace { }; } // namespace -TEST_F(FramebufferTest, WidenedDrawBufferIsIdentifiedPerDrawBufferSlotNotPerAttachmentPoint) { - ScopedBackendOverride backend(MakeUnique(/*substituted=*/true)); - - // Complementary's `composite` framebuffer again: draw buffer 0 is a natively renderable - // RGBA8, draw buffer 1 is the widened RGB8_SNORM. Only the second may be doctored. - GLuint framebuffer = 0; - GLuint colortex7 = 0; - GLuint colortex1 = 0; - MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); - MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &colortex7); - MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &colortex1); - MG_Impl::GLImpl::TextureStorage2D(colortex7, 1, GL_RGBA8, 4, 4); - MG_Impl::GLImpl::TextureStorage2D(colortex1, 1, GL_RGB8_SNORM, 4, 4); - MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, colortex7, 0); - MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT1, colortex1, 0); - - auto& framebufferObject = MG_State::pGLContext->GetFramebufferObject(framebuffer); - ASSERT_NE(framebufferObject, nullptr); - framebufferObject->SetDrawBuffer(0, FramebufferAttachmentType::Color0); - framebufferObject->SetDrawBuffer(1, FramebufferAttachmentType::Color1); - - EXPECT_EQ(MG_Backend::DirectGLES::FramebufferImpl::ComputeAlphaWidenedDrawBufferMask(*framebufferObject), - 1u << 1); - - // Swapping the draw-buffer array moves the bit with the SLOT, not with the attachment point: - // glColorMaski and glClearBufferfv both address slots. - framebufferObject->SetDrawBuffer(0, FramebufferAttachmentType::Color1); - framebufferObject->SetDrawBuffer(1, FramebufferAttachmentType::Color0); - EXPECT_EQ(MG_Backend::DirectGLES::FramebufferImpl::ComputeAlphaWidenedDrawBufferMask(*framebufferObject), - 1u << 0); - EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); -} - TEST_F(FramebufferTest, DrawIntoAWidenedDrawBufferReachesTheDriverWithAlphaWritesMaskedOff) { ScopedRenderStateDriverStubs driver; MG_Backend::DirectGLES::FramebufferImpl::g_alphaWidenedDrawBufferMask = 1u << 1; diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 6fed64e1..8f59981d 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -30,6 +30,7 @@ // P5 b1: MGPipeResourceTrackerInstance(), so the split probe case can ask the PRODUCTION // tracker for a buffer's handle instead of minting one by hand. #include +#include #include #include #include @@ -373,12 +374,20 @@ TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensions) { TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) { MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend; + // P13 W6: the backend applies a blit (BlitFramebuffer, which the record sink calls with the + // verb's framebuffer handles); the named-framebuffer DSA entries are the verb port's alone - + // they record a verb naming the framebuffer, and the backend never sees a frontend object. const auto& funcs = backend.GetBackendFunctions().GL; - - EXPECT_NE(funcs.ClearNamedFramebufferfv, nullptr); - EXPECT_NE(funcs.ClearNamedFramebufferfi, nullptr); EXPECT_NE(funcs.BlitFramebuffer, nullptr); - EXPECT_NE(funcs.BlitNamedFramebuffer, nullptr); + EXPECT_EQ(funcs.ClearNamedFramebufferfv, nullptr); + EXPECT_EQ(funcs.BlitNamedFramebuffer, nullptr); + + MobileGL::MG_Backend::GlobalBackendFunctionsTable table = backend.GetBackendFunctions(); + MobileGL::MG_Record::InstallMonolithVerbPort(table); + EXPECT_NE(table.GL.ClearNamedFramebufferfv, nullptr); + EXPECT_NE(table.GL.ClearNamedFramebufferfi, nullptr); + EXPECT_NE(table.GL.BlitFramebuffer, nullptr); + EXPECT_NE(table.GL.BlitNamedFramebuffer, nullptr); } TEST(DirectGLESSanity, RewritesBaseInstanceBuiltinForEsslVertexShaders) { diff --git a/MobileGL/MG_Test/Texture/CMakeLists.txt b/MobileGL/MG_Test/Texture/CMakeLists.txt index dd7fdf3b..7aa390f9 100644 --- a/MobileGL/MG_Test/Texture/CMakeLists.txt +++ b/MobileGL/MG_Test/Texture/CMakeLists.txt @@ -37,20 +37,3 @@ target_link_libraries( gtest_discover_tests(TextureViewTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) -add_executable( - VkClearManagerTest - VkClearManagerTest.cpp -) - -target_include_directories(VkClearManagerTest PRIVATE - ${MGL_ROOT}/include - ${MGL_ROOT}/MobileGL -) - -target_link_libraries( - VkClearManagerTest PRIVATE - GTest::gtest_main - ${LINK_LIBRARIES} -) - -gtest_discover_tests(VkClearManagerTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)