From e0113503af0f068654518e2ce2e563c33d615cbf Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 6 Oct 2026 16:46:09 -0400 Subject: [PATCH] [Refactor] (P13 W6b): the frontend-arm code after each record-arm return goes, and the transform-feedback state keeps one role. --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 450 +---- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 8 - MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp | 6 - .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 217 -- .../Renderer/VertexInputStateFactory.cpp | 3 - .../DirectVulkan/Renderer/VulkanRenderer.cpp | 1744 ----------------- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 19 - MobileGL/MG_Impl/Pipe/PipeFill.cpp | 4 - 8 files changed, 7 insertions(+), 2444 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index f327fe59..b5873528 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -247,17 +247,6 @@ namespace MobileGL::MG_Backend::DirectGLES { g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_COMPARE_FUNC, GL_ALWAYS); } return native.get(); - if (!g_rawDepthFetchSamplerState) { - g_rawDepthFetchSamplerState = MakeShared(0); - g_rawDepthFetchSamplerState->SetMinFilter(SamplerFilterMode::Nearest); - g_rawDepthFetchSamplerState->SetMagFilter(SamplerFilterMode::Nearest); - g_rawDepthFetchSamplerState->SetMipmapMode(SamplerMipmapMode::None); - g_rawDepthFetchSamplerState->SetCompareMode(SamplerCompareMode::None); - g_rawDepthFetchSamplerState->SetSamplerCompareFunc(SamplerCompareFunc::Always); - g_rawDepthFetchSamplerBackend = MakeShared(); - } - g_rawDepthFetchSamplerBackend->SyncToBackend(g_rawDepthFetchSamplerState); - return g_rawDepthFetchSamplerBackend.get(); } // P5e (tx2), CONTRACT-P5E §5.3: the substitution's decision, over the fifteen sampler VALUES @@ -391,23 +380,6 @@ namespace MobileGL::MG_Backend::DirectGLES { } MGLOG_E_ONCE("%s skipped: the verb carried no indirect buffer handle", label); return nullptr; - auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - if (drawBuffer) { - drawBuffer->SyncPersistentMappedRange(); - const SizeT commandOffset = reinterpret_cast(indirect); - if (commandOffset + requiredBytes > drawBuffer->GetSize()) { - MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label); - return nullptr; - } - return drawBuffer->MappedData() + commandOffset; - } - - if (!indirect) { - MGLOG_E_ONCE("%s skipped: indirect pointer is null", label); - return nullptr; - } - - return reinterpret_cast(indirect); } namespace DebugImpl { @@ -802,16 +774,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // The applier's ShaderBufferWritableMask survives as the server's record of WHICH // points a shader may write through, which is what P9's narrowing channel will name. return; - const SizeT bindingPointCnt = - MG_Pipe::gPipeInputs.GetTouchedBufferBindingPointCount(BufferTarget::ShaderStorage); - for (SizeT i = 0; i < bindingPointCnt; ++i) { - const auto& obj = - MG_Pipe::gPipeInputs.GetBufferBindingPoint(BufferTarget::ShaderStorage, i).GetBoundObject(); - // P3a (D-D): announced on the reverse channel on the handle arm, poked into - // the object on the legacy one. MarkBufferGpuWritten is the one place that - // decides, so the three announcement sites stay one line each. - MarkBufferGpuWritten(obj); - } } void SyncAtomicCounterBuffers(const Vector& glBindings, Int esslBindingTop) { @@ -1678,29 +1640,6 @@ namespace MobileGL::MG_Backend::DirectGLES { resource->syncedChangeSerial = BufferImpl::ResourceSerialForHandle(res); BufferImpl::BumpBufferMutationEpoch(); continue; - if (target.buffer->IsBackendPersistentMapped()) continue; - const SizeT size = target.end - target.start; - BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId); - void* mapped = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, - static_cast(target.start), - static_cast(size), GL_MAP_READ_BIT); - if (mapped == nullptr) { - // Silent before: the capture landed in the ES buffer and the - // application's next glMapBuffer read the untouched shadow, which - // is indistinguishable from "the draw wrote nothing". - MGLOG_E_ONCE("EndTransformFeedback: failed to map backend buffer %u [%zu, %zu) for " - "capture readback (ES error %s); the captured data will NOT be visible to " - "the application", - target.backendId, target.start, target.end, - MG_Util::ConvertGLEnumToString(TakeXfbDriverError()).c_str()); - continue; - } - target.buffer->WritebackFromBackend({mapped, size}, target.start); - // WritebackFromBackend bumps the frontend change serial with no - // backend op, leaving the buffer draw-dirty behind the epoch's - // back; re-open the draw-clean memos. - BufferImpl::BumpBufferMutationEpoch(); - g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget); } } targets.clear(); @@ -1964,17 +1903,6 @@ namespace MobileGL::MG_Backend::DirectGLES { BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(i), resource->id, static_cast(range.Offset), static_cast(range.Size)); continue; - auto& point = MG_Pipe::gPipeInputs.GetBufferBindingPoint(BufferTarget::TransformFeedback, - static_cast(i)); - const auto& bufferObject = point.GetBoundObject(); - if (!bufferObject) continue; - auto* backendResource = BufferImpl::EnsureBufferResource(bufferObject); - if (!backendResource || backendResource->id == 0) continue; - const Range1D range = point.GetRange(); - const SizeT start = std::min(range.start, bufferObject->GetSize()); - const SizeT end = std::min(range.end, bufferObject->GetSize()); - if (end <= start) continue; - xfb.targets.push_back({bufferObject, backendResource->id, start, end, i}); } @@ -2128,7 +2056,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } auto& xfb = g_xfbObjects[key]; // Even each virtual context's name-0 object gets its own native object. - if ((server || name != 0) && xfb.esId == 0) { + if (xfb.esId == 0) { g_GLESFuncs.glGenTransformFeedbacks(1, &xfb.esId); } g_GLESFuncs.glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb.esId); @@ -2156,15 +2084,12 @@ namespace MobileGL::MG_Backend::DirectGLES { if (g_currentXfbName == key ) { - if (server) { - // The following BindStreamOutput names this context's actual - // default lifetime. Never create a native object for key 0. - g_currentXfbName = 0; - roleState.NeedsBind = true; - BufferImpl::InvalidateTransformFeedbackBindingShadows(); - return; - } - BindTransformFeedback(0); + // The following BindStreamOutput names this context's actual + // default lifetime. Never create a native object for key 0. + g_currentXfbName = 0; + roleState.NeedsBind = true; + BufferImpl::InvalidateTransformFeedbackBindingShadows(); + return; } } @@ -2172,7 +2097,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // driver objects and the frontend objects they pinned all belonged to it. void OnBackendContextDestroyed() { for (auto& state : g_roleXfbState) state = {}; - g_activeXfbRole = -1; g_currentXfbState = nullptr; g_xfbObjects.clear(); g_currentXfbName = 0; @@ -2354,50 +2278,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // server to stage - and reading gPipeInputs there is the role violation this family's // other monolith glue is guarded against. return true; - const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray(); - if (!currentVAO) return true; - - // A draw whose attributes all come from buffers has nothing to snapshot, and asking - // the plan would read the index stream to answer a question nobody asked. - Bool clientVertexRate = false; - if (!VaoHasEnabledClientArray(*currentVAO, &clientVertexRate)) return true; - - auto* twin = ResolveVaoTwin(currentVAO); - if (twin == nullptr) return false; - - const SharedPtr elementBuffer = - currentVAO->GetIndexBufferBindingSlot().GetBoundObject(); - ClientSnapshotSources sources{&elementBuffer}; - - MG_Pipe::MGPDrawRange range{elementStart, static_cast(count), baseVertex}; - MG_Pipe::MGPipeClientDrawInputs inputs{}; - inputs.IndexSize = indexSize; - inputs.Ranges = ⦥ - inputs.RangeCount = 1; - inputs.InstanceCount = static_cast(instanceCount); - inputs.BaseInstance = baseInstance; - inputs.ClientIndices = clientIndices; - inputs.ClientIndexBytes = clientIndexBytes; - inputs.WantVertices = clientVertexRate; - // The driver does its own restart handling; the plan has to know only that the restart - // value is not a vertex, or the snapshot would stage an element no primitive fetches. - inputs.PrimitiveRestart = MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) || - MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex); - inputs.RestartIndex = MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex) - ? (indexSize == 1 ? 0xffu : indexSize == 2 ? 0xffffu : 0xffffffffu) - : MG_Pipe::gPipeInputs.GetPrimitiveRestartIndex(); - - MG_Pipe::MGPipeClientFetchPlan plan; - if (!plan.Build(inputs, &ReadClientSnapshotBytes, &sources)) return false; - // The element SET is GL's: a divisor'd array's instance elements start at the raw - // baseInstance. The POINTER shift is not - it is this backend's emulation of what the - // driver does natively when it can (EmulatedFetchBaseInstance is the same answer the - // attribute walk uses), and applying both would shift the fetch twice. - // EmulatedFetchBaseInstance, which is declared with the draw entry points far below: - // a native-baseInstance driver applies the shift itself, so the pointer carries none. - const Uint32 fetchBaseInstance = - g_GLESCapabilities.SupportsBaseInstance ? 0u : static_cast(baseInstance); - return twin->SyncClientSideAttributesForDraw(currentVAO, plan, fetchBaseInstance); } // The indirect executors' form: the command's own words ARE the draw's fetch, and a @@ -2408,35 +2288,6 @@ namespace MobileGL::MG_Backend::DirectGLES { const Uint8* commandBytes, SizeT commandOffset, GLsizei stride, GLsizei index, Uint8 indexSize, const SharedPtr& commandBuffer) { return true; - // P13 W4a (ID-P8-14a): ASK FIRST WHETHER THERE IS ANYTHING TO SNAPSHOT. The command - // words below are read only to size a client-memory fetch; a VAO whose attributes all - // come from buffers has none, and reading them anyway cost a whole-buffer readback of a - // GPU-written command buffer per command, per viewport pass (the hang 62bfe461 left - // behind on a phone). - { - const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray(); - if (!currentVAO || !VaoHasEnabledClientArray(*currentVAO)) return true; - } - const Uint8* source = commandBytes; - if (commandBuffer) { - commandBuffer->SyncGpuWrites(); - const Uint8* mapped = commandBuffer->MappedData(); - if (mapped == nullptr) return false; - source = mapped + commandOffset; - } - const SizeT at = static_cast(index) * static_cast(stride); - if (indexSize == 0) { - DrawArraysIndirectCommand cmd{}; - std::memcpy(&cmd, source + at, sizeof(cmd)); - return SyncClientSideVertexArraysForFetch(0, cmd.first, static_cast(cmd.count), - static_cast(cmd.instanceCount), 0, cmd.baseInstance, - nullptr, 0); - } - DrawElementsIndirectCommand cmd{}; - std::memcpy(&cmd, source + at, sizeof(cmd)); - return SyncClientSideVertexArraysForFetch(indexSize, cmd.firstIndex, static_cast(cmd.count), - static_cast(cmd.instanceCount), cmd.baseVertex, - static_cast(cmd.baseInstance), nullptr, 0); } // The indexed entry points' form. `indices` is a byte offset into the bound element buffer @@ -2449,31 +2300,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // MONOLITH ONLY - see SyncClientSideVertexArraysForFetch; this wrapper's own read of // the bound VAO is the frontend read that must not happen on an apply thread. return true; - const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray(); - if (!currentVAO) return true; - const Uint8 indexSize = static_cast(MG_Util::GetGLTypeSize(type)); - if (indexSize == 0) return true; - - const Bool clientIndices = !currentVAO->GetIndexBufferBindingSlot().GetBoundObject(); - Uint32 elementStart = 0; - Uint64 clientIndexBytes = 0; - if (clientIndices) { - if (indices == nullptr) return false; - clientIndexBytes = static_cast(count) * indexSize; - } else { - const Uint64 byteOffset = reinterpret_cast(indices); - if (byteOffset % indexSize != 0 || - byteOffset / indexSize > std::numeric_limits::max()) { - MGLOG_E_ONCE("An indexed draw over a client-memory vertex array names byte offset %llu, " - "which is not a whole number of %u-byte indices; the draw is skipped", - static_cast(byteOffset), static_cast(indexSize)); - return false; - } - elementStart = static_cast(byteOffset / indexSize); - } - return SyncClientSideVertexArraysForFetch(indexSize, elementStart, count, instanceCount, baseVertex, - baseInstance, clientIndices ? indices : nullptr, - clientIndexBytes); } static void SyncClientSideVertexArraysForDrawArrays(GLint first, GLsizei count) { @@ -4093,27 +3919,6 @@ namespace MobileGL::MG_Backend::DirectGLES { } } return; - if (g_writableImageBufferUnitCount == 0) return; - for (Uint unit = 0; unit < g_writableImageBufferUnits.size(); ++unit) { - if (!g_writableImageBufferUnits[unit]) continue; - const auto& imageBinding = MG_Pipe::gPipeInputs.GetImageTextureBinding(static_cast(unit)); - if (!IsWritableImageBufferTexture(imageBinding)) { - TrackWritableImageBufferUnit(unit, false); - continue; - } - auto* textureBuffer = - static_cast(imageBinding.Texture.get()); - const auto& bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject(); - BufferImpl::MarkBufferGpuWritten(bufferObject); - // P13 W4a: image units stay on this arm until W4c, but monolith's buffer readers - // are already the record arm's (SplitHostBytesForCpuRead), and they ask the TWIN. - if (bufferObject) { - if (auto* twin = BufferImpl::FindBufferResourceForHandle( - BufferImpl::HandleOfBuffer(bufferObject.get()))) { - twin->serverGpuWritten = true; - } - } - } } void SyncImageTextureBindings() { @@ -4196,10 +4001,6 @@ namespace MobileGL::MG_Backend::DirectGLES { SyncImageTextureBinding(view); } return; - for (Uint32 unit = 0; unit < end; ++unit) { - SyncImageTextureBinding(unit); - } - return; } } // The wide pre-handle sweep reads the frontend binding of every unit, which an @@ -4208,9 +4009,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // ever arrived no unit has ever been given an image, and re-binding 0 on units that // never held one is the no-op the comment above already proved. return; - for (Uint unit = 0; unit < unitCount; ++unit) { - SyncImageTextureBinding(unit); - } } // What the draw path last swept the image units against. A draw never swept them at all: @@ -4288,16 +4086,6 @@ namespace MobileGL::MG_Backend::DirectGLES { g_imageSweepBackendContextGeneration = g_backendContextGeneration; g_imageSweepValid = true; return; - if (g_imageSweepValid && g_imageSweepContextId == keys.contextId && - g_imageSweepSamplingGeneration == keys.samplingGeneration && - g_imageSweepBackendContextGeneration == g_backendContextGeneration) { - return; - } - SyncImageTextureBindings(); - g_imageSweepContextId = keys.contextId; - g_imageSweepSamplingGeneration = keys.samplingGeneration; - g_imageSweepBackendContextGeneration = g_backendContextGeneration; - g_imageSweepValid = true; } } // namespace TextureImpl @@ -4422,7 +4210,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // applier's OWN bound handle, so it is this binding's by construction (ID-19). // Monolith keeps the corroboration verbatim. return true; - return record.Fbo == g_backendFramebufferObjects.HandleOf(bound.get()); } static Bool SyncedFramebufferSerialIsCurrent(FramebufferTarget target, Uint64 serial) { @@ -8987,48 +8774,6 @@ namespace MobileGL::MG_Backend::DirectGLES { nullptr, static_cast(actualDrawCount), stride, "MultiDrawElementsIndirectCount"); return; - - auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - auto parameterBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); - if (!drawBuffer) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: no GL_DRAW_INDIRECT_BUFFER is bound"); - return; - } - if (!parameterBuffer) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: no GL_PARAMETER_BUFFER is bound"); - return; - } - - drawBuffer->SyncPersistentMappedRange(); - parameterBuffer->SyncPersistentMappedRange(); - - const SizeT commandOffset = reinterpret_cast(indirect); - const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + - sizeof(DrawElementsIndirectCommand); - if (commandBytes > drawBuffer->GetSize()) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); - return; - } - if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); - return; - } - - // Both counts are read from the CPU shadow, which a buffer with no shadow does not - // have - MappedData() is null there and the reads below would be a null dereference, - // not a wrong picture. The DirectVulkan twin declines the same way. - if (parameterBuffer->MappedData() == nullptr || drawBuffer->MappedData() == nullptr) { - MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or " - "draw-indirect buffer"); - return; - } - - Uint32 actualDrawCount = 0; - std::memcpy(&actualDrawCount, parameterBuffer->MappedData() + drawcount, sizeof(actualDrawCount)); - actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); - ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBuffer->MappedData() + commandOffset, commandOffset, - drawBuffer, static_cast(actualDrawCount), stride, - "MultiDrawElementsIndirectCount"); } void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { @@ -9142,45 +8887,6 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(actualDrawCount), stride, "MultiDrawArraysIndirectCount"); return; - - auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - auto parameterBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); - if (!drawBuffer) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: no GL_DRAW_INDIRECT_BUFFER is bound"); - return; - } - if (!parameterBuffer) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: no GL_PARAMETER_BUFFER is bound"); - return; - } - - drawBuffer->SyncPersistentMappedRange(); - parameterBuffer->SyncPersistentMappedRange(); - - const SizeT commandOffset = reinterpret_cast(indirect); - const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(maxdrawcount - 1) + - sizeof(DrawArraysIndirectCommand); - if (commandBytes > drawBuffer->GetSize()) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); - return; - } - if (drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); - return; - } - - // See the indexed twin: no CPU shadow means no count to read, not a wrong one. - if (parameterBuffer->MappedData() == nullptr || drawBuffer->MappedData() == nullptr) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or " - "draw-indirect buffer"); - return; - } - - Uint32 actualDrawCount = 0; - std::memcpy(&actualDrawCount, parameterBuffer->MappedData() + drawcount, sizeof(actualDrawCount)); - actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); - ExecuteArraysIndirectCommands(mode, drawBuffer->MappedData() + commandOffset, commandOffset, drawBuffer, - static_cast(actualDrawCount), stride, "MultiDrawArraysIndirectCount"); } void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, @@ -10760,26 +10466,6 @@ namespace MobileGL::MG_Backend::DirectGLES { } backendObj->Bind(TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget), unit); return true; - - auto& textureUnit = MG_Pipe::gPipeInputs.GetTextureUnitObject(unit); - const auto& bindingSlot = textureUnit.GetBindingSlot(textureTarget); - { - const auto& textureObject = bindingSlot.GetBoundObject(); - if (!textureObject) { - MGLOG_D("%s: Texture target %s does not have texture bound.", __func__, - MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); - } - - auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get()); - auto& backendObj = backendTextureSlot - ? *backendTextureSlot - : TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject); - if (!backendObj) { - backendObj = MakeShared(); - } - backendObj->Bind(TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget), unit); - } - return true; } // ---- Scoped driver-state guards for the readback/copy/blit emulation paths -------------------- @@ -12465,52 +12151,6 @@ namespace MobileGL::MG_Backend::DirectGLES { } GenerateMipmapByRecord(target, mipRes, *record, backendTexture, driverWrittenBefore); return; - auto unitIndex = MG_Pipe::gPipeInputs.GetActiveTextureUnit(); - auto& unit = MG_Pipe::gPipeInputs.GetTextureUnitObject(unitIndex); - auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)); - auto& texture = slot.GetBoundObject(); - MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture."); - if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F || IsDepthOnlyFormat(texture->GetFormat()) || - texture->GetFormat() == TextureInternalFormat::RGB16F || - texture->GetFormat() == TextureInternalFormat::RGB32F) { - EnsureGenerateMipmapStorageAllocated(texture); - } - // Filtered on the CPU before the backend sync, so the dirty levels ride down with it. - if (GenerateThreeChannelFloatMipmapOnCpu(texture)) { - TextureImpl::SyncTextureObjectToBackend(texture); - return; - } - // Every path below writes the levels above the base on the driver only. - TextureImpl::NoteDriverSideTextureWrite(texture); - auto& backendTexture = TextureImpl::SyncTextureObjectToBackend(texture); - - // Asked for before the format arms below, which are 2D-only emulations of the same - // generation and reach a view just as well. - if (GenerateMipmapThroughViewWindowForTexture(texture)) return; - if (IsDepthOnlyFormat(texture->GetFormat())) { - GenerateDepthTexture2DMipmap(texture, backendTexture); - return; - } - if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F && - texture->GetTarget() == TextureTarget::Texture2D) { - GenerateColorTexture2DMipmap(texture, backendTexture); - return; - } - - const GLenum backendTarget = - TextureImpl::ConvertTextureTargetToBackendGLEnum(MG_Util::ConvertGLEnumToTextureTarget(target)); - backendTexture->Bind(backendTarget, unitIndex); - // ANGLE/Mesa may validate the currently bound FBO while generating mipmaps. - // Also detach the source texture from synced FBO objects for ANGLE's validation. - ScopedDetachedTextureFramebufferAttachments detachedAttachments(texture); - // Bind a complete internal FBO that does not reference the source texture. - ScopedCompleteFramebufferBinding completeFramebuffer; - // ErrorLopper is compiled out at the default log level; RecordGLError below - // forwards the next queued error to the APP, so stale flags from earlier - // best-effort calls must be drained by the always-live helper. - ClearGLErrors(); - g_GLESFuncs.glGenerateMipmap(backendTarget); - RecordGLError("glGenerateMipmap", backendTarget, texture->GetFormat()); } const GLubyte* GetString(GLenum name) { @@ -12719,61 +12359,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // integration-gpu lanes under MOBILEGL_TRANSPORT=monolith, where this mirror is on an // ordinary correct path and must still run. return; - // P4a (D-M) named this site `copy-image-shadow-mirror` with MGPipeUnmigratedEmulation. - // P8-SE removed the call: the return above means no transport reaches it, and on a - // monolith arm the call was a no-op (PipeCatalogueTest's list and the lint - // scripts/ci/unmigrated_emulation_sites.py follow). - - const auto srcUploadTarget = srcEndpoint.Texture->GetUploadTargets()[0]; - const auto dstUploadTarget = dstEndpoint.Texture->GetUploadTargets()[0]; - const IntVec3 srcSize = srcMipmap->GetMipmapTexelSize(srcUploadTarget, static_cast(srcLevel)); - const IntVec3 dstSize = dstMipmap->GetMipmapTexelSize(dstUploadTarget, static_cast(dstLevel)); - const SizeT srcSlices = static_cast(std::max(srcSize.z(), 1)); - const SizeT dstSlices = static_cast(std::max(dstSize.z(), 1)); - if (srcSize.x() <= 0 || srcSize.y() <= 0 || dstSize.x() <= 0 || dstSize.y() <= 0) return; - const SizeT srcTexels = static_cast(srcSize.x()) * static_cast(srcSize.y()) * srcSlices; - const SizeT dstTexels = static_cast(dstSize.x()) * static_cast(dstSize.y()) * dstSlices; - const SizeT srcBytes = srcMipmap->GetMipmapByteSize(srcUploadTarget, static_cast(srcLevel)); - const SizeT dstBytes = dstMipmap->GetMipmapByteSize(dstUploadTarget, static_cast(dstLevel)); - // A shadow that is not exactly texels x texelSize bytes is one this cannot index (a - // compressed blob, or a level whose allocation disagrees with its recorded extent). - const SizeT texelBytes = srcTexels == 0 ? 0 : srcBytes / srcTexels; - if (texelBytes == 0 || srcBytes != srcTexels * texelBytes || dstTexels == 0 || - dstBytes != dstTexels * texelBytes) { - return; - } - if (static_cast(srcX) + width > static_cast(srcSize.x()) || - static_cast(srcY) + height > static_cast(srcSize.y()) || - static_cast(srcZ) + depth > srcSlices || - static_cast(dstX) + width > static_cast(dstSize.x()) || - static_cast(dstY) + height > static_cast(dstSize.y()) || - static_cast(dstZ) + depth > dstSlices) { - return; - } - - const auto* srcBase = static_cast( - srcMipmap->MapMipmapData(srcUploadTarget, static_cast(srcLevel))); - auto* dstBase = static_cast(dstMipmap->MapMipmapData(dstUploadTarget, static_cast(dstLevel))); - if (!srcBase || !dstBase) return; - - const SizeT rowBytes = static_cast(width) * texelBytes; - for (GLsizei slice = 0; slice < depth; ++slice) { - for (GLsizei row = 0; row < height; ++row) { - const SizeT srcOffset = ((static_cast(srcZ + slice) * static_cast(srcSize.y()) + - static_cast(srcY + row)) * - static_cast(srcSize.x()) + - static_cast(srcX)) * - texelBytes; - const SizeT dstOffset = ((static_cast(dstZ + slice) * static_cast(dstSize.y()) + - static_cast(dstY + row)) * - static_cast(dstSize.x()) + - static_cast(dstX)) * - texelBytes; - Memcpy(dstBase + dstOffset, srcBase + srcOffset, rowBytes); - } - } - MGLOG_D("CopyImageSubData: mirrored %dx%dx%d texels into the destination's CPU shadow", width, height, - depth); } void CopyImageSubData(const CopyImageEndpoint& srcEndpoint, @@ -12924,8 +12509,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // barriered row. TextureImpl::NoteImageUnitBoundWithoutReadingTheFrontend(unit, texture != 0); return; - (void)texture; - TextureImpl::SyncImageTextureBinding(unit); } // Only the pnames MG_Impl/GLImpl/Getter/GL_Getter.cpp has no case for reach here. Every @@ -12972,25 +12555,6 @@ namespace MobileGL::MG_Backend::DirectGLES { storageBlockName, storageBlockBinding); } return; - if (!MG_Pipe::gPipeInputs.ValidateProgramName(program)) return; - auto& programObject = MG_Pipe::gPipeInputs.GetProgramObject(program); - if (!programObject) return; - - // 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* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(programObject.get()); - if (!backendProgramSlot || !*backendProgramSlot) return; - auto& backendObj = *backendProgramSlot; - // Not merely "a program id exists": a backend object whose synced link version has - // fallen behind is about to be rebuilt anyway, and its current driver interface is - // the PREVIOUS link's - applying to it could land the binding on an unrelated block. - if (!backendObj->GetBackendProgramId() || - backendObj->GetSyncedLinkVersion() != programObject->GetLinkVersion()) { - return; // SyncToBackend's reseed will carry it - } - PrgramImpl::ApplyShaderStorageBlockBinding(backendObj->GetBackendProgramId(), storageBlockName, - storageBlockBinding); } void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 33106e50..460a1de3 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3956,8 +3956,6 @@ namespace MobileGL::MG_Backend::DirectGLES { // 0xDD audit could not reach a draw. R-2 / table 3: honest inproc is inproc that // does not read the client object's memory. Under monolith nothing changes. return resource->hostBytes; - if (bufferObject) return bufferObject->MappedData(); - return resource->hostBytes; }; if (resource->contextGeneration != g_bufferContextGeneration) { @@ -11216,11 +11214,6 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool IsAlphaWidenedFallbackReadAttachment() { const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); return record && IsAlphaWidenedColorSurface(record->ReadSurface); - const auto* attachmentObject = GetReadColorAttachment(); - if (attachmentObject == nullptr) { - return false; - } - return IsAlphaWidenedColorAttachment(*attachmentObject); } Bool IsFixedPointFallbackReadAttachment() { @@ -12871,7 +12864,6 @@ namespace MobileGL::MG_Backend::DirectGLES { const auto& st = MG_Pipe::MGPipeApplier(); if (static_cast(unit) >= st.BoundShaderImages.size()) return 0; return static_cast(st.BoundShaderImages[static_cast(unit)].InternalFormat); - return static_cast(MG_Pipe::gPipeInputs.GetImageTextureBinding(unit).Format); } // Combines one (unit, format) pair into a running digest. Commutative, so the order diff --git a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp index 174fe0df..aa2a33e0 100644 --- a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp +++ b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp @@ -296,12 +296,6 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { RefuseMissingIndirectBufferRecord("BoundDrawIndirectBufferId", res); } return resource->id; - // MONOLITH GLUE from here down, token for token what this function did before. - const auto& indirect = - MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - if (!indirect) return 0; - const auto* resource = BufferImpl::EnsureBufferResource(indirect); - return resource ? resource->id : 0; } // --------------------------------------------------------------------------- diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 8f1327b1..36a19e1c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -395,105 +395,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { DrawWireIndirect(mode, type, indirect, drawcount, stride, true); return; - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirect called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElementsIndirect called with null GL context"); - pVulkanRenderer->MultiDrawElementsIndirect(mode, type, indirect, drawcount, stride); } void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { DrawWireIndirect(mode, 0, indirect, drawcount, stride, false); return; - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirect called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawArraysIndirect called with null GL context"); - - if (drawcount <= 0) { - return; - } - - // With a bound GL_DRAW_INDIRECT_BUFFER the command parameters may be GPU-written - // (e.g. by a compute shader), so consume them natively on the GPU. - auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - if (drawBuffer) { - pVulkanRenderer->MultiDrawArraysIndirect(mode, indirect, drawcount, stride); - return; - } - - if (stride == 0) { - stride = sizeof(DrawArraysIndirectCommand); - } - if (stride < static_cast(sizeof(DrawArraysIndirectCommand))) { - MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", - stride, sizeof(DrawArraysIndirectCommand)); - return; - } - - // No indirect buffer bound: the pointer refers to client memory. - const auto* commandBytes = ResolveIndirectCommandBytes( - indirect, - static_cast(stride) * static_cast(drawcount - 1) + sizeof(DrawArraysIndirectCommand), - "MultiDrawArraysIndirect"); - if (!commandBytes) { - return; - } - - for (GLsizei i = 0; i < drawcount; ++i) { - DrawArraysIndirectCommand cmd{}; - std::memcpy(&cmd, commandBytes + static_cast(i) * stride, sizeof(cmd)); - if (cmd.count == 0 || cmd.instanceCount == 0) { - continue; - } - - DrawCmd payload{}; - payload.mode = mode; - payload.params.vertexCount = cmd.count; - payload.params.instanceCount = cmd.instanceCount; - payload.params.firstVertex = cmd.first; - payload.params.firstInstance = cmd.baseInstance; - pVulkanRenderer->DrawArrays(payload); - } } void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { DrawWireIndirect(mode, type, indirect, maxdrawcount, stride, true, true, drawcount); return; - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsIndirectCount called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawElementsIndirectCount called with null GL context"); - pVulkanRenderer->MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride); } void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { DrawWireIndirect(mode, 0, indirect, maxdrawcount, stride, false, true, drawcount); return; - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawArraysIndirectCount called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::MultiDrawArraysIndirectCount called with null GL context"); - - if (maxdrawcount <= 0) { - return; - } - if (stride == 0) { - stride = sizeof(DrawArraysIndirectCommand); - } - if (stride < static_cast(sizeof(DrawArraysIndirectCommand))) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu", - stride, sizeof(DrawArraysIndirectCommand)); - return; - } - - auto parameterBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); - if (!parameterBuffer || drawcount < 0 || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range"); - return; - } - - parameterBuffer->SyncPersistentMappedRange(); - if (parameterBuffer->MappedData() == nullptr) { - MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read parameter buffer"); - return; - } - - Uint32 actualDrawCount = 0; - std::memcpy(&actualDrawCount, parameterBuffer->MappedData() + drawcount, sizeof(actualDrawCount)); - actualDrawCount = std::min(actualDrawCount, static_cast(maxdrawcount)); - MultiDrawArraysIndirect(mode, indirect, static_cast(actualDrawCount), stride); } void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex) { @@ -537,47 +452,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) { DrawWireIndirect(mode, type, indirect, 1, 0, true); return; - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsIndirect called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawElementsIndirect called with null GL context"); - - const SizeT indexSize = MG_Util::GetGLTypeSize(type); - if (indexSize == 0) { - MGLOG_E_ONCE("DrawElementsIndirect skipped: unsupported index type 0x%x", type); - return; - } - - // With a bound GL_DRAW_INDIRECT_BUFFER the command parameters may be GPU-written - // (e.g. by a compute shader), so consume them natively on the GPU. - auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - if (drawBuffer) { - pVulkanRenderer->MultiDrawElementsIndirect(mode, type, indirect, 1, 0); - return; - } - - // No indirect buffer bound: the pointer refers to client memory. - const auto* commandBytes = - ResolveIndirectCommandBytes(indirect, sizeof(DrawElementsIndirectCommand), "DrawElementsIndirect"); - if (!commandBytes) { - return; - } - - DrawElementsIndirectCommand cmd{}; - std::memcpy(&cmd, commandBytes, sizeof(cmd)); - if (cmd.count == 0 || cmd.instanceCount == 0) { - return; - } - - DrawIndexedCmd payload{}; - payload.mode = mode; - payload.indexBufferView.indexType = type; - payload.indexBufferView.indexByteOffset = static_cast(cmd.firstIndex) * indexSize; - payload.indexBufferView.indexByteSize = static_cast(cmd.count) * indexSize; - payload.params.indexCount = cmd.count; - payload.params.instanceCount = cmd.instanceCount; - payload.params.firstIndex = 0; - payload.params.vertexOffset = cmd.baseVertex; - payload.params.firstInstance = static_cast(cmd.baseInstance); - pVulkanRenderer->DrawElements(payload); } void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance) { @@ -598,37 +472,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DrawArraysIndirect(GLenum mode, const void* indirect) { DrawWireIndirect(mode, 0, indirect, 1, 0, false); return; - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArraysIndirect called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DrawArraysIndirect called with null GL context"); - - // With a bound GL_DRAW_INDIRECT_BUFFER the command parameters may be GPU-written - // (e.g. by a compute shader), so consume them natively on the GPU. - auto drawBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); - if (drawBuffer) { - pVulkanRenderer->MultiDrawArraysIndirect(mode, indirect, 1, 0); - return; - } - - // No indirect buffer bound: the pointer refers to client memory. - const auto* commandBytes = - ResolveIndirectCommandBytes(indirect, sizeof(DrawArraysIndirectCommand), "DrawArraysIndirect"); - if (!commandBytes) { - return; - } - - DrawArraysIndirectCommand cmd{}; - std::memcpy(&cmd, commandBytes, sizeof(cmd)); - if (cmd.count == 0 || cmd.instanceCount == 0) { - return; - } - - DrawCmd payload{}; - payload.mode = mode; - payload.params.vertexCount = cmd.count; - payload.params.instanceCount = cmd.instanceCount; - payload.params.firstVertex = cmd.first; - payload.params.firstInstance = cmd.baseInstance; - pVulkanRenderer->DrawArrays(payload); } void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { @@ -672,9 +515,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // that was here (ReadWireBuffer, a whole-GPU wait once a shader had written the store) is gone. pVulkanRenderer->DispatchComputeIndirect(indirect); return; - MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DispatchComputeIndirect called with null VulkanRenderer"); - MOBILEGL_ASSERT(MG_Pipe::gPipeInputs.IsLive(), "DirectVulkan::DispatchComputeIndirect called with null GL context"); - pVulkanRenderer->DispatchComputeIndirect(indirect); } void MemoryBarrier(GLbitfield barriers) { @@ -748,31 +588,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { else found->Binding = static_cast(storageBlockBinding); ++record.BindingsSerial; return; - auto* programObject = TryGetDirectVulkanProgram(program); - if (!programObject || storageBlockName == nullptr) return; - const Int maxBindings = -#if MOBILEGL_BUILD_DISAGGREGATED - // P5c (hd, CONTRACT-P5C §3.7): with an active transport the dynamic parameters are - // the SERVER's own backend's - the client caps mirror (pActiveBackendObject) is - // client memory the apply thread may not name (rule E). Monolith reads the mirror - // as it always did. - MG_Config::Transport != MG_Config::TransportMode::Monolith - ? (MG_Remote::Server::ServerLoopInstance().Backend() != nullptr - ? static_cast(MG_Remote::Server::ServerLoopInstance().Backend() - ->GetDynamicParameters() - .MaxShaderStorageBufferBindings) - : 0) - : -#endif - pActiveBackendObject - ? pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings - : 0; - if (storageBlockBinding >= static_cast(maxBindings)) { - MG_Pipe::gPipeInputs.RecordError( - ErrorCode::InvalidValue, - MakeUnique("DirectVulkan", __func__, "Shader storage binding is out of range.")); - return; - } // P7 wave 2 package C, OQ-8: NOTHING LEFT TO PATCH, and the hazard goes with it. // // The frontend has already recorded the new binding on the program, and that record is @@ -854,38 +669,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { } outIndices[count] = outIndices[0]; return true; - const SizeT indexSize = MG_Util::GetGLTypeSize(type); - if (indexSize == 0 || count < 2) { - return false; - } - const Uint8* indexBytes = nullptr; - const auto& vao = *MG_Pipe::gPipeInputs.GetBoundVertexArray(); - const auto& indexBufferShared = vao.GetIndexBufferBindingSlot().GetBoundObject(); - if (indexBufferShared != nullptr) { - const SizeT offset = reinterpret_cast(indices); - const SizeT bufferSize = indexBufferShared->GetSize(); - if (indexBufferShared->MappedData() == nullptr || offset > bufferSize || - static_cast(count) * indexSize > bufferSize - offset) { - return false; - } - indexBufferShared->SyncPersistentMappedRange(); - indexBytes = indexBufferShared->MappedData() + offset; - } else { - indexBytes = static_cast(indices); - if (indexBytes == nullptr) { - return false; - } - } - outIndices.resize(static_cast(count) + 1); - for (GLsizei i = 0; i < count; ++i) { - switch (indexSize) { - case 1: outIndices[i] = indexBytes[i]; break; - case 2: outIndices[i] = reinterpret_cast(indexBytes)[i]; break; - default: outIndices[i] = reinterpret_cast(indexBytes)[i]; break; - } - } - outIndices[count] = outIndices[0]; - return true; } void DrawArrays(GLenum mode, GLint first, GLsizei count) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index 41408c64..76836293 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -807,8 +807,5 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool supported = (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; m_wireVertexFormatSupport.emplace(format, supported); return supported; - VkFormatProperties properties{}; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties); - return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index f6083eef..c88c22b8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -5884,9 +5884,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { [&] { const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); return isDefaultFbo == (fbo != nullptr && fbo->IsDefault); - const auto& fbo = - MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - return isDefaultFbo == (fbo != nullptr && fbo->IsDefaultFramebuffer()); }(), "GetBaseTransformFlagsRaw: isDefaultFbo does not match the bound draw framebuffer"); const VkSurfaceTransformFlagBitsKHR preTransform = m_swapchainObject.GetPreTransform(); @@ -6315,626 +6312,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // until the next frame boundary, which a long trace may not reach. m_textureManager->CollectGarbage(); return SetupWireDraw(frame, mode, aspects, drawParams, pIndexBufferView); - // Sync each sampled texture at most once across this whole draw: the layout - // probe loop, the post-transition loop, and ResolveSamplerDescriptor would - // otherwise each re-run the full SyncTexture path on the same textures. - MakeXfbWritesVisible(); - VkTextureManager::DrawSyncScope drawSyncScope(*m_textureManager); - m_textureManager->CollectGarbage(); - { - // Mirror DirectGLES's SyncToBackend gate: a program whose phase-B job failed or - // was cancelled has no usable optimized module - and on an in-place - // SanitizeAndOptimizeBinary failure GetGeneratedSpirv() still holds the RAW - // glslang words, which must never reach vkCreateShaderModule. Drop the draw. - const auto& drawProgram = *MG_Pipe::gPipeInputs.GetProgramForDraw(); - if (!drawProgram.GetLinkStatus() || !drawProgram.GetSpirvStatus()) { - MGLOG_D("SetupDraw skipped: program=%u is linked=%d spirv=%d", - drawProgram.GetExternalIndex(), static_cast(drawProgram.GetLinkStatus()), - static_cast(drawProgram.GetSpirvStatus())); - return false; - } - } - if (MG_Util::PipeStats::Enabled()) { - // THE per-draw denominator for Magma, plus the draw-program read above. Placed - // here rather than inside TrySetupDrawFastPath because the fast path has 27 - // decline returns and one success return: counting the gate from the caller is - // the only shape that cannot miss one. - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::Draws, 1); - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::AccessorCalls, 1); - } - if (TrySetupDrawFastPath(frame, mode, aspects, drawParams, pIndexBufferView)) { - if (MG_Util::PipeStats::Enabled()) { - MG_Util::PipeStats::CountGate(MG_Util::PipeStats::Gate::MagmaDrawFastPath, /*hit=*/true); - } - return true; - } - if (MG_Util::PipeStats::Enabled()) { - MG_Util::PipeStats::CountGate(MG_Util::PipeStats::Gate::MagmaDrawFastPath, /*hit=*/false); - // The three reads the full path makes immediately below (draw FBO, VAO, - // program). The accessor reads the declined fast path had already made before - // it turned back are NOT counted. - MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::AccessorCalls, 3); - } - const auto& drawFbo = - MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - if (drawFbo != nullptr && IsUnsupportedFramebufferForDirectVulkan(*drawFbo)) { - // Nothing was mutated: other entries' per-probe guards (FBO identity + - // version among them) stay authoritative, so none need invalidating. - RecordUnsupportedFramebufferError(__func__); - return false; - } - const auto& vao = *MG_Pipe::gPipeInputs.GetBoundVertexArray(); - const auto& program = *MG_Pipe::gPipeInputs.GetProgramForDraw(); - // The fast path declined (or had no entry for this program): whatever THIS - // program's entry saw may be stale, and the full path below mutates state as - // it goes, so the entry must not stay matchable if that path fails mid-way. - // Select it now - the program's own entry when one exists, else an invalid - // slot, else a round-robin victim - and invalidate it until the successful - // refill at the end. Other programs' entries keep their validity: every fact - // they carry is re-guarded per probe (live pass hash, epochs, versions, - // sums), so a full path run in between can only make them decline. - SetupDrawSnapshot* fillSnap = nullptr; - { - Uint32 fillIndex = kSetupDrawSnapshotCount; - const Uint64 fillProgramLifetimeId = program.GetLifetimeId(); - for (Uint32 i = 0; i < kSetupDrawSnapshotCount; ++i) { - if (m_setupDrawSnapshots[i].valid && - m_setupDrawSnapshots[i].programLifetimeId == fillProgramLifetimeId) { - fillIndex = i; - break; - } - } - if (fillIndex == kSetupDrawSnapshotCount) { - for (Uint32 i = 0; i < kSetupDrawSnapshotCount; ++i) { - if (!m_setupDrawSnapshots[i].valid) { - fillIndex = i; - break; - } - } - } - if (fillIndex == kSetupDrawSnapshotCount) { - fillIndex = m_setupDrawSnapshotVictim; - m_setupDrawSnapshotVictim = (m_setupDrawSnapshotVictim + 1) % kSetupDrawSnapshotCount; - } - fillSnap = &m_setupDrawSnapshots[fillIndex]; - fillSnap->valid = false; - m_setupDrawSnapshotMru = fillIndex; - } - const Bool drawFboIsDefault = drawFbo != nullptr && drawFbo->IsDefaultFramebuffer(); - ProgramFactory::CompileOptionFlags transformFlags = - ProgramFactory::CompileOptionFlags(GetBaseTransformFlagsRaw(drawFboIsDefault)); - // Captured draws take the xfb-decorated program variant. - if (m_transformFeedbackFeatureEnabled && MG_Pipe::gPipeInputs.IsTransformFeedbackActive() && - program.GetTransformFeedbackVaryingCount() > 0) { - transformFlags |= ProgramFactory::CompileOptionBit::XfbCapture; - } - // Sampling a colour render target through the driver's implicit-LOD path faults the GPU on - // Adreno 650 (see ForceExplicitLod0SamplePass); ask for the explicit-LOD variant when doing - // so cannot change a texel, i.e. when every sampler this program reads is pinned to a - // single mip level. The probe walks every sampler binding, so its verdict is memoized - // under the sampled-set memo's key plus the sampled textures' params-version sum (level - // range and filter changes live there); the previous draw's texture list is valid for the - // sum exactly when that key matches (same program, same binds). - { - const Uint64 lodProgramLifetimeId = program.GetLifetimeId(); - const Uint32 lodProgramVersion = program.GetBackendStateVersion(); - const Uint64 lodBindGeneration = MG_Pipe::gPipeInputs.GetTextureBindGeneration(); - // The probe also reads the EFFECTIVE sampler's filters/aniso/LOD range - // (ProgramSamplesOnlySingleLevelTextures), and those setters bump ONLY the - // sampling-resolution generation - not the texture params version the sum - // below covers. Without this key a filter/aniso change would keep serving - // the stale verdict. - const Uint64 lodSamplingGeneration = MG_Pipe::gPipeInputs.GetSamplingResolutionGeneration(); - Bool lodMemoHit = false; - if (m_lastLodDecisionValid && m_lastSampledSetValid && - m_lastLodProgramLifetimeId == lodProgramLifetimeId && - m_lastLodProgramVersion == lodProgramVersion && - m_lastLodBindGeneration == lodBindGeneration && - m_lastLodSamplingGeneration == lodSamplingGeneration && m_lastLodBaseFlags == transformFlags && - m_lastSampledSetProgramLifetimeId == lodProgramLifetimeId && - m_lastSampledSetProgramVersion == lodProgramVersion && - m_lastSampledSetBindGeneration == lodBindGeneration) { - Uint64 paramsSum = 0; - for (const auto* sampledTexture : m_sampledTexturesScratch) { - if (sampledTexture != nullptr) { - paramsSum += sampledTexture->GetTextureParamsVersion(); - } - } - if (paramsSum == m_lastLodParamsSum) { - transformFlags = m_lastLodResultFlags; - lodMemoHit = true; - } - } - if (!lodMemoHit) { - const ProgramFactory::CompileOptionFlags baseFlags = transformFlags; - const auto& baseProgramObj = m_programFactory->GetOrCreateProgram(program, transformFlags); - if (UniformManager::ProgramSamplesOnlySingleLevelTextures(program, baseProgramObj)) { - transformFlags |= ProgramFactory::CompileOptionBit::ExplicitLod0Sampling; - } - m_lastLodDecisionValid = true; - m_lastLodProgramLifetimeId = lodProgramLifetimeId; - m_lastLodProgramVersion = lodProgramVersion; - m_lastLodBindGeneration = lodBindGeneration; - m_lastLodSamplingGeneration = lodSamplingGeneration; - m_lastLodBaseFlags = baseFlags; - m_lastLodResultFlags = transformFlags; - m_lastLodParamsSum = 0; // filled below once the sampled set is known - } - } - // GL's gl_BaseVertex is zero for every command without a baseVertex parameter, while - // Vulkan's builtin reports the draw's firstVertex; a non-indexed draw therefore takes - // the zeroed program variant. The question is about the program's SPIR-V, not about - // this draw, so it is memoized on (program lifetime, backend-state version): only the - // very first draw of a program pays the extra lookup, and a program used exclusively - // with non-indexed draws never resolves - never compiles, never re-stamps - the - // variant no draw of it would use. - const Bool nonIndexedDraw = !(aspects & DrawSetupAspect::IndexBuffer); - const Uint64 baseVertexProgramLifetimeId = program.GetLifetimeId(); - const Uint32 baseVertexProgramVersion = program.GetBackendStateVersion(); - const Bool baseVertexQueryKnown = m_lastBaseVertexQueryValid && - m_lastBaseVertexProgramLifetimeId == baseVertexProgramLifetimeId && - m_lastBaseVertexProgramVersion == baseVertexProgramVersion; - if (baseVertexQueryKnown && nonIndexedDraw && m_lastBaseVertexReads) { - transformFlags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex; - } - const ProgramFactory::VkProgramObject* resolvedProgramObj = - &m_programFactory->GetOrCreateProgram(program, transformFlags); - if (!baseVertexQueryKnown) { - // Read the answer out of the entry BEFORE any second lookup: that lookup may - // insert and move every entry of the open-addressing cache, dangling the - // reference. The zeroing pass leaves the variable declared, so the variant just - // resolved answers the same as the base one either way. - const Bool readsBaseVertex = resolvedProgramObj->readsBaseVertexBuiltin; - m_lastBaseVertexQueryValid = true; - m_lastBaseVertexProgramLifetimeId = baseVertexProgramLifetimeId; - m_lastBaseVertexProgramVersion = baseVertexProgramVersion; - m_lastBaseVertexReads = readsBaseVertex; - if (nonIndexedDraw && readsBaseVertex) { - transformFlags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex; - resolvedProgramObj = &m_programFactory->GetOrCreateProgram(program, transformFlags); - } - } - const auto& programObj = *resolvedProgramObj; - // Pinned for BeginXfbCaptureForDraw, which otherwise decides from GL state alone and has - // no way to know the bound pipeline's last pre-rasterization module lost (or never got) - // its Xfb execution mode. See VkProgramObject::xfbCaptureDeclined. - m_currentDrawXfbCaptureDeclined = programObj.xfbCaptureDeclined; - // The build already said why, once, naming the program and the stage. Refusing here - - // before any pipeline is built from it - is what makes that message a decline rather - // than a note attached to invalid usage the driver still receives. - if (programObj.pointSizeCapabilityUnsupported || programObj.invalidSpirvDeclined) { - return false; - } - // For the snapshot's memoised entry pointer: if anything below inserts into the - // program cache (blit/aux program compiles), the epoch moves and the snapshot - // stores no pointer for this draw - the fast path then re-looks-up once. - const Uint64 programFactoryEpochAtResolve = m_programFactory->GetCacheStructureEpoch(); - - // Begin command recording if not yet - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - // New command buffer: a program/FBO address from a previous frame may have been - // recycled, so start the sampled-set skip cache fresh this frame. - m_lastSampledSetValid = false; - } - - if (!PrepareStorageImageTextures(frame, program, programObj)) { - MGLOG_E_ONCE("SetupDraw skipped: storage image preparation failed"); - return false; - } - if (!PrepareSamplerImageFeedbackSnapshots(frame, program, programObj, - VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT)) { - MGLOG_E_ONCE("SetupDraw skipped: sampler/image feedback snapshot failed"); - return false; - } - - auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - - // Check if any of the textures to sample have pending clears, - // which probably indicates it's been gone through codepath like `fbo attach` -> `clear` -> `fbo detach`, and - // without draws in between to give it a chance to materialize such clear. - // Deal with this situation here. - // Reuse the previous draw's sampled-texture list when the set is provably unchanged (same - // program+state+transform and no bind/unbind/delete since), skipping the per-draw GL walk. - // The layout/feedback/transition loops below still run on the list every draw, so this only - // elides re-resolving *which* textures are sampled, never their layout handling. - auto& sampledTextures = m_sampledTexturesScratch; - { - const Uint64 programLifetimeId = program.GetLifetimeId(); - const Uint32 programVersion = program.GetBackendStateVersion(); - const Uint64 bindGeneration = MG_Pipe::gPipeInputs.GetTextureBindGeneration(); - // The bind generation alone stopped covering this set the moment ResolveSampledBinding - // started asking SamplesAsIncompleteTexture: membership now depends on the effective - // sampler PARAMETERS (MIN_FILTER decides whether the mip chain is read at all) and on - // the texture SHAPE, and neither moves the bind generation. A texture that flips - // incomplete -> complete under a fixed binding - one glTexParameteri, one - // glSamplerParameteri, a BASE_LEVEL/MAX_LEVEL change, or an upload that fills the - // chain - would keep replaying the FALLBACK out of this memo, so the real texture - // never got its pre-pass sync, its pending-clear materialisation or its sampled-layout - // transition, and the descriptor path would then transition it from INSIDE the open - // render pass, which the subpass declares no self-dependency for. - // - // The sampling-resolution generation is exactly the counter for that family and is - // deliberately coarse (any texture, any sampler), so this one term covers every input - // the predicate reads that the bind generation does not: TextureObjectBase:: - // BumpShapeVersion and SamplerObject::BumpVersion both bump it, while WHICH sampler - // object a unit carries goes through TextureUnit::SetSamplerObject and moves the bind - // generation instead. Same term the SetupDrawSnapshot fast path and the LOD memo - // already carry. - const Uint64 samplingGeneration = MG_Pipe::gPipeInputs.GetSamplingResolutionGeneration(); - const Bool sampledSetUnchanged = - m_lastSampledSetValid && m_lastSampledSetProgramLifetimeId == programLifetimeId && - m_lastSampledSetProgramVersion == programVersion && - m_lastSampledSetTransformFlags == transformFlags && - m_lastSampledSetBindGeneration == bindGeneration && - m_lastSampledSetSamplingGeneration == samplingGeneration; - if (!sampledSetUnchanged) { - const Bool hasSampledTextures = m_uniformManager->CollectSampledTextures( - program, programObj, sampledTextures, &m_sampledBindingRecordsScratch); - MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__); - m_lastSampledSetValid = true; - m_lastSampledSetProgramLifetimeId = programLifetimeId; - m_lastSampledSetProgramVersion = programVersion; - m_lastSampledSetTransformFlags = transformFlags; - m_lastSampledSetBindGeneration = bindGeneration; - m_lastSampledSetSamplingGeneration = samplingGeneration; - } - // Complete a freshly-made LOD decision (see above): its params sum - // can only be taken once the sampled set is known. A genuine - // all-zero sum merely re-probes next draw. - if (m_lastLodDecisionValid && m_lastLodParamsSum == 0) { - Uint64 paramsSum = 0; - for (const auto* sampledTexture : sampledTextures) { - if (sampledTexture != nullptr) { - paramsSum += sampledTexture->GetTextureParamsVersion(); - } - } - m_lastLodParamsSum = paramsSum; - } - } - MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s", - program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(), - activeRenderPass ? "true" : "false"); - Bool activeRenderPassUsesSampledTexture = false; - if (activeRenderPass != nullptr) { - for (auto* sampledTexture : sampledTextures) { - if (sampledTexture == nullptr) { - continue; - } - if (ActiveRenderPassUsesTexture(*activeRenderPass, *sampledTexture)) { - MGLOG_D("SetupDraw: active render pass is still using sampled textureId=%d; ending render pass before descriptor preparation", - sampledTexture->GetExternalIndex()); - activeRenderPassUsesSampledTexture = true; - break; - } - } - } - if (activeRenderPassUsesSampledTexture) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - activeRenderPass = nullptr; - } - // The syncs below upload each sampled texture's pending texels into a batch that runs - // ahead of this recording; one whose image the recording already cleared or drew into - // would be overwritten by that earlier work. Submit it first, here, before anything below - // records into the frame or decides from the active pass. - for (auto* sampledTexture : sampledTextures) { - if (sampledTexture != nullptr && OrderPendingUploadAfterRecording(frame, *sampledTexture)) { - activeRenderPass = nullptr; - } - } - Bool needSampledTextureTransitions = false; - auto& sampledResources = m_sampledResourcesScratch; - sampledResources.assign(sampledTextures.size(), nullptr); - for (SizeT sampledIndex = 0; sampledIndex < sampledTextures.size(); ++sampledIndex) { - auto* sampledTexture = sampledTextures[sampledIndex]; - if (!sampledTexture) { - continue; - } - - auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*sampledTexture); - if (textureResource == nullptr) { - // SyncTextureAndGetDescriptor has a real failure channel - an incomplete or - // otherwise unbackable texture declines and returns nullptr with its own log - // line - and the assert that used to be the only guard here is compiled out of - // every build past DEBUG. The next line dereferenced it, so a sampler left - // pointing at a texture GL calls incomplete was a SIGSEGV inside SetupDraw - // rather than a degraded draw. Leave the slot null and carry on: the descriptor - // resolve substitutes the fallback texture for exactly these bindings - // (ResolveSamplerDescriptor's SamplesAsIncompleteTexture branch), and the fast - // path at the top of SetupDraw already treats a null resource as "re-resolve". - MGLOG_E_ONCE("SetupDraw: no texture resource for sampled textureId=%d; leaving the binding to the " - "descriptor resolve's fallback", - sampledTexture->GetExternalIndex()); - continue; - } - sampledResources[sampledIndex] = textureResource; - MGLOG_D("SetupDraw: sampled textureId=%d layout(before)=%s(%d)", - sampledTexture->GetExternalIndex(), VkImageLayoutToString(textureResource->layout), - static_cast(textureResource->layout)); - if (m_clearManager->HasPendingClear(sampledTexture) || - !IsValidSampledImageLayout(textureResource->layout)) { - // Out-of-pass work is needed (deferred clear materialization or - // a sampled-layout transition). When the open frame recording - // has not referenced this image yet, that work can execute - // ahead of the WHOLE recording - record it into the pre-pass - // stream instead of splitting the active render pass (ANGLE's - // outside-render-pass command stream, restricted to the - // provably reorderable case). - if (activeRenderPass != nullptr && - !m_frameContext.GetCurrent().hasPreCommandBufferRecorded && - !m_textureManager->WasTouchedThisRecording(*textureResource)) { - VkCommandBuffer preCommandBuffer = m_frameContext.BeginPreCommandRecording(); - const Bool preClearReady = - MaterializePendingClearForTexture(preCommandBuffer, *sampledTexture); - MOBILEGL_ASSERT(preClearReady, - "%s: pre-pass MaterializePendingClearForTexture failed for textureId=%d", - __func__, sampledTexture->GetExternalIndex()); - const Bool preTransitionReady = - m_textureManager->TransitionTextureForSampling(preCommandBuffer, *sampledTexture); - MOBILEGL_ASSERT(preTransitionReady, - "%s: pre-pass TransitionTextureForSampling failed for textureId=%d", - __func__, sampledTexture->GetExternalIndex()); - continue; - } - needSampledTextureTransitions = true; - } - } - - if (activeRenderPass && needSampledTextureTransitions) { - MGLOG_D("SetupDraw: ending active render pass before sampled texture transitions"); - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - activeRenderPass = nullptr; - } - - for (SizeT sampledIndex = 0; sampledIndex < sampledTextures.size(); ++sampledIndex) { - auto* sampledTexture = sampledTextures[sampledIndex]; - if (!sampledTexture) { - continue; - } - // Fast path: the first loop already resolved this texture, nothing - // is pending against it, and its layout is still sampleable (the - // layout re-check covers an EndRenderPass between the loops having - // rewritten an attachment's layout). Skipping the materialize + - // transition + re-resolve chain here is the difference between one - // pointer read and three calls per sampled texture per draw. - if (auto* fastResource = sampledResources[sampledIndex]; - fastResource != nullptr && !m_clearManager->HasPendingClear(sampledTexture) && - IsValidSampledImageLayout(fastResource->layout)) { - m_textureManager->StampResourceRecordingUse(*fastResource); - continue; - } - const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sampledTexture); - MOBILEGL_ASSERT(clearReady, "%s: MaterializePendingClearForTexture failed for textureId=%d", - __func__, sampledTexture->GetExternalIndex()); - const Bool ready = m_textureManager->TransitionTextureForSampling(frame.commandBuffer, *sampledTexture); - MOBILEGL_ASSERT(ready, "%s: TransitionTextureForSampling failed for textureId=%d", - __func__, sampledTexture->GetExternalIndex()); - auto* transitionedResource = m_textureManager->SyncTextureAndGetDescriptor(*sampledTexture); - if (transitionedResource == nullptr) { - // Same declined-sync channel as the first loop, and the same reason not to - // dereference it: StampResourceRecordingUse below takes a reference. - MGLOG_E_ONCE("SetupDraw: no texture resource after transitioning sampled textureId=%d; leaving the " - "binding to the descriptor resolve's fallback", - sampledTexture->GetExternalIndex()); - continue; - } - // Pre-pass stream bookkeeping: the draw about to be recorded reads - // this image, so later out-of-pass work on it can no longer jump - // ahead of the recording. - m_textureManager->StampResourceRecordingUse(*transitionedResource); - MGLOG_D("SetupDraw: sampled textureId=%d layout(after)=%s(%d)", - sampledTexture->GetExternalIndex(), VkImageLayoutToString(transitionedResource->layout), - static_cast(transitionedResource->layout)); - } - - // Depth/stencil participation of THIS draw, for the default-FBO depth-less - // pass flavor (GL: a disabled depth/stencil test neither reads nor writes - // its buffer). - const Bool drawUsesDepthStencil = - MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::DepthTest) || - MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::StencilTest); - auto* renderPassEntry = m_renderPassManager->GetOrCreateRenderPass( - *drawFbo, DefaultFramebufferWriteIndex(drawFbo->IsDefaultFramebuffer()), drawUsesDepthStencil); - // nullptr: the framebuffer has an attachment DirectVulkan cannot represent (a texture the - // texture manager declined to back, or a view it could not build). The builder logged which - // one; drop the draw here, exactly as an unresolvable sampler descriptor drops one in - // BindProgramUniformBuffers. Before this existed the same condition dereferenced a null - // resource or handed VK_NULL_HANDLE to vkCreateFramebuffer and took the process down. - if (renderPassEntry == nullptr) { - return false; - } - if (activeRenderPass && !activeRenderPass->CompatibleWith(*renderPassEntry)) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - activeRenderPass = nullptr; - renderPassEntry = m_renderPassManager->GetOrCreateRenderPass( - *drawFbo, DefaultFramebufferWriteIndex(drawFbo->IsDefaultFramebuffer()), drawUsesDepthStencil); - if (renderPassEntry == nullptr) { - return false; - } - } - if (renderPassEntry->attachmentCount == 0 || renderPassEntry->extent.x() <= 0 || renderPassEntry->extent.y() <= 0) { - MGLOG_D("SetupDraw skipped: drawFbo=%u resolved to an empty render pass (attachmentCount=%u extent=%dx%d)", - drawFbo->GetExternalIndex(), - renderPassEntry->attachmentCount, - renderPassEntry->extent.x(), - renderPassEntry->extent.y()); - return false; - } - - // Vertex-input pre-flight, run before pipeline creation so that a bad attribute can never be - // baked into a cached VkPipeline. - { - const auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); - const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask; - - // An enabled array whose GL type has no VkFormat mapping never reaches the vertex input - // state, which makes it indistinguishable from a disabled array: the draw would treat it as - // "missing" and silently feed the shader the current attribute value instead of the app's - // vertex data. Fail loudly rather than render wrong pixels. - const Uint32 brokenAttribMask = vertexInputState.unsupportedAttribMask & activeAttribMask; - if (brokenAttribMask != 0) { - MGLOG_E_ONCE("SetupDraw skipped: program=%u reads vertex attribute location mask 0x%x whose enabled " - "array has no supported vertex format", - program.GetExternalIndex(), brokenAttribMask); - return false; - } - - // Every genuinely disabled attribute the shader reads must have a current-value type we can - // synthesize a binding for; otherwise the upload below would push a null payload. - const Uint32 missingAttribMask = - activeAttribMask & ~vertexInputState.attributeLocationMask; - for (Uint32 location = 0; location < kMaxVertexAttribs; ++location) { - if ((missingAttribMask & (1u << location)) == 0) continue; - - const GLenum glType = programObj.vertexInputTypes[location]; - if (MG_State::GLState::ClassifyVertexAttribType(glType).baseType == - MG_State::GLState::VertexAttribBaseType::Unsupported) { - MGLOG_E_ONCE("SetupDraw skipped: program=%u location=%u has no enabled array and its shader input " - "type 0x%x is not supported as a current generic vertex attribute", - program.GetExternalIndex(), location, glType); - return false; - } - } - } - - auto pipeline = GetOrCreatePipeline(mode, program, programObj, transformFlags, vao, *renderPassEntry, - ResolvePrimitiveRestartEnable(aspects, pIndexBufferView)); - // GetOrCreatePipeline documents a VK_NULL_HANDLE return (empty stages, or a driver that - // rejected vkCreateGraphicsPipelines). Binding it dereferences null inside the driver - - // 9 of the 15 CTS process deaths were exactly this vkCmdBindPipeline. A draw that has no - // pipeline is a skipped draw, which is what every other failure below already does. - // MGLOG_E, latched: the condition is a property of the program, so an unlatched line - // here is one per draw forever. Parked at MGLOG_I until the Log.h ordering was fixed. - if (pipeline == VK_NULL_HANDLE) { - MGLOG_E_ONCE("SetupDraw skipped: no graphics pipeline for program=%u (creation failed or the " - "program has no shader stages)", - program.GetExternalIndex()); - return false; - } - activeRenderPass = VkRenderPassManager::GetActiveRenderPass(); - - // Begin render pass, and handle clear - if (activeRenderPass && activeRenderPass->CompatibleWith(*renderPassEntry)) { - ClearAttachmentsOnActiveRenderPass(frame.commandBuffer, *renderPassEntry); - } else { - // No active render pass or active one not compatible. - // Restart a new render pass - Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, *renderPassEntry); - MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__); - } - - if (!g_dynamicStateShadow->graphicsPipelineValid || g_dynamicStateShadow->graphicsPipeline != pipeline) { - vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - g_dynamicStateShadow->graphicsPipelineValid = true; - g_dynamicStateShadow->graphicsPipeline = pipeline; - } - - const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers( - frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(), - VK_PIPELINE_BIND_POINT_GRAPHICS, nullptr, false, - m_samplerImageBindingOverridesScratch.empty() ? nullptr : &m_samplerImageBindingOverridesScratch); - if (!boundUniforms) { - MGLOG_E_ONCE("SetupDraw skipped: BindProgramUniformBuffers failed"); - return false; - } - - auto vtxUploadOk = UploadAndBindVertexBuffers( - frame.commandBuffer, vao, programObj, drawParams, pIndexBufferView); - if (!vtxUploadOk) { - MGLOG_E_ONCE("SetupDraw skipped: failed to upload vertex buffers"); - return false; - } - - if (aspects & DrawSetupAspect::IndexBuffer) { - auto idxUploadOk = UploadAndBindIndexBuffer(frame, vao, pIndexBufferView); - MOBILEGL_ASSERT(idxUploadOk, "SetupDraw skipped: failed to upload index buffer"); - } - - ApplyDynamicDrawStateTail(frame, renderPassEntry->extent, drawFbo->IsDefaultFramebuffer(), - ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin)); - - // Snapshot the fully resolved configuration for the consecutive-draw - // fast path (see TrySetupDrawFastPath). - { - auto& snap = *fillSnap; - const auto* nowActiveRenderPass = VkRenderPassManager::GetActiveRenderPass(); - if (nowActiveRenderPass != nullptr && !programObj.hasStorageImages) { - snap.valid = true; - snap.aspects = aspects.GetRaw(); - snap.primitiveRestartEnable = ResolvePrimitiveRestartEnable(aspects, pIndexBufferView); - snap.mode = mode; - snap.programLifetimeId = program.GetLifetimeId(); - snap.programVersion = program.GetBackendStateVersion(); - snap.vao = &vao; - snap.vaoLifetimeId = vao.GetLifetimeId(); - // Subsystem-guarded for the same reason as the other stamping site: the field - // is dead with bit 6 clear, and MOBILEGL_PIPE_PUSH=0 has to be P1 exactly. - if (MagmaPipeTrackHArmIsHandles(MG_Pipe::kMGPipeSubsystemMagmaVertexInput)) { - snap.vaoHandle = ResolveVaoHandle(vao); - } - snap.vaoConfigVersion = vao.GetConfigVersion(); - snap.drawFbo = drawFbo.get(); - snap.drawFboLifetimeId = drawFbo->GetLifetimeId(); - snap.fboVersion = drawFbo->GetObjectVersion(); - snap.drawFboIsDefault = drawFboIsDefault; - snap.viewportCount = ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin); - snap.renderStateVersion = MG_Pipe::gPipeInputs.GetPipelineStateVersion(); - snap.bindGeneration = MG_Pipe::gPipeInputs.GetTextureBindGeneration(); - snap.baseTransformFlags = GetBaseTransformFlagsRaw(drawFboIsDefault); - snap.resolvedTransformFlags = transformFlags.GetRaw(); - snap.renderPassHash = nowActiveRenderPass->hash; - snap.imageIndex = m_imageIndexAcquired; - snap.textureEraseEpoch = m_textureManager->GetResourceEraseEpoch(); - snap.textureImageEpoch = m_textureManager->GetTextureImageEpoch(); - snap.renderbufferImageEpoch = m_renderPassManager->GetRenderbufferImageEpoch(); - snap.drawUsesDepthStencil = drawUsesDepthStencil; - snap.renderPassExtent = renderPassEntry->extent; - snap.renderPassColorCount = renderPassEntry->colorAttachmentCount; - snap.renderPassSampleCount = renderPassEntry->sampleCount; - snap.pipeline = pipeline; - // The layout identity the fast path's aux-memo compare answers against. - // A memo hit here, not a rebuild: the pre-flight above resolved this - // VAO's entry already, so this re-reads the VAO's stamped state memo. - snap.vaoLayoutHash = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao).layoutHash; - // Entry pointer memo: only when nothing since the resolve restructured - // the factory cache (see programFactoryEpochAtResolve above). - if (m_programFactory->GetCacheStructureEpoch() == programFactoryEpochAtResolve) { - snap.programObj = &programObj; - snap.programFactoryEpoch = programFactoryEpochAtResolve; - } else { - snap.programObj = nullptr; - snap.programFactoryEpoch = 0; - } - snap.samplingResolutionGeneration = MG_Pipe::gPipeInputs.GetSamplingResolutionGeneration(); - Uint64 snapContentSum = 0; - Uint64 snapParamsSum = 0; - // Per-entry copies of this draw's sampled set (the scratch vectors - // will be overwritten by the next full-path draw of ANY program). - // Record each resource's layout VALUE for the descriptor-reuse hint; - // transitions above updated the resources in place, so this reads the - // layouts the descriptors just resolved against. - snap.sampledTextures = sampledTextures; - snap.sampledResources = sampledResources; - snap.sampledBindingRecords = m_sampledBindingRecordsScratch; - snap.sampledLayouts.assign(sampledTextures.size(), VK_IMAGE_LAYOUT_UNDEFINED); - for (SizeT i = 0; i < sampledTextures.size(); ++i) { - const auto* sampledTexture = sampledTextures[i]; - if (sampledTexture == nullptr) { - continue; - } - snapContentSum += sampledTexture->GetContentVersion(); - snapParamsSum += sampledTexture->GetTextureParamsVersion(); - if (sampledResources[i] != nullptr) { - snap.sampledLayouts[i] = sampledResources[i]->layout; - } - } - snap.sampledContentSum = snapContentSum; - snap.sampledParamsSum = snapParamsSum; - } else { - snap.valid = false; - } - } - return true; } void VulkanRenderer::DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) { @@ -6944,59 +6321,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_textureManager->CollectGarbage(); DispatchWireCompute(numGroupsX, numGroupsY, numGroupsZ); return; - m_textureManager->CollectGarbage(); - auto& frame = m_frameContext.GetCurrent(); - // The DISPATCH accessor: with a pipeline bound this is its compute stage program - // itself, never the graphics composite (which carries no compute stage at all). - const auto& program = *MG_Pipe::gPipeInputs.GetProgramForDispatch(); - if (!program.GetLinkStatus() || !program.GetSpirvStatus()) { - MGLOG_E_ONCE("DispatchCompute skipped: program=%u has no optimized SPIR-V", - program.GetExternalIndex()); - return; - } - ProgramFactory::CompileOptionFlags transformFlags = 0; - const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); - // No shader module of a program that failed validation exists; the build said why, once. - if (programObj.invalidSpirvDeclined) return; - - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - - if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - - if (!PrepareStorageImageTextures(frame, program, programObj)) { - MGLOG_E_ONCE("DispatchCompute skipped: storage image preparation failed"); - return; - } - if (!PrepareSamplerImageFeedbackSnapshots(frame, program, programObj, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT)) { - MGLOG_E_ONCE("DispatchCompute skipped: sampler/image feedback snapshot failed"); - return; - } - - const VkPipeline pipeline = GetOrCreateComputePipeline(programObj); - if (pipeline == VK_NULL_HANDLE) { - MGLOG_E_ONCE("DispatchCompute skipped: compute pipeline creation failed for program=%u", - program.GetExternalIndex()); - return; - } - - vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); - const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers( - frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(), - VK_PIPELINE_BIND_POINT_COMPUTE, nullptr, false, - m_samplerImageBindingOverridesScratch.empty() ? nullptr : &m_samplerImageBindingOverridesScratch); - if (!boundUniforms) { - MGLOG_E_ONCE("DispatchCompute skipped: BindProgramUniformBuffers failed"); - return; - } - - MGLOG_D("DirectVulkan: glDispatchCompute(%u, %u, %u)", numGroupsX, numGroupsY, numGroupsZ); - EndActiveRenderPassOn(frame.commandBuffer); - vkCmdDispatch(frame.commandBuffer, numGroupsX, numGroupsY, numGroupsZ); } void VulkanRenderer::DispatchComputeIndirect(GLintptr indirect) { @@ -7006,71 +6330,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_textureManager->CollectGarbage(); DispatchWireComputeIndirect(indirect); return; - m_textureManager->CollectGarbage(); - auto& frame = m_frameContext.GetCurrent(); - // See DispatchCompute: the dispatch accessor, not the draw one. - const auto& program = *MG_Pipe::gPipeInputs.GetProgramForDispatch(); - if (!program.GetLinkStatus() || !program.GetSpirvStatus()) { - MGLOG_E_ONCE("DispatchComputeIndirect skipped: program=%u has no optimized SPIR-V", - program.GetExternalIndex()); - return; - } - ProgramFactory::CompileOptionFlags transformFlags = 0; - const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); - // No shader module of a program that failed validation exists; the build said why, once. - if (programObj.invalidSpirvDeclined) return; - - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - - if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - - if (!PrepareStorageImageTextures(frame, program, programObj)) { - MGLOG_E_ONCE("DispatchComputeIndirect skipped: storage image preparation failed"); - return; - } - if (!PrepareSamplerImageFeedbackSnapshots(frame, program, programObj, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT)) { - MGLOG_E_ONCE("DispatchComputeIndirect skipped: sampler/image feedback snapshot failed"); - return; - } - - const VkPipeline pipeline = GetOrCreateComputePipeline(programObj); - if (pipeline == VK_NULL_HANDLE) { - MGLOG_E_ONCE("DispatchComputeIndirect skipped: compute pipeline creation failed for program=%u", - program.GetExternalIndex()); - return; - } - - vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); - const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers( - frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(), - VK_PIPELINE_BIND_POINT_COMPUTE, nullptr, false, - m_samplerImageBindingOverridesScratch.empty() ? nullptr : &m_samplerImageBindingOverridesScratch); - if (!boundUniforms) { - MGLOG_E_ONCE("DispatchComputeIndirect skipped: BindProgramUniformBuffers failed"); - return; - } - - auto indirectBuffer = MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::DispatchIndirect).GetBoundObject(); - if (!indirectBuffer) { - MGLOG_E_ONCE("DispatchComputeIndirect skipped: GL_DISPATCH_INDIRECT_BUFFER is not bound"); - return; - } - indirectBuffer->SyncPersistentMappedRange(); - - BufferSlice slice{}; - if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, indirectBuffer, slice)) { - MGLOG_E_ONCE("DispatchComputeIndirect skipped: failed to sync indirect dispatch buffer"); - return; - } - - MGLOG_D("DirectVulkan: glDispatchComputeIndirect(offset=%zu)", static_cast(indirect)); - EndActiveRenderPassOn(frame.commandBuffer); - vkCmdDispatchIndirect(frame.commandBuffer, slice.buffer, slice.offset + static_cast(indirect)); } VkMemoryBarrier VulkanRenderer::BuildMemoryBarrierForGlBarriers(GLbitfield barriers) { @@ -7279,200 +6538,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { payload.stencil = MG_Pipe::gPipeInputs.GetClearStencil(); ClearWireFramebuffer(*fbo, payload); return; - m_clearManager->CollectGarbage(); - if ((mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) == 0) { - return; - } - // GL 3.3 §3.1: when RASTERIZER_DISCARD is enabled, Clear and ClearBuffer* are ignored. - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) { - return; - } - auto* fbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get(); - MOBILEGL_ASSERT(fbo, "VulkanRenderer::Clear: draw framebuffer not found (fbo == nullptr)"); - if (IsUnsupportedFramebufferForDirectVulkan(*fbo)) { - RecordUnsupportedFramebufferError(__func__); - return; - } - - ClearFramebufferPayload payload { - .color = MG_Pipe::gPipeInputs.GetClearColor(), - .depth = MG_Pipe::gPipeInputs.GetClearDepth(), - .stencil = MG_Pipe::gPipeInputs.GetClearStencil() - }; - - // A render-pass loadOp clear always covers the complete attachment, while - // OpenGL glClear is clipped by GL_SCISSOR_TEST. Blaze3D relies on this for - // GuiItemAtlas: animated items clear only their atlas slot before being - // redrawn. Queueing that clear as a loadOp erases every cached static item. - if (MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::ScissorTest)) { - VkClearRect clearRect{}; - switch (PrepareScissoredClear(*fbo, clearRect)) { - case ScissoredClearPrep::NoOp: - return; - case ScissoredClearPrep::NotNeeded: - break; // full-coverage scissor: the deferred whole-surface path below is equivalent - case ScissoredClearPrep::Ready: { - VkClearAttachment clearAttachments[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS + 1]; - Uint32 clearAttachmentCount = 0; - - if ((mask & GL_COLOR_BUFFER_BIT) != 0) { - const auto& drawBuffers = fbo->GetDrawBuffers(); - for (Uint32 drawBufferIndex = 0; drawBufferIndex < drawBuffers.size(); ++drawBufferIndex) { - const auto attachmentType = drawBuffers[drawBufferIndex]; - if (attachmentType == FramebufferAttachmentType::None) { - continue; - } - const auto& attachment = fbo->GetAttachment(attachmentType); - if (!attachment.IsComplete()) { - continue; - } - - const BoolVec4 colorMask = MG_Pipe::gPipeInputs.GetColorMaskIndexed(drawBufferIndex); - if (!colorMask.r() && !colorMask.g() && !colorMask.b() && !colorMask.a()) { - continue; - } - if (!colorMask.r() || !colorMask.g() || !colorMask.b() || !colorMask.a()) { - MGLOG_W_ONCE("DirectVulkan: scissored glClear with a partial color mask is not supported"); - continue; - } - - MG_State::GLState::ITextureObject* colorTexture = nullptr; - if (attachment.IsTexture()) { - colorTexture = attachment.GetTexture().get(); - } - VkClearAttachment clearAttachment{}; - clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - clearAttachment.colorAttachment = drawBufferIndex; - // glClear only ever supplies float values (ClearFramebufferPayload has no - // other form), so the float member is always the right one here. - clearAttachment.clearValue.color = { - payload.color.x(), payload.color.y(), payload.color.z(), - ColorFormatLacksAlpha(colorTexture) ? 1.0f : payload.color.w() - }; - clearAttachments[clearAttachmentCount++] = clearAttachment; - } - } - - VkImageAspectFlags depthStencilAspects = 0; - if ((mask & GL_DEPTH_BUFFER_BIT) != 0 && MG_Pipe::gPipeInputs.GetDepthMask()) { - const auto& depthAttachment = fbo->GetAttachment(FramebufferAttachmentType::Depth); - if (depthAttachment.IsComplete()) { - depthStencilAspects |= VK_IMAGE_ASPECT_DEPTH_BIT; - } - } - if ((mask & GL_STENCIL_BUFFER_BIT) != 0) { - const auto& stencilAttachment = fbo->GetAttachment(FramebufferAttachmentType::Stencil); - if (stencilAttachment.IsComplete()) { - // GL 3.3 §4.2.3: the clear is masked by the front stencil write mask. - // vkCmdClearAttachments writes every bit, so only a full (8-bit stencil) or - // zero mask can be expressed; treat a partial mask like a partial color mask. - const Uint32 stencilWriteMask = - MG_Pipe::gPipeInputs.GetStencilState(StencilFace::Front).WriteMask; - if ((stencilWriteMask & 0xFFu) == 0xFFu) { - depthStencilAspects |= VK_IMAGE_ASPECT_STENCIL_BIT; - } else if (stencilWriteMask != 0) { - MGLOG_W_ONCE("DirectVulkan: scissored glClear with a partial stencil write mask is not supported"); - } - } - } - if (depthStencilAspects != 0) { - VkClearAttachment clearAttachment{}; - clearAttachment.aspectMask = depthStencilAspects; - clearAttachment.clearValue.depthStencil = {payload.depth, payload.stencil}; - clearAttachments[clearAttachmentCount++] = clearAttachment; - } - - if (clearAttachmentCount != 0) { - vkCmdClearAttachments(m_frameContext.GetCurrent().commandBuffer, - clearAttachmentCount, clearAttachments, - 1, &clearRect); - } - return; - } - } - } - - // GL 3.3 §4.2.3: glClear honors the write masks. Mirror the scissored path's - // gating for the deferred path: drop fully-masked planes, warn on partial - // masks vkCmdClear*/loadOp clears cannot express. - GLbitfield deferredMask = mask; - if ((deferredMask & GL_DEPTH_BUFFER_BIT) != 0 && !MG_Pipe::gPipeInputs.GetDepthMask()) { - deferredMask &= ~static_cast(GL_DEPTH_BUFFER_BIT); - } - if ((deferredMask & GL_STENCIL_BUFFER_BIT) != 0) { - const Uint32 stencilWriteMask = MG_Pipe::gPipeInputs.GetStencilState(StencilFace::Front).WriteMask; - if ((stencilWriteMask & 0xFFu) != 0xFFu) { - if (stencilWriteMask != 0) { - MGLOG_W_ONCE("DirectVulkan: deferred glClear with a partial stencil write mask is not supported"); - } - deferredMask &= ~static_cast(GL_STENCIL_BUFFER_BIT); - } - } - if ((deferredMask & GL_COLOR_BUFFER_BIT) != 0) { - const auto& drawBuffers = fbo->GetDrawBuffers(); - Bool anyFullMask = false; - Bool anyRestrictedMask = false; - for (Uint32 drawBufferIndex = 0; drawBufferIndex < drawBuffers.size(); ++drawBufferIndex) { - if (drawBuffers[drawBufferIndex] == FramebufferAttachmentType::None) { - continue; - } - const BoolVec4 colorMask = MG_Pipe::gPipeInputs.GetColorMaskIndexed(drawBufferIndex); - const Bool full = colorMask.r() && colorMask.g() && colorMask.b() && colorMask.a(); - if (full) { - anyFullMask = true; - } else { - anyRestrictedMask = true; - if (colorMask.r() || colorMask.g() || colorMask.b() || colorMask.a()) { - MGLOG_W_ONCE("DirectVulkan: deferred glClear with a partial color mask is not supported"); - } - } - } - if (!anyFullMask) { - deferredMask &= ~static_cast(GL_COLOR_BUFFER_BIT); - } else if (anyRestrictedMask) { - // Mixed per-buffer masks: queue only the fully-writable texture targets - // individually and drop the framebuffer-level color clear. - for (Uint32 drawBufferIndex = 0; drawBufferIndex < drawBuffers.size(); ++drawBufferIndex) { - const auto attachmentType = drawBuffers[drawBufferIndex]; - if (attachmentType == FramebufferAttachmentType::None) { - continue; - } - const BoolVec4 colorMask = MG_Pipe::gPipeInputs.GetColorMaskIndexed(drawBufferIndex); - if (!(colorMask.r() && colorMask.g() && colorMask.b() && colorMask.a())) { - continue; - } - const auto& attachment = fbo->GetAttachment(attachmentType); - if (attachment.IsRenderbuffer()) { - m_renderPassManager->QueueRenderbufferClear( - {.mask = GL_COLOR_BUFFER_BIT, .color = payload.color}, attachment); - } else if (attachment.IsTexture()) { - SettleTexelsBeforeQueuedClear(*fbo, attachment); - m_clearManager->QueueClear({.mask = GL_COLOR_BUFFER_BIT, .color = payload.color}, - attachment); - } - } - deferredMask &= ~static_cast(GL_COLOR_BUFFER_BIT); - } - } - if (deferredMask == 0) { - return; - } - - if ((deferredMask & GL_COLOR_BUFFER_BIT) != 0) { - for (const auto drawBuffer : fbo->GetDrawBuffers()) { - if (drawBuffer != FramebufferAttachmentType::None) { - SettleTexelsBeforeQueuedClear(*fbo, fbo->GetAttachment(drawBuffer)); - } - } - } - if ((deferredMask & GL_DEPTH_BUFFER_BIT) != 0) { - SettleTexelsBeforeQueuedClear(*fbo, fbo->GetAttachment(FramebufferAttachmentType::Depth)); - } - if ((deferredMask & GL_STENCIL_BUFFER_BIT) != 0) { - SettleTexelsBeforeQueuedClear(*fbo, fbo->GetAttachment(FramebufferAttachmentType::Stencil)); - } - m_clearManager->QueueClear(deferredMask, payload, *fbo); - m_renderPassManager->QueueRenderbufferClear(deferredMask, payload, *fbo); } void VulkanRenderer::QueueClearBufferPayloadForFramebuffer( @@ -7674,11 +6739,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!fbo) MagmaWireFatal("clear-buffer-framebuffer-record"); ClearWireFramebuffer(*fbo,clearPayload,buffer == GL_COLOR ? drawbuffer : -1); return; - auto* fbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get(); - if (!fbo) { - return; - } - QueueClearBufferPayloadForFramebuffer(*fbo, buffer, drawbuffer, clearPayload); } void VulkanRenderer::ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { @@ -8694,9 +7754,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLbitfield mask, GLenum filter) { BlitWireFramebuffers(srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter); return; - auto readFbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); - auto drawFbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - BlitNamedFramebuffer(readFbo, drawFbo, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } void VulkanRenderer::BlitNamedFramebuffer(const SharedPtr& readFbo, @@ -9332,213 +8389,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLint x, GLint y, GLsizei width, GLsizei height) { CopyWireFramebufferToTexture(target,level,xoffset,yoffset,x,y,width,height); return; - if (width <= 0 || height <= 0) { - return; - } - - const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); - if (textureTarget != TextureTarget::Texture2D) { - RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, - "CopyTexSubImage2D currently only supports GL_TEXTURE_2D destinations."); - return; - } - if (level < 0) { - RecordTextureCopyError(__func__, ErrorCode::InvalidValue, - "CopyTexSubImage2D level must be non-negative."); - return; - } - - auto& textureUnit = MG_Pipe::gPipeInputs.GetTextureUnitObject(MG_Pipe::gPipeInputs.GetActiveTextureUnit()); - auto destinationTexture = textureUnit.GetBindingSlot(textureTarget).GetBoundObject(); - if (destinationTexture == nullptr) { - RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, - "CopyTexSubImage2D requires a bound destination texture."); - return; - } - - auto readFbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); - if (readFbo == nullptr) { - RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, - "CopyTexSubImage2D requires a framebuffer bound to GL_READ_FRAMEBUFFER."); - return; - } - if (IsUnsupportedFramebufferForDirectVulkan(*readFbo)) { - RecordTextureCopyError(__func__, ErrorCode::InvalidFramebufferOperation, - "CopyTexSubImage2D does not support the current non-default read framebuffer configuration on DirectVulkan."); - return; - } - - auto& frame = m_frameContext.GetCurrent(); - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - - if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - - const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer(); - // CopyTexSubImage2D only ever READS the default framebuffer, so it resolves the image - // the app last rendered into (see m_defaultFramebufferImageIndex) - never the one - // Present() has already acquired for the next frame. - const Uint32 srcImageIndex = DefaultFramebufferReadIndex(readIsDefaultFbo); - - BlitImageBinding dstBinding{}; - if (!ResolveTextureCopyDestinationBinding(*destinationTexture, static_cast(level), *m_textureManager, - dstBinding)) { - RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, - "CopyTexSubImage2D failed to resolve the destination texture."); - return; - } - - BlitImageBinding srcBinding{}; - if (!ResolveTextureCopySourceBinding(*readFbo, srcImageIndex, m_swapchainObject, *m_textureManager, - *m_renderPassManager, dstBinding.aspectMask, srcBinding)) { - RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, - "CopyTexSubImage2D requires a complete read attachment compatible with the destination texture."); - return; - } - - if (!readIsDefaultFbo) { - const auto sourceAttachmentType = ResolveFramebufferCopyAttachmentType(*readFbo, true, srcBinding.aspectMask); - const auto& sourceAttachment = readFbo->GetAttachment(sourceAttachmentType); - auto sourceTexture = sourceAttachment.GetTexture(); - MOBILEGL_ASSERT(sourceTexture != nullptr, "CopyTexSubImage2D: source texture attachment is null"); - const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); - MOBILEGL_ASSERT(clearReady, - "CopyTexSubImage2D: failed to materialize pending clear for source textureId=%d", - sourceTexture->GetExternalIndex()); - } - - { - // A clear queued for the destination predates this copy in API order; - // execute it now so the deferred materialization cannot stomp the copy. - const Bool dstClearReady = MaterializePendingClearForTexture(frame.commandBuffer, *destinationTexture); - MOBILEGL_ASSERT(dstClearReady, - "CopyTexSubImage2D: failed to materialize pending clear for destination textureId=%d", - destinationTexture->GetExternalIndex()); - } - - const Bool srcUsesSwapchainDepth = readIsDefaultFbo && (srcBinding.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) == 0; - const VkImageLayout srcOriginalLayout = readIsDefaultFbo - ? (srcUsesSwapchainDepth - ? m_swapchainObject.GetDepthStencilImageLayout(srcImageIndex) - : m_swapchainObject.GetImageLayout(srcImageIndex)) - : *srcBinding.trackedLayout; - if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, - "CopyTexSubImage2D source image has undefined layout."); - return; - } - - const VkImageLayout dstOriginalLayout = *dstBinding.trackedLayout; - const VkImageLayout dstRestoreLayout = dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED - ? ((dstBinding.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0 - ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL - : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - : dstOriginalLayout; - - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask); - if (readIsDefaultFbo) { - VkImageLayout srcTrackedLayout = srcOriginalLayout; - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask, - srcBinding.mipLevel, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain source image", __func__); - if (srcUsesSwapchainDepth) { - m_swapchainObject.SetDepthStencilImageLayout(srcImageIndex, srcTrackedLayout); - } else { - m_swapchainObject.SetImageLayout(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 source image", __func__); - } - - VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags dstAccessMask = 0; - GetImageTransitionSourceState(dstOriginalLayout, dstStageMask, dstAccessMask); - Bool dstReady = 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(dstReady, "%s: failed to transition destination image", __func__); - - VkImageCopy copyRegion{}; - copyRegion.srcSubresource.aspectMask = srcBinding.aspectMask; - copyRegion.srcSubresource.mipLevel = srcBinding.mipLevel; - copyRegion.srcSubresource.baseArrayLayer = srcBinding.baseArrayLayer; - copyRegion.srcSubresource.layerCount = srcBinding.layerCount; - // KNOWN GAP, deliberately not half-fixed here: when the read framebuffer is the default - // one this samples GL rows [y, y+h) counted from the TOP of a display-oriented image, so - // it takes the mirrored band AND writes it into the (GL-oriented) destination texture - // upside down. Correcting only the offset would swap one wrong answer for another, - // because vkCmdCopyImage cannot reverse rows: this path has to become a vkCmdBlitImage - // with an inverted source Y pair, the way BlitFramebuffer above now does it. Tracked - // separately; the four sites behind the 1,759-case orientation defect are the viewport, - // the scissor, the ReadPixels copy offset and the readback remap. - if (readIsDefaultFbo) { - MGLOG_D("DirectVulkan::CopyTexSubImage2D: copying from the DEFAULT framebuffer still uses the raw GL " - "Y origin (x=%d y=%d w=%d h=%d); the result is the mirrored band, stored flipped", - x, y, width, height); - } - copyRegion.srcOffset = {x, y, 0}; - copyRegion.dstSubresource.aspectMask = dstBinding.aspectMask; - copyRegion.dstSubresource.mipLevel = dstBinding.mipLevel; - copyRegion.dstSubresource.baseArrayLayer = dstBinding.baseArrayLayer; - copyRegion.dstSubresource.layerCount = dstBinding.layerCount; - copyRegion.dstOffset = {xoffset, yoffset, 0}; - copyRegion.extent = {static_cast(width), static_cast(height), 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 = srcUsesSwapchainDepth - ? m_swapchainObject.GetDepthStencilImageLayout(srcImageIndex) - : 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, - srcBinding.mipLevel, srcBinding.mipLevelCount); - MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain source image layout", __func__); - if (srcUsesSwapchainDepth) { - m_swapchainObject.SetDepthStencilImageLayout(srcImageIndex, srcTrackedLayout); - } else { - 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, - srcBinding.mipLevel, 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); - Bool dstRestored = 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(dstRestored, "%s: failed to restore destination image layout", __func__); } namespace { @@ -9646,8 +8496,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint CopyImageEndpointName(const CopyImageEndpoint& endpoint) { return endpoint.IsRenderbuffer() ? endpoint.RenderbufferHandle.Slot : endpoint.TextureHandle.Slot; - if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetExternalIndex(); - return endpoint.Texture ? endpoint.Texture->GetExternalIndex() : 0u; } } // namespace @@ -9716,35 +8564,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { out.arrayLayers = resource->arrayLayers; out.format = resource->format; return out.image != VK_NULL_HANDLE; - if (endpoint.IsRenderbuffer()) { - auto* resource = m_renderPassManager->GetOrCreateRenderbufferResource(endpoint.Renderbuffer); - if (resource == nullptr) return false; - out.isRenderbuffer = true; - out.image = resource->image; - out.trackedLayout = &resource->layout; - out.aspect = resource->aspect; - out.mipLevels = 1; - out.extent = resource->extent; - out.depth = 1; - out.arrayLayers = 1; - out.format = resource->format; - return out.image != VK_NULL_HANDLE; - } - // An endpoint that named nothing is the frontend validator's INVALID_VALUE and never - // reaches here - but the assertion that says so is compiled out of a release build. - if (endpoint.Texture == nullptr) return false; - auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*endpoint.Texture); - if (resource == nullptr) return false; - out.isRenderbuffer = false; - out.image = resource->image; - out.trackedLayout = &resource->layout; - out.aspect = resource->aspect; - out.mipLevels = resource->mipLevels; - out.extent = resource->extent; - out.depth = resource->depth; - out.arrayLayers = resource->arrayLayers; - out.format = resource->format; - return true; }; CopyImageVkImage srcImage{}; CopyImageVkImage dstImage{}; @@ -9947,10 +8766,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) { return true; - if (endpoint.IsRenderbuffer()) { - return MaterializePendingClearForRenderbuffer(frame.commandBuffer, endpoint.Renderbuffer); - } - return MaterializePendingClearForTexture(frame.commandBuffer, *endpoint.Texture); }; const Bool clearReady = materializeClear(srcEndpoint); MOBILEGL_ASSERT(clearReady, "%s: failed to materialize pending clear for source objectId=%u", @@ -10220,218 +9035,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void* pixels) { ReadWirePixels(x,y,width,height,format,type,pixels); return; - if (width <= 0 || height <= 0) { - return; - } - - auto readFbo = MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); - if (readFbo == nullptr) { - MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: no read framebuffer is bound"); - return; - } - - if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL || format == GL_STENCIL_INDEX) { - ReadDepthStencilPixels(*readFbo, x, y, width, height, format, type, pixels); - return; - } - - auto& frame = m_frameContext.GetCurrent(); - if (!readFbo->IsDefaultFramebuffer()) { - // The read syncs the source texture, whose pending texels must land after work already - // recorded against it (see OrderPendingUploadAfterRecording). - if (const auto readTexture = readFbo->GetAttachment(readFbo->GetReadBuffer()).GetTexture()) { - OrderPendingUploadAfterRecording(frame, *readTexture); - } - } - if (!frame.isCommandRecording) { - m_frameContext.BeginCommandRecording(); - } - if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { - VkRenderPassManager::EndRenderPass(frame.commandBuffer); - } - - const Bool readIsDefaultFbo = readFbo->IsDefaultFramebuffer(); - // Materialize any pending clear on the read-buffer attachment BEFORE resolving the - // blit binding below: for a renderbuffer/texture that has never been part of any - // render pass yet (e.g. a GL_NONE draw buffer slot whose attachment is only ever - // touched via an explicit glReadBuffer), materializing lazily creates its backing - // Vulkan resource for the first time. UnorderedMap is open-addressing and may - // rehash on that insertion, invalidating any RenderbufferResource*/TextureResource* - // obtained beforehand - so ResolveColorBlitBinding's cached `trackedLayout` pointer - // must be taken AFTER this, never before it. - // - // The default framebuffer needs this just as much, and used to be excluded: its clear is - // parked the same way, and with no draw between the clear and the readback no render - // pass ever runs to fold it in, so the readback returned the previous frame's image - // (KHR-GL40.draw_indirect.negative-*). It only takes a different materializer because the - // image to clear is the acquired swapchain image, not the attachment's placeholder - // texture. - if (readIsDefaultFbo) { - const Bool clearReady = MaterializePendingClearForDefaultFramebuffer(frame.commandBuffer, *readFbo, - readFbo->GetReadBuffer()); - MOBILEGL_ASSERT(clearReady, "ReadPixels: failed to materialize the default framebuffer's pending clear"); - } else { - const auto& sourceAttachment = readFbo->GetAttachment(readFbo->GetReadBuffer()); - auto sourceTexture = sourceAttachment.GetTexture(); - if (sourceTexture != nullptr) { - const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); - MOBILEGL_ASSERT(clearReady, - "ReadPixels: 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, - "ReadPixels: failed to materialize pending clear for source renderbuffer %u", - sourceAttachment.GetRenderbuffer()->GetExternalIndex()); - } - } - - BlitImageBinding srcBinding{}; - // A read resolves the image the app last rendered into, not the one Present() has - // already acquired for the frame that is starting (see m_defaultFramebufferImageIndex). - // A retrace snapshot taken just before a swap is applied after it, and must still find - // the colour the app drew. - const Uint32 srcImageIndex = DefaultFramebufferReadIndex(readIsDefaultFbo); - if (readIsDefaultFbo && !m_swapchainObject.IsImageContentDefined(srcImageIndex)) { - // Named rather than silent: the readback hands back whatever the image holds. The - // ordinary cause is a read of the default framebuffer applied after the Present that - // consumed it, where the app's own colour is still what is expected. - MGLOG_W_ONCE("DirectVulkan::ReadPixels: no write is recorded for swapchain image %u of the " - "default framebuffer; the readback returns its raw pixels", - srcImageIndex); - } - if (!ResolveColorBlitBinding(*readFbo, true, srcImageIndex, m_swapchainObject, *m_textureManager, - *m_renderPassManager, srcBinding)) { - return; - } - - const VkImageLayout srcOriginalLayout = readIsDefaultFbo - ? m_swapchainObject.GetImageLayout(srcImageIndex) - : *srcBinding.trackedLayout; - if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) { - MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: source image layout is undefined"); - return; - } - - const VkFormat srcFormat = srcBinding.format; - const SizeT sourceTexelSize = GetReadbackTexelSize(srcFormat); - if (sourceTexelSize == 0) { - MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: unsupported source format=%d", - static_cast(srcFormat)); - return; - } - const VkDeviceSize readbackSize = static_cast(width) * - static_cast(height) * 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::ReadPixels skipped: failed to create readback buffer"); - return; - } - - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = 0; - GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask); - // The copy below reads `srcBinding.baseArrayLayer`, which for a glFramebufferTextureLayer - // attachment is any layer of the array - the barrier covers all of them (see - // VkTextureManager::TransitionImageLayout), so the layer being read is one it moved. - if (readIsDefaultFbo) { - VkImageLayout trackedLayout = srcOriginalLayout; - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, trackedLayout, 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, trackedLayout); - } 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, 1); - MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__); - } - - VkBufferImageCopy copyRegion{}; - copyRegion.imageSubresource.aspectMask = srcBinding.aspectMask; - copyRegion.imageSubresource.mipLevel = srcBinding.mipLevel; - copyRegion.imageSubresource.baseArrayLayer = srcBinding.baseArrayLayer; - copyRegion.imageSubresource.layerCount = 1; - // The GL rect, aimed at the default framebuffer's stored orientation. Using the GL y - // verbatim copied rows [y, y+h) counted from the TOP of the image, i.e. the wrong band for - // every read that was not full-height. - VkOffset2D copyOffset{x, y}; - VkExtent2D copyExtent{static_cast(width), static_cast(height)}; - if (readIsDefaultFbo) { - const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent(); - const Bool mapped = MapDefaultFramebufferReadbackRect( - x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), ©Offset, - ©Extent); - MOBILEGL_ASSERT(mapped, "ReadPixels: default framebuffer read rectangle is out of bounds"); - if (!mapped) return; - } - copyRegion.imageOffset = {copyOffset.x, copyOffset.y, static_cast(srcBinding.depthOffset)}; - copyRegion.imageExtent = {copyExtent.width, copyExtent.height, 1}; - vkCmdCopyImageToBuffer(frame.commandBuffer, srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - readback.GetHandle(), 1, ©Region); - - VkPipelineStageFlags restoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags restoreAccessMask = 0; - GetImageTransitionDestinationState(srcOriginalLayout, restoreStageMask, restoreAccessMask); - if (readIsDefaultFbo) { - VkImageLayout trackedLayout = m_swapchainObject.GetImageLayout(srcImageIndex); - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, trackedLayout, srcOriginalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, restoreStageMask, - VK_ACCESS_TRANSFER_READ_BIT, restoreAccessMask, srcBinding.aspectMask); - MOBILEGL_ASSERT(ok, "%s: failed to restore swapchain source image layout", __func__); - m_swapchainObject.SetImageLayout(srcImageIndex, trackedLayout); - } else { - Bool ok = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, srcOriginalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, restoreStageMask, - VK_ACCESS_TRANSFER_READ_BIT, restoreAccessMask, srcBinding.aspectMask, - srcBinding.mipLevel, 1); - MOBILEGL_ASSERT(ok, "%s: failed to restore source image layout", __func__); - } - - if (!SubmitReadbackCommandsAndWait(frame)) { - return; - } - const auto* mapped = static_cast(readback.Map()); - if (mapped == nullptr) { - MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: failed to map readback buffer"); - return; - } - if (!readback.Invalidate(readbackSize)) { - MGLOG_E_ONCE("DirectVulkan::ReadPixels skipped: failed to invalidate readback buffer"); - return; - } - if (readIsDefaultFbo) { - const VkSurfaceTransformFlagBitsKHR preTransform = m_swapchainObject.GetPreTransform(); - // No full-extent gate any more: the remap works on the copied rect, and the copy was - // already aimed with the same mapping. The gate is exactly what made every partial - // read of the default framebuffer come back in Vulkan row order. - Vector remapped(static_cast(width) * static_cast(height) * sourceTexelSize); - if (RemapDefaultFramebufferReadback(mapped, static_cast(width), - static_cast(height), preTransform, sourceTexelSize, - remapped.data())) { - PackReadbackToClientOrPbo(remapped.data(), srcFormat, width, height, 1, format, type, pixels, - /*applyPackImageParams=*/false, /*applyReadColorClamp=*/true); - return; - } - MGLOG_D("DirectVulkan::ReadPixels: default-FBO remap failed (w=%d h=%d preTransform=%d); falling back " - "to raw readback", - width, height, static_cast(preTransform)); - } - PackReadbackToClientOrPbo(mapped, srcFormat, width, height, 1, format, type, pixels, - /*applyPackImageParams=*/false, /*applyReadColorClamp=*/true); } Bool VulkanRenderer::BlitDepthAcrossFormats(FrameContext::FrameData& frame, VkImage srcImage, VkFormat srcFormat, @@ -11191,309 +9794,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::GenerateMipmap(GLenum target) { GenerateWireMipmap(); return; - const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); - // Whatever is left here is a coverage gap in this backend, not a broken invariant, so it - // declines (leaving the mip chain unwritten) rather than asserting the process down. What - // remains is the multisample targets, which GL 4.6 core 8.14.4 forbids to glGenerateMipmap - // outright. - // - // Every ARRAY target - 1D array, 2D array, cube map array - needs no blit code of its own: - // its layers live in the VkImage's arrayLayers, so resource->extent/depth already describe - // one layer's image and the loop below already copies every layer of the window per level - // via srcSubresource.layerCount. The one thing they DO need is that the GL-space storage - // allocation not shrink the layer count down the chain, which MipShrinkingComponentCount - // handles. - if (textureTarget != TextureTarget::Texture2D && textureTarget != TextureTarget::Texture2DArray && - textureTarget != TextureTarget::Texture3D && textureTarget != TextureTarget::TextureCubeMap && - textureTarget != TextureTarget::TextureCubeMapArray && - // A 1D texture needs nothing special: its storage extent is {width, 1, 1}, so the blit - // loop below already emits the y and z offsets of 0 and 1 that a 1D image requires. - textureTarget != TextureTarget::Texture1D && textureTarget != TextureTarget::Texture1DArray) { - MGLOG_W_ONCE("GenerateMipmap: unsupported target %s", MG_Util::ConvertTextureTargetToString(textureTarget).c_str()); - return; - } - - auto& textureUnit = MG_Pipe::gPipeInputs.GetTextureUnitObject(MG_Pipe::gPipeInputs.GetActiveTextureUnit()); - auto texture = textureUnit.GetBindingSlot(textureTarget).GetBoundObject(); - MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture."); - MOBILEGL_ASSERT(texture->IsComplete(), "GenerateMipmap requires a complete texture."); - - auto* mipmapTexture = MG_State::GLState::AsMipmapTexture(texture.get()); - MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires a mipmapped texture object."); - - const Uint32 currentMipLevelCount = static_cast(mipmapTexture->GetMipmapLevelCount()); - MOBILEGL_ASSERT(currentMipLevelCount > 0, "GenerateMipmap requires level 0 storage."); - - const auto& uploadTargets = mipmapTexture->GetUploadTargets(); - MOBILEGL_ASSERT(!uploadTargets.empty(), "GenerateMipmap requires at least one upload target."); - - // The generation window the call names, in the BOUND object's coordinates: BASE_LEVEL is - // where generation starts, and it runs to the smaller of the base image's full chain and - // MAX_LEVEL + 1 (GL 4.6 core 8.17). Two things make this the FRONTEND's plan rather than - // an arithmetic of this function's own: that window is what the storage allocation below - // has to cover, and through a view the numbers are the VIEW's own - BASE_LEVEL 1 of a view - // whose MIN_LEVEL is 1 describes the STORAGE's level 2, and MAX_LEVEL 2 ends the chain at - // the storage's level 3. - const auto mipmapPlan = MG_Impl::GLImpl::ComputeMipmapGenerationRange(*mipmapTexture, uploadTargets.front()); - const Uint32 baseMipLevel = std::min(mipmapPlan.Base, currentMipLevelCount - 1); - // A base level the storage does not reach leaves nothing to generate; the storage window - // below collapses to a single level, which every consumer reads as a no-op. - const Uint32 endMipLevel = std::max(mipmapPlan.End, baseMipLevel + 1); - - // A texture that has only ever defined level 0 carries a single-level backing, so defining - // the rest of the chain below recreates the image and carries the old contents over with a - // copy that is submitted and waited on out of band. Anything this frame has already - // recorded into the old image is not submitted yet, so that copy would read pre-flush - // content and every generated level would descend from a stale level 0 - the same hazard - // the storage-usage upgrade flushes for before its own preserve-copy. - if (m_textureManager->NeedsMipChainGrowth(*texture) && HasPendingRecordedWork()) { - if (FlushPendingCommands()) { - // Fresh command buffer: the sampled-descriptor-set memo describes bindings that - // only existed in the retired one. - m_lastSampledSetValid = false; - } - } - - 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, *texture); - MOBILEGL_ASSERT(clearReady, - "GenerateMipmap: failed to materialize pending clear for textureId=%d", - texture->GetExternalIndex()); - - auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); - MOBILEGL_ASSERT(resource != nullptr && resource->image != VK_NULL_HANDLE, - "GenerateMipmap failed to sync the backend texture."); - - VkFormatProperties formatProperties{}; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, resource->format, &formatProperties); - const VkFormatFeatureFlags optimalTilingFeatures = formatProperties.optimalTilingFeatures; - const Bool isDepthOrStencilTexture = - (resource->aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0; - const Bool supportsNativeBlit = - (optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 && - (optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0; - if (!isDepthOrStencilTexture && !supportsNativeBlit) { - MGLOG_W_ONCE("GenerateMipmap skipped for textureId=%d because Vulkan format %d does not support blit-based mip generation", - texture->GetExternalIndex(), static_cast(resource->format)); - return; - } - if (isDepthOrStencilTexture) { - MOBILEGL_ASSERT((resource->aspect & VK_IMAGE_ASPECT_STENCIL_BIT) == 0, - "GenerateMipmap: depth-stencil mipmap generation is not supported yet."); - } - - // P5c (T5 / tx): under an active transport the generated chain is defined on the - // server's staged shadow (keyed by the synced TextureResource above) and the client's - // level storage is never written; in monolith the client-object path runs unchanged. - const Bool allocatedMipmapStorage = - EnsureGenerateMipmapShadowAllocated(*resource, baseMipLevel, uploadTargets); - MOBILEGL_ASSERT(allocatedMipmapStorage, "GenerateMipmap could not allocate a full mip chain for this texture."); - - resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); - MOBILEGL_ASSERT(resource != nullptr && resource->image != VK_NULL_HANDLE, - "GenerateMipmap failed to resync the backend texture after allocating mip storage."); - m_textureManager->StampResourceRecordingWrite(*resource); - if (resource->layout == VK_IMAGE_LAYOUT_UNDEFINED) { - const VkImageLayout finalLayout = ResolveGenerateMipmapFinalLayout(resource->aspect); - Bool transitioned = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, resource->layout, finalLayout, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels); - MOBILEGL_ASSERT(transitioned, "GenerateMipmap: failed to transition uninitialized mip chain"); - return; - } - - // Every blit below runs in the STORAGE image's coordinates. A view owns no image of its - // own (VkTextureManager::StorageTextureOf), so the window the application named through - // the view - its BASE_LEVEL..MAX_LEVEL and its layer slice - has to land at the levels and - // layers the view aliases, and everything outside that window belongs to the storage owner - // and must not move (GL 4.6 core 8.18). - const Uint32 storageBaseMipLevel = ToStorageMipLevel(texture.get(), baseMipLevel); - const Uint32 storageEndMipLevel = ToStorageMipLevel(texture.get(), endMipLevel); - - // The chain may also stop where the base image's extent stops: levels past 1x1 are not - // generatable, and a chain the storage does not carry cannot be written. - const IntVec3 storageBaseTexelSize = { - static_cast(resource->extent.width), - static_cast(resource->extent.height), - static_cast(resource->depth), - }; - const IntVec3 baseTexelSize = ComputeMipTexelSize(storageBaseTexelSize, storageBaseMipLevel); - const Uint32 storageChainEndMipLevel = storageBaseMipLevel + ComputeFullMipLevelCount(baseTexelSize); - const Uint32 generateMipLevelCount = - std::min({storageEndMipLevel, storageChainEndMipLevel, static_cast(resource->mipLevels)}); - if (generateMipLevelCount <= storageBaseMipLevel + 1) { - resource->layout = ResolveGenerateMipmapFinalLayout(resource->aspect); - return; - } - - // A view writes ONLY the layers it names; the storage owner keeps every other one. A - // texture that is not a view addresses the whole image, which is what layerCount - // resource->arrayLayers already means. - Uint32 baseArrayLayer = 0; - Uint32 arrayLayerCount = static_cast(resource->arrayLayers); - if (texture->IsTextureView()) { - baseArrayLayer = std::min(ToStorageArrayLayer(texture.get(), 0), arrayLayerCount); - arrayLayerCount = std::min(static_cast(texture->GetViewNumLayers()), - static_cast(resource->arrayLayers) - baseArrayLayer); - } - MOBILEGL_ASSERT(arrayLayerCount > 0, "GenerateMipmap: empty layer window for textureId=%d", - texture->GetExternalIndex()); - - const VkImageLayout originalLayout = resource->layout; - const VkImageLayout finalLayout = ResolveGenerateMipmapFinalLayout(resource->aspect); - if (isDepthOrStencilTexture && !supportsNativeBlit) { - const Bool supportsShaderDepthMipmap = - (optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0 && - (optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0; - MOBILEGL_ASSERT(resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT, - "GenerateMipmap: shader fallback only supports depth-only textures."); - MOBILEGL_ASSERT(textureTarget == TextureTarget::Texture2D && resource->depth == 1 && resource->arrayLayers == 1, - "GenerateMipmap: shader fallback only supports single-layer GL_TEXTURE_2D depth textures."); - MOBILEGL_ASSERT(supportsShaderDepthMipmap, - "GenerateMipmap: depth texture format %d lacks sampled/depth-attachment support for shader fallback.", - static_cast(resource->format)); - const Bool depthReady = GenerateDepthMipmapWithShader(frame, *texture, *resource, - storageBaseMipLevel, generateMipLevelCount, - storageBaseTexelSize, originalLayout, finalLayout); - MOBILEGL_ASSERT(depthReady, - "GenerateMipmap: depth fallback failed for textureId=%d target=%d internalFormat=%d vkFormat=%d", - texture->GetExternalIndex(), static_cast(texture->GetTarget()), - static_cast(texture->GetFormat()), static_cast(resource->format)); - return; - } - - const VkFilter blitFilter = isDepthOrStencilTexture - ? VK_FILTER_NEAREST - : ((optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 - ? VK_FILTER_LINEAR - : VK_FILTER_NEAREST); - - VkPipelineStageFlags originalSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags originalSrcAccessMask = 0; - GetImageTransitionSourceState(originalLayout, originalSrcStageMask, originalSrcAccessMask); - - VkPipelineStageFlags finalDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkAccessFlags finalDstAccessMask = 0; - GetImageTransitionDestinationState(finalLayout, finalDstStageMask, finalDstAccessMask); - - if (originalLayout != finalLayout) { - if (storageBaseMipLevel > 0) { - VkImageLayout lowerMipLayout = originalLayout; - const Bool lowerReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, lowerMipLayout, finalLayout, - originalSrcStageMask, finalDstStageMask, - originalSrcAccessMask, finalDstAccessMask, - resource->aspect, 0, storageBaseMipLevel); - MOBILEGL_ASSERT(lowerReady, "%s: failed to transition lower untouched mip levels", __func__); - } - - if (generateMipLevelCount < resource->mipLevels) { - VkImageLayout upperMipLayout = originalLayout; - const Bool upperReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, upperMipLayout, finalLayout, - originalSrcStageMask, finalDstStageMask, - originalSrcAccessMask, finalDstAccessMask, - resource->aspect, generateMipLevelCount, resource->mipLevels - generateMipLevelCount); - MOBILEGL_ASSERT(upperReady, "%s: failed to transition upper untouched mip levels", __func__); - } - } - - VkImageLayout srcMipLayout = originalLayout; - Bool srcReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, srcMipLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - originalSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - originalSrcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, - resource->aspect, storageBaseMipLevel, 1); - MOBILEGL_ASSERT(srcReady, "%s: failed to transition base mip level to transfer source", __func__); - - // Every generated level starts from originalLayout and ends up TRANSFER_DST_OPTIMAL, and - // the loop below only ever moves a level OUT of that layout after it has been written - so - // the whole range can be prepared in one barrier instead of one per level. That turns a - // 12-level chain's 3(N-1)+1 barrier commands into 2(N-1)+2. Each level is still - // individually transitioned to TRANSFER_SRC before it is read, so the write-then-read - // dependency between consecutive levels is unchanged. - if (generateMipLevelCount > storageBaseMipLevel + 1) { - VkImageLayout dstRangeLayout = originalLayout; - const Bool dstRangeReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, dstRangeLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - originalSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, - originalSrcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, - resource->aspect, storageBaseMipLevel + 1, generateMipLevelCount - (storageBaseMipLevel + 1)); - MOBILEGL_ASSERT(dstRangeReady, "%s: failed to transition mip levels to transfer destination", __func__); - } - - for (Uint32 level = storageBaseMipLevel + 1; level < generateMipLevelCount; ++level) { - const IntVec3 srcTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level - 1); - const IntVec3 dstTexelSize = ComputeMipTexelSize(storageBaseTexelSize, level); - - VkImageBlit blitRegion{}; - blitRegion.srcSubresource.aspectMask = resource->aspect; - blitRegion.srcSubresource.mipLevel = level - 1; - blitRegion.srcSubresource.baseArrayLayer = baseArrayLayer; - blitRegion.srcSubresource.layerCount = arrayLayerCount; - blitRegion.srcOffsets[0] = {0, 0, 0}; - blitRegion.srcOffsets[1] = {srcTexelSize.x(), srcTexelSize.y(), srcTexelSize.z()}; - blitRegion.dstSubresource.aspectMask = resource->aspect; - blitRegion.dstSubresource.mipLevel = level; - blitRegion.dstSubresource.baseArrayLayer = baseArrayLayer; - blitRegion.dstSubresource.layerCount = arrayLayerCount; - blitRegion.dstOffsets[0] = {0, 0, 0}; - blitRegion.dstOffsets[1] = {dstTexelSize.x(), dstTexelSize.y(), dstTexelSize.z()}; - - vkCmdBlitImage(frame.commandBuffer, - resource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &blitRegion, blitFilter); - - VkImageLayout finishedSrcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - Bool srcRestored = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, finishedSrcLayout, finalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, finalDstStageMask, - VK_ACCESS_TRANSFER_READ_BIT, finalDstAccessMask, - resource->aspect, level - 1, 1); - MOBILEGL_ASSERT(srcRestored, "%s: failed to transition mip level %u to final layout", __func__, level - 1); - - if (level + 1 < generateMipLevelCount) { - VkImageLayout nextSrcLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - Bool nextSrcReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, nextSrcLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, - resource->aspect, level, 1); - MOBILEGL_ASSERT(nextSrcReady, "%s: failed to prepare mip level %u as next transfer source", __func__, level); - } else { - VkImageLayout lastMipLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - Bool lastMipReady = VkTextureManager::TransitionImageLayout( - frame.commandBuffer, resource->image, lastMipLayout, finalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, finalDstStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, finalDstAccessMask, - resource->aspect, level, 1); - MOBILEGL_ASSERT(lastMipReady, "%s: failed to transition last mip level to final layout", __func__); - } - } - - resource->layout = finalLayout; - - // The chain above is GPU work recorded into this frame's command buffer, which is not - // submitted until the frame ends - but a texture upload goes out on a command buffer of - // its own the moment it happens. A glTexSubImage2D into a level this just generated - // would therefore reach the GPU FIRST and be overwritten by these blits, which is how - // KHR-GL40.texture_gather.base-level lost the texels it wrote into level 1 right after - // generating the chain. Submitting here is what orders the two. - if (HasPendingRecordedWork() && FlushPendingCommands()) { - // Fresh command buffer: the sampled-descriptor-set memo describes bindings that - // only existed in the retired one. - m_lastSampledSetValid = false; - } } // Keyed on the frontend's never-reused lifetime id, NOT on the GL name. The name is @@ -11584,50 +9884,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } return BeginWireXfbCaptureForDraw(frame); - const auto& program = MG_Pipe::gPipeInputs.GetTransformFeedbackProgram(); - if (!program || program->GetTransformFeedbackVaryingCount() == 0) { - return false; - } - const SizeT bufferCount = std::min(program->GetTransformFeedbackBufferCount(), 4); - if (bufferCount == 0) { - return false; - } - - VkBuffer buffers[4] = {}; - VkDeviceSize offsets[4] = {}; - VkDeviceSize sizes[4] = {}; - for (SizeT i = 0; i < bufferCount; ++i) { - if (!program->GetTransformFeedbackStride(static_cast(i))) continue; - auto& point = MG_Pipe::gPipeInputs.GetBufferBindingPoint(BufferTarget::TransformFeedback, - static_cast(i)); - const auto& bufferObject = point.GetBoundObject(); - if (bufferObject == nullptr) { - return false; - } - // Host-visible coherent GPU residency: the capture writes land where - // MapBuffer/GetBufferSubData read. Coherence makes them visible once they - // have happened, so the buffer is also flagged for the wait that a later CPU - // read has to perform - the capture is a GPU write like any shader's. - bufferObject->EnsureGpuResidentStorage(); - // P5c ev (R2, CONTRACT-P5C §4.2): through the reverse channel, not a direct poke - // of the client object from the apply thread. - MG_Pipe::MGPipeAnnounceBufferGpuWritten(bufferObject); - BufferSlice slice{}; - if (!m_bufferManager.AcquireResidentSlice(BufferKind::Vertex, bufferObject, slice)) { - MGLOG_E_ONCE("BeginXfbCaptureForDraw: failed to acquire capture buffer %zu", i); - return false; - } - const Range1D range = point.GetRange(); - const VkDeviceSize rangeStart = static_cast(range.start); - const VkDeviceSize rangeSize = range.end > range.start - ? static_cast(range.end - range.start) - : VK_WHOLE_SIZE; - buffers[i] = slice.buffer; - offsets[i] = slice.offset + rangeStart; - sizes[i] = rangeSize; - } - - return BeginXfbCaptureWithBuffers(frame, static_cast(bufferCount), buffers, offsets, sizes); } Bool VulkanRenderer::BeginXfbCaptureWithBuffers(FrameContext::FrameData& frame, Uint32 bufferCount, diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 9c502324..62408a7c 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -6583,15 +6583,6 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type, bufSize, pixels); return; - if (MG_Backend::pActiveBackendObject != nullptr && - MG_Backend::pActiveBackendObject->GetBackendType() == BackendType::DirectVulkan && - MGL_BACKEND_SLOT_LOCAL(GetTextureImage)) { - MGP_FILL(GetTextureImage); - MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type, - bufSize, pixels); - return; - } - CopyTextureImageToClientOrPBO_State(textureObject, uploadTarget, level, format, type, bufSize, pixels, caller); } void GetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) { @@ -6868,16 +6859,6 @@ namespace MobileGL::MG_Impl::GLImpl { if (!GetTexImage_State(target, level, format, type, pixels)) return; GetTexImage_Backend(target, level, format, type, pixels); return; - if (MGL_BACKEND_SLOT_LOCAL(GetTexImage)) { - GetTexImage_Backend(target, level, format, type, pixels); - return; - } - TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); - TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); - auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); - const auto& textureObject = activeUnit.GetBindingSlot(textureTarget).GetBoundObject(); - CopyTextureImageToClientOrPBO_State(textureObject, textureUploadTarget, level, format, type, -1, pixels, - __func__); } void GetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params) { diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 39712b87..3bf63425 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -2263,10 +2263,6 @@ namespace MobileGL::MG_Pipe { Bool PipeInputs::HasOpenTransformFeedbackSpan(Uint64 lifetimeId) const { return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0; - - MGP_STICKY_FORWARD_PULL(HasOpenTransformFeedbackSpan); - const auto* ctx = LiveContext(); - return ctx != nullptr && ctx->HasOpenTransformFeedbackSpan(lifetimeId); } void PipeInputs::InvalidateCompileEnv() {