mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
Audit of every memoization implementation; sixteen verified defects fixed: DirectGLES backend: - Broadcast draw-buffer memo: cleared at MakeCurrent/DestroyEGLContext like its sibling shadows; its identity+version key is only monotonic within one GLContext, so a library teardown + re-init could false-hit on a recycled FBO address. - Backend texture id re-mint (RecreateBackendTexture) now bumps an attachment generation that the SyncCurrentFBO gate and every FBO twin compare, so driver FBOs re-attach instead of keeping the deleted texture name; the attachment walk re-enters until the generation is quiescent (a walk itself can re-mint). - Buffer id re-mint (persistent-map adoption, immutable-store retire) now bumps a generation the VAO twin sync compares, forcing a full re-emit of the baked glVertexAttribPointer / element-array bindings that frontend versions cannot see. - VAO element-array sync memo: bound-object identity joins the wrapping Uint16 slot version (same pairing the ResolvedDrawBuffers IBO memo already uses). DirectVulkan backend: - EBO slice memo gains the mapped-buffer guard its vertex-binding sibling has: a shadow-backed persistent map mutates with no epoch bump, so a hit must decline. - VkClearManager::MergeClearPayload keeps colorEncoding/colorInt/colorUint with the color, so deferred glClearBufferiv/uiv no longer degrade to all-zero float. - GetOrCreateComputePipeline no longer memoizes a failed creation (same contract as PipelineFactory): a transient driver failure was permanently disabling every dispatch of that program. - Explicit-LOD-0 verdict memo keys on the sampling-resolution generation; sampler filter/aniso/LOD setters bump only that counter, so the old key served a stale verdict (wrong SPIR-V variant) after glTexParameter/glSamplerParameter changes. - SetupDraw fast path declines instead of re-arming on a moved sampling-resolution generation (the snapshot bakes the LOD verdict into its pipeline), and recomputes the XfbCapture bit so the first draw after glBeginTransformFeedback cannot bind the undecorated variant and silently capture nothing. - VertexInputStateFactory eviction epoch is drawn from a process-wide source: VAO state-pointer memos outlive the factory across renderer recreation, and a fresh factory restarting at epoch 1 would dereference a dead factory's entry. - Cached render passes re-read the live renderbuffer clear payload at begin (the clear VALUE is not in the pass hash; the entry's inline snapshot replayed the creation-time color and dropped the newly queued one). - FramebufferObject gains a never-reused lifetime id, keyed into the render-pass fast-path memo and the SetupDraw snapshot beside the raw pointer + Uint16 version pair, which address reuse plus fresh version counts could equal. - SyncTextureResource's preserved-content image goes through the deferred-release ring on both failure paths instead of a synchronous destructor under the GPU. MG_State frontend: - Layer-1 compile memo is env-disciplined like layers 2/3: a node computed against a dead CompileEnv (e.g. pre-capability fallback limits) no longer answers glCompileShader forever once the environment's content changes. - Pipeline composite cache rebuilds from each stage program's last-link shader snapshot (new LinkedShaderRef list + pinned link inputs) instead of the live attach list and current compile nodes: post-link glAttachShader/glCompileShader must not leak into the composite while the (lifetimeId, linkVersion) signature still hits - GL's "as last linked" rule.
218 lines
8.8 KiB
C++
218 lines
8.8 KiB
C++
// MobileGL - MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#include "FramebufferObject.h"
|
|
#include "MG_Util/Types.h"
|
|
|
|
#include <atomic>
|
|
|
|
namespace MobileGL::MG_State::GLState {
|
|
// Starts at 1 so a zero-initialized memo slot can never carry a live object's id.
|
|
// Atomic for the same reason as the VAO counter: it costs nothing, and a duplicate
|
|
// id would resurrect exactly the ABA this id exists to kill.
|
|
static std::atomic<Uint64> s_nextFramebufferLifetimeId{1};
|
|
|
|
Uint64 FramebufferObject::AllocateLifetimeId() {
|
|
return s_nextFramebufferLifetimeId.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
// FramebufferAttachmentObject
|
|
FramebufferAttachmentObject::FramebufferAttachmentObject(
|
|
const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget textureUploadTarget, Int level,
|
|
Int layer, Bool layered)
|
|
: m_texture(texture), m_textureUploadTarget(textureUploadTarget), m_textureLevel(level),
|
|
m_textureLayer(layer), m_layered(layered) {}
|
|
FramebufferAttachmentObject::FramebufferAttachmentObject(const SharedPtr<RenderbufferObject>& renderbuffer)
|
|
: m_renderbuffer(renderbuffer) {}
|
|
FramebufferAttachmentObject::FramebufferAttachmentObject(Bool IsValid)
|
|
: m_texture(nullptr), m_renderbuffer(nullptr) {
|
|
m_isValid = IsValid;
|
|
}
|
|
|
|
Bool FramebufferAttachmentObject::IsTexture() const {
|
|
return m_texture != nullptr;
|
|
}
|
|
|
|
Bool FramebufferAttachmentObject::IsRenderbuffer() const {
|
|
return m_renderbuffer != nullptr;
|
|
}
|
|
|
|
Bool FramebufferAttachmentObject::IsEmpty() const {
|
|
return m_texture == nullptr && m_renderbuffer == nullptr;
|
|
}
|
|
|
|
const SharedPtr<MG_State::GLState::ITextureObject>& FramebufferAttachmentObject::GetTexture() const {
|
|
return m_texture;
|
|
}
|
|
|
|
const SharedPtr<RenderbufferObject>& FramebufferAttachmentObject::GetRenderbuffer() const {
|
|
return m_renderbuffer;
|
|
}
|
|
|
|
Int FramebufferAttachmentObject::GetTextureLevel() const {
|
|
return m_textureLevel;
|
|
}
|
|
|
|
Int FramebufferAttachmentObject::GetTextureLayer() const {
|
|
return m_textureLayer;
|
|
}
|
|
|
|
Bool FramebufferAttachmentObject::IsLayered() const {
|
|
return m_layered;
|
|
}
|
|
|
|
TextureUploadTarget FramebufferAttachmentObject::GetTextureUploadTarget() const {
|
|
return m_textureUploadTarget;
|
|
}
|
|
|
|
Bool FramebufferAttachmentObject::IsComplete() const {
|
|
if (IsTexture()) {
|
|
Bool complete = m_texture->IsComplete();
|
|
return complete;
|
|
} else if (IsRenderbuffer()) {
|
|
Bool complete = m_renderbuffer->IsAllocated();
|
|
return complete;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
IntVec3 FramebufferAttachmentObject::GetSize() const {
|
|
if (IsTexture()) {
|
|
MOBILEGL_ASSERT(nullptr != static_cast<MG_State::GLState::TextureObjectMipmap*>(m_texture.get()),
|
|
"Texture object here should always be an object with mipmap");
|
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(m_texture.get());
|
|
TextureUploadTarget resolvedTarget = m_textureUploadTarget;
|
|
if (resolvedTarget == TextureUploadTarget::Unknown) {
|
|
const auto& uploadTargets = m_texture->GetUploadTargets();
|
|
MOBILEGL_ASSERT(!uploadTargets.empty(),
|
|
"FramebufferAttachmentObject::GetSize: textureId=%u exposes no upload targets",
|
|
m_texture->GetExternalIndex());
|
|
resolvedTarget = uploadTargets[0];
|
|
}
|
|
return textureMipmapObject->GetMipmapTexelSize(resolvedTarget, m_textureLevel);
|
|
} else if (IsRenderbuffer()) {
|
|
return {m_renderbuffer->GetWidth(), m_renderbuffer->GetHeight(), 1};
|
|
}
|
|
return {0, 0, 0};
|
|
}
|
|
|
|
Bool FramebufferAttachmentObject::IsValid() const {
|
|
return m_isValid;
|
|
}
|
|
|
|
// FramebufferObject
|
|
FramebufferObject::FramebufferObject(Uint externalIndex)
|
|
: m_externalIndex(externalIndex), m_attachmentVersions{}, m_drawBuffers{} {
|
|
m_attachmentObjects.fill(FramebufferAttachmentObject(false));
|
|
m_drawBuffers.fill(FramebufferAttachmentType::None);
|
|
const FramebufferAttachmentType defaultColorBuffer =
|
|
(externalIndex == 0) ? FramebufferAttachmentType::BackLeft : FramebufferAttachmentType::Color0;
|
|
m_drawBuffers[0] = defaultColorBuffer;
|
|
m_readBuffer = defaultColorBuffer;
|
|
m_attachmentVersions.fill(0);
|
|
}
|
|
|
|
void FramebufferObject::AttachTexture(FramebufferAttachmentType type, const SharedPtr<ITextureObject>& texture,
|
|
TextureUploadTarget textureUploadTarget, int level, int layer, Bool layered) {
|
|
m_attachmentObjects[static_cast<SizeT>(type)] =
|
|
FramebufferAttachmentObject(texture, textureUploadTarget, level, layer, layered);
|
|
BumpAttachmentVersion(type);
|
|
}
|
|
|
|
void FramebufferObject::AttachRenderbuffer(FramebufferAttachmentType type,
|
|
const SharedPtr<RenderbufferObject>& renderbuffer) {
|
|
m_attachmentObjects[static_cast<SizeT>(type)] = FramebufferAttachmentObject(renderbuffer);
|
|
BumpAttachmentVersion(type);
|
|
}
|
|
|
|
void FramebufferObject::Detach(FramebufferAttachmentType type) {
|
|
m_attachmentObjects[static_cast<SizeT>(type)] = FramebufferAttachmentObject(false);
|
|
BumpAttachmentVersion(type);
|
|
}
|
|
|
|
const FramebufferAttachmentObject& FramebufferObject::GetAttachment(FramebufferAttachmentType type) const {
|
|
return m_attachmentObjects[static_cast<SizeT>(type)];
|
|
}
|
|
|
|
const FramebufferObject::FramebufferAttachmentObjectArray& FramebufferObject::GetAllAttachmentObjects() const {
|
|
return m_attachmentObjects;
|
|
}
|
|
|
|
Bool FramebufferObject::CheckCompleteness() const {
|
|
if (m_attachmentObjects.empty()) {
|
|
return false;
|
|
}
|
|
|
|
Int width = -1, height = -1;
|
|
Int validAttachmentCount = 0;
|
|
for (const auto& attachmentObject : m_attachmentObjects) {
|
|
if (!attachmentObject.IsValid()) continue;
|
|
|
|
++validAttachmentCount;
|
|
const auto& attachment = attachmentObject;
|
|
auto attachmentSize = attachment.GetSize();
|
|
Int w = attachmentSize.x();
|
|
Int h = attachmentSize.y();
|
|
|
|
if (width == -1) {
|
|
width = w;
|
|
height = h;
|
|
} else if (width != w || height != h) {
|
|
return false;
|
|
}
|
|
|
|
if (!attachment.IsComplete()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (validAttachmentCount == 0) return false;
|
|
return true;
|
|
}
|
|
|
|
void FramebufferObject::SetDrawBuffer(Uint index, FramebufferAttachmentType buffer) {
|
|
if (m_drawBuffers[index] == buffer) return;
|
|
m_drawBuffers[index] = buffer;
|
|
BumpAttachmentVersion(buffer);
|
|
}
|
|
|
|
const FramebufferObject::FramebufferAttachmentArray& FramebufferObject::GetDrawBuffers() const {
|
|
return m_drawBuffers;
|
|
}
|
|
|
|
void FramebufferObject::SetReadBuffer(FramebufferAttachmentType buf) {
|
|
if (m_readBuffer == buf) return;
|
|
m_readBuffer = buf;
|
|
++m_objectVersion;
|
|
}
|
|
|
|
Uint FramebufferObject::GetExternalIndex() const {
|
|
return m_externalIndex;
|
|
}
|
|
|
|
#define MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(name, member, type) \
|
|
void FramebufferObject::Set##name(type value) { \
|
|
if (member == value) return; \
|
|
member = value; \
|
|
++m_objectVersion; \
|
|
}
|
|
|
|
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultWidth, m_defaultWidth, Int)
|
|
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultHeight, m_defaultHeight, Int)
|
|
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultLayers, m_defaultLayers, Int)
|
|
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultSamples, m_defaultSamples, Int)
|
|
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultFixedSampleLocations, m_defaultFixedSampleLocations, Bool)
|
|
#undef MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER
|
|
|
|
void FramebufferObject::BumpAttachmentVersion(FramebufferAttachmentType type) {
|
|
++m_attachmentVersions[static_cast<SizeT>(type)];
|
|
++m_objectVersion;
|
|
}
|
|
} // namespace MobileGL::MG_State::GLState
|