[Perf] (MG_Backend): revive DirectGLES's dead framebuffer-sync guard, and stop probing twice

SyncCurrentFBO has an early-out that compares three memos, and it could never
fire. One of the three, g_fboBindVersions, was only ever stamped by
ForceBindCurrentFBO - which runs from glBlitFramebuffer and the DSA
glClearNamedFramebuffer* paths and nowhere else. An application that touches
neither leaves that memo at 0 while the binding slot's version is at least 1 from
its first glBindFramebuffer, so the first term mismatched forever and the guard
was dead code rather than merely too coarse. Every draw therefore re-walked all
40-odd attachment slots and rebuilt the 8-slot snorm/unorm clamp mask for a
framebuffer that had not changed since the previous draw.

SyncCurrentFBO now stamps all three memos itself, through one helper, on every
path that leaves the target synced - including the default-framebuffer
"nothing to do" path, which previously returned without stamping anything. The
memo is renamed to say what it now records (a sync, not a bind). Instrumenting a
throwaway build put it at 539998 hits against 2 misses, the misses being the
first bind of each target; it was 0 hits before.

Skipping the sync also skips the Bind() inside it, so all eleven call sites were
checked: every one issues its own bind afterwards (PrepareForDraw and the
glClearBuffer* paths bind Draw, ReadPixels and the CopyTexSubImage paths bind
Read, BlitFramebuffer binds both, GetTexImage uses its own scoped binder). The
global snorm/unorm clamp masks written inside the sync stay correct because they
can only be stale if a different framebuffer was synced as Draw in between, which
moves the pointer or slot version and forces the re-sync that rewrites them.
InvalidateFramebufferBindingCache now also clears these memos: both its callers
mean the ES context may have been reset, and a live early-out must not survive
that.

Two smaller items in the same pass. StateBackendObjectRegistry kept the backend
twin and its liveness weak_ptr in two maps, so every lookup cost two hash probes
and the draw path does ten to twenty of them; they are one map with one entry
type now, one probe. The weak_ptr check itself is load-bearing and stays -
glDeleteVertexArrays followed by glGenVertexArrays recycles heap addresses
readily. And SyncNeccessaryBuffers ran the full EnsureBufferResource check once
per enabled vertex attribute, which on an interleaved Minecraft-shaped VAO means
four to eight times over the same VBO; it is deduplicated per distinct buffer now.

ns per draw, DriverBench on a GTX 1660 SUPER, A/B against a build differing only
by this diff: mc_vanilla_draw 1403 -> 1113, mc_ubo_range 983 -> 797,
mc_sampler_churn 2309 -> 2003, mc_sodium_multidraw 3232 -> 3023. Against the
native driver Espryt is now 4.3x on both the plain draw and the per-draw
uniform-range case, from 8.7x and 9.1x at the start of this work.

Unit tests 421/421. Also replayed all 38 locally-available DirectGLES trace
fixtures against a baseline library: every one produced bit-identical ssim and
mismatched-pixel counts, including the improved-transparency OIT trace whose
scratch clear framebuffer is exactly the draw-buffer hazard the code comments
warn about.
This commit is contained in:
BZLZHH
2026-08-06 09:23:47 -04:00
parent 49aab57f03
commit fd40960f70
3 changed files with 205 additions and 159 deletions
+125 -93
View File
@@ -343,13 +343,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
// VBO
// VBO. Once per distinct buffer, not once per enabled attribute: an interleaved
// Minecraft-shaped VAO feeds all of its attributes out of one VBO, so the
// per-attribute loop re-ran the whole resource check (context generation, pending
// ranges, change serial) 4-8 times over the same object. Repeats were pure
// overhead - nothing between two calls can change what the second one would do.
const MG_State::GLState::BufferObject*
syncedBuffers[MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS];
Uint syncedBufferCount = 0;
for (const auto& attrib : currentVAOObject->GetAllAttributes()) {
if (!attrib.Enabled) continue;
auto& bufferObject = attrib.Buffer;
if (bufferObject) {
CreateAndSyncBufferObject(bufferObject);
const auto& bufferObject = attrib.Buffer;
if (!bufferObject) continue;
const auto* bufferKey = bufferObject.get();
Bool alreadySynced = false;
for (Uint i = 0; i < syncedBufferCount; ++i) {
if (syncedBuffers[i] == bufferKey) {
alreadySynced = true;
break;
}
}
if (alreadySynced) continue;
syncedBuffers[syncedBufferCount++] = bufferKey;
CreateAndSyncBufferObject(bufferObject);
}
// IBO
@@ -734,10 +752,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
const auto& backendVAOIt = g_backendVertexArrayObjects.find(currentVAOObject.get());
Bool exist = (backendVAOIt != g_backendVertexArrayObjects.end());
auto& backendObj = exist ? backendVAOIt->second : g_backendVertexArrayObjects.GetOrCreate(currentVAOObject);
if (!exist) {
auto* backendVAOSlot = g_backendVertexArrayObjects.Find(currentVAOObject.get());
auto& backendObj =
backendVAOSlot ? *backendVAOSlot : g_backendVertexArrayObjects.GetOrCreate(currentVAOObject);
if (!backendObj) {
backendObj = MakeShared<VertexArrayImpl::BackendVertexArrayObject>();
}
backendObj->SyncToBackend(currentVAOObject);
@@ -797,10 +815,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
const auto& backendTextureIt = g_backendTextureObjects.find(textureObject.get());
Bool exist = (backendTextureIt != g_backendTextureObjects.end());
auto& backendObj = exist ? backendTextureIt->second : g_backendTextureObjects.GetOrCreate(textureObject);
if (!exist) {
auto* backendTextureSlot = g_backendTextureObjects.Find(textureObject.get());
auto& backendObj = backendTextureSlot ? *backendTextureSlot
: g_backendTextureObjects.GetOrCreate(textureObject);
if (!backendObj) {
backendObj = MakeShared<BackendTextureObject>();
}
if (imageBindableStorageRequired) {
@@ -937,6 +955,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace TextureImpl
namespace FramebufferImpl {
// Record that `target` now reflects this exact (binding, object, revision) triple.
// Every one of the three has to move together: stamping a subset leaves an early-out
// that either never fires or fires on state it never actually pushed.
static void StampSyncedFBO(FramebufferTarget target, Uint16 slotVersion, Uint16 objectVersion,
MG_State::GLState::FramebufferObject* fbo) {
g_fboSyncedSlotVersions[SizeT(target)] = slotVersion;
g_fboSyncedObjectVersions[SizeT(target)] = objectVersion;
g_fboSyncedObjects[SizeT(target)] = fbo;
}
void SyncCurrentFBO() {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
@@ -953,17 +981,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& slot = MG_State::pGLContext->GetFramebufferBindingSlot(target);
auto& currentFBO = slot.GetBoundObject();
// The slot version only tracks rebinds; attachment/drawbuffer edits on an
// already-bound FBO bump its object version and must re-sync it too (e.g.
// Minecraft 26.x reuses one FBO for depth-blit destinations with draw
// buffers NONE and for color clears with draw buffer 0 — dropping the
// glDrawBuffers change turns every offscreen clear into a no-op).
// The three memos together say "this target is already synced": which object is
// bound (pointer), that it is still the same binding and not a recycled address
// (slot version, bumped by every real rebind), and that the object has not been
// edited since (object version, bumped by every attachment/draw-buffer/read-buffer
// change). All three are stamped by every path below that leaves the target synced
// - including the ones that decide there is nothing to do. Leaving the slot version
// to ForceBindCurrentFBO alone made it a permanent mismatch for any app that never
// hits a blit or a DSA clear, so this early-out never fired and a Minecraft-shaped
// frame re-ran the whole attachment walk on all 5495 draws.
const Uint16 slotVersion = slot.GetVersion();
const Uint16 objectVersion = currentFBO ? currentFBO->GetObjectVersion() : 0;
auto* currentPtr = currentFBO.get();
if (slotVersion == g_fboBindVersions[SizeT(target)] &&
if (slotVersion == g_fboSyncedSlotVersions[SizeT(target)] &&
objectVersion == g_fboSyncedObjectVersions[SizeT(target)] &&
currentPtr == g_fboSyncedObjects[SizeT(target)]) {
lastUpdatedFBO = currentPtr;
continue;
}
@@ -974,6 +1007,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (currentFBO == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) {
// Default FBO, nothing to sync
StampSyncedFBO(target, slotVersion, objectVersion, currentPtr);
continue;
}
@@ -986,26 +1020,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
// GL_FRAMEBUFFER — without this every glReadBuffer is a no-op and all reads hit
// COLOR_ATTACHMENT0).
if (target == FramebufferTarget::Read) {
const auto& syncedFBOIt = g_backendFramebufferObjects.find(currentFBO.get());
if (syncedFBOIt != g_backendFramebufferObjects.end() && syncedFBOIt->second) {
syncedFBOIt->second->SyncReadBufferToBackend(currentFBO);
auto* syncedFBOSlot = g_backendFramebufferObjects.Find(currentFBO.get());
if (syncedFBOSlot && *syncedFBOSlot) {
(*syncedFBOSlot)->SyncReadBufferToBackend(currentFBO);
}
}
g_fboSyncedObjectVersions[SizeT(target)] = objectVersion;
g_fboSyncedObjects[SizeT(target)] = currentPtr;
StampSyncedFBO(target, slotVersion, objectVersion, currentPtr);
continue;
}
const auto& backendFBOIt = g_backendFramebufferObjects.find(currentFBO.get());
Bool exist = (backendFBOIt != g_backendFramebufferObjects.end());
auto& backendObj = exist ? backendFBOIt->second : g_backendFramebufferObjects.GetOrCreate(currentFBO);
if (!exist) {
auto* backendFBOSlot = g_backendFramebufferObjects.Find(currentFBO.get());
auto& backendObj =
backendFBOSlot ? *backendFBOSlot : g_backendFramebufferObjects.GetOrCreate(currentFBO);
if (!backendObj) {
backendObj = MakeShared<BackendFramebufferObject>();
}
backendObj->SyncToBackend(currentFBO, target);
g_fboSyncedObjectVersions[SizeT(target)] = objectVersion;
g_fboSyncedObjects[SizeT(target)] = currentPtr;
StampSyncedFBO(target, slotVersion, objectVersion, currentPtr);
lastUpdatedFBO = currentFBO.get();
}
}
@@ -1448,10 +1480,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_fragColorBroadcastCount = std::max<Uint>(enabledDrawBuffers, 1);
}
const auto& backendProgramIt = g_backendProgramObjects.find(currentProgram.get());
Bool exist = (backendProgramIt != g_backendProgramObjects.end());
auto& backendObj = exist ? backendProgramIt->second : g_backendProgramObjects.GetOrCreate(currentProgram);
if (!exist) {
auto* backendProgramSlot = g_backendProgramObjects.Find(currentProgram.get());
auto& backendObj =
backendProgramSlot ? *backendProgramSlot : g_backendProgramObjects.GetOrCreate(currentProgram);
if (!backendObj) {
backendObj = MakeShared<BackendProgramObjectImpl>();
backendObj->SyncToBackend(currentProgram);
} else {
@@ -1486,9 +1518,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// which is where the cost actually is. What is left here is one registry lookup.
const auto& currentFBO = slot.GetBoundObject();
if (currentFBO && currentFBO != MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO) {
const auto& backendFBOIt = FramebufferImpl::g_backendFramebufferObjects.find(currentFBO.get());
if (backendFBOIt != FramebufferImpl::g_backendFramebufferObjects.end()) {
backendFBOIt->second->Bind(target);
auto* backendFBOSlot = FramebufferImpl::g_backendFramebufferObjects.Find(currentFBO.get());
if (backendFBOSlot && *backendFBOSlot) {
(*backendFBOSlot)->Bind(target);
} else {
MGLOG_E("No backend FBO found (maybe not synced) for current %s FBO, cannot bind FBO.",
(target == FramebufferTarget::Read ? "READ" : "DRAW"));
@@ -1514,10 +1546,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
auto& registry = FramebufferImpl::g_backendFramebufferObjects;
const auto& backendFBOIt = registry.find(framebuffer.get());
const Bool exists = backendFBOIt != registry.end();
auto& backendObj = exists ? backendFBOIt->second : registry.GetOrCreate(framebuffer);
if (!exists) {
auto* backendFBOSlot = registry.Find(framebuffer.get());
auto& backendObj = backendFBOSlot ? *backendFBOSlot : registry.GetOrCreate(framebuffer);
if (!backendObj) {
backendObj = MakeShared<FramebufferImpl::BackendFramebufferObject>();
}
if (forceSync) {
@@ -1535,7 +1566,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& slot = MG_State::pGLContext->GetFramebufferBindingSlot(target);
const auto& fbo = slot.GetBoundObject();
SyncAndBindFramebufferObject(fbo, target);
FramebufferImpl::g_fboBindVersions[(SizeT)target] = slot.GetVersion();
FramebufferImpl::g_fboSyncedSlotVersions[(SizeT)target] = slot.GetVersion();
FramebufferImpl::g_fboSyncedObjectVersions[(SizeT)target] = fbo ? fbo->GetObjectVersion() : 0;
FramebufferImpl::g_fboSyncedObjects[(SizeT)target] = fbo.get();
}
@@ -1561,9 +1592,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
#endif
const auto& currentVAO = MG_State::pGLContext->GetBoundVertexArray();
if (currentVAO) {
const auto& backendVAOIt = VertexArrayImpl::g_backendVertexArrayObjects.find(currentVAO.get());
if (backendVAOIt != VertexArrayImpl::g_backendVertexArrayObjects.end()) {
backendVAOIt->second->Bind();
auto* backendVAOSlot = VertexArrayImpl::g_backendVertexArrayObjects.Find(currentVAO.get());
if (backendVAOSlot && *backendVAOSlot) {
(*backendVAOSlot)->Bind();
}
} else {
VertexArrayImpl::BindBackendVAOId(0);
@@ -1671,13 +1702,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
// Bind texture object
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) {
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
if (!backendTextureSlot || !*backendTextureSlot) {
fullyResolved = false;
continue;
}
backendTextureIt->second->Bind(targetGL, unit);
(*backendTextureSlot)->Bind(targetGL, unit);
boundBackendTargets[backendTargetIndex] = true;
claimedByFrontendTarget[backendTargetIndex] = target;
}
@@ -1716,9 +1747,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (Int unit = 0; unit <= maxTouchedUnit; ++unit) {
const auto& samplerObject = MG_State::pGLContext->GetTextureUnitObject(unit).GetSamplerObject();
if (samplerObject) {
const auto& backendSamplerIt = SamplerImpl::g_backendSamplerObjects.find(samplerObject.get());
if (backendSamplerIt != SamplerImpl::g_backendSamplerObjects.end()) {
backendSamplerIt->second->Bind(unit);
auto* backendSamplerSlot = SamplerImpl::g_backendSamplerObjects.Find(samplerObject.get());
if (backendSamplerSlot && *backendSamplerSlot) {
(*backendSamplerSlot)->Bind(unit);
}
} else {
// Symmetric with the bind above: a sampler object left on the unit by an earlier
@@ -1840,9 +1871,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE
ZoneScopedNC("BindCurrentProgram", TRACY_ZONECOLOR_BACKEND);
#endif
const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(currentProgram.get());
if (backendProgramIt != PrgramImpl::g_backendProgramObjects.end()) {
auto& backendProgram = *backendProgramIt->second;
auto* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(currentProgram.get());
if (backendProgramSlot && *backendProgramSlot) {
auto& backendProgram = **backendProgramSlot;
backendProgram.Use();
// Global UBO: block index and binding-point assignment are cached at
@@ -1994,12 +2025,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
GetRawDepthFetchSampler()->Bind(unit);
MGLOG_D("Using raw depth fetch sampler on unit %d.", unit);
} else if (samplerObject) {
const auto& backendSamplerIt =
SamplerImpl::g_backendSamplerObjects.find(samplerObject.get());
Bool exist = (backendSamplerIt != SamplerImpl::g_backendSamplerObjects.end());
auto& backendObj = exist ? backendSamplerIt->second
: SamplerImpl::g_backendSamplerObjects.GetOrCreate(samplerObject);
if (!exist) {
auto* backendSamplerSlot =
SamplerImpl::g_backendSamplerObjects.Find(samplerObject.get());
auto& backendObj = backendSamplerSlot
? *backendSamplerSlot
: SamplerImpl::g_backendSamplerObjects.GetOrCreate(samplerObject);
if (!backendObj) {
backendObj = MakeShared<SamplerImpl::BackendSamplerObject>();
}
backendObj->SyncToBackend(samplerObject);
@@ -2025,9 +2056,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!currentProgram || !currentProgram->GetLinkStatus()) {
return nullptr;
}
const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(currentProgram.get());
if (backendProgramIt != PrgramImpl::g_backendProgramObjects.end()) {
return backendProgramIt->second;
if (auto* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(currentProgram.get())) {
return *backendProgramSlot;
}
return nullptr;
}
@@ -2197,11 +2227,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
return 0;
}
const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(programObject.get());
Bool exist = (backendProgramIt != PrgramImpl::g_backendProgramObjects.end());
auto* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(programObject.get());
auto& backendObj =
exist ? backendProgramIt->second : PrgramImpl::g_backendProgramObjects.GetOrCreate(programObject);
if (!exist) {
backendProgramSlot ? *backendProgramSlot : PrgramImpl::g_backendProgramObjects.GetOrCreate(programObject);
if (!backendObj) {
backendObj = MakeShared<PrgramImpl::BackendProgramObjectImpl>();
}
if (!backendObj->GetBackendProgramId()) {
@@ -2361,9 +2390,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
PrepareForDraw(syncBit);
const auto& currentVAO = MG_State::pGLContext->GetBoundVertexArray();
if (currentVAO) {
const auto& backendVAOIt = VertexArrayImpl::g_backendVertexArrayObjects.find(currentVAO.get());
if (backendVAOIt != VertexArrayImpl::g_backendVertexArrayObjects.end()) {
backendVAOIt->second->SyncClientSideAttributesForDrawArrays(currentVAO, first, count);
auto* backendVAOSlot = VertexArrayImpl::g_backendVertexArrayObjects.Find(currentVAO.get());
if (backendVAOSlot && *backendVAOSlot) {
(*backendVAOSlot)->SyncClientSideAttributesForDrawArrays(currentVAO, first, count);
}
}
g_GLESFuncs.glDrawArrays(mode, first, count);
@@ -2390,9 +2419,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (GLsizei i = 0; i < drawcount; ++i) {
// Client-side arrays are uploaded per sub-draw range, like the single DrawArrays path.
if (currentVAO) {
const auto& backendVAOIt = VertexArrayImpl::g_backendVertexArrayObjects.find(currentVAO.get());
if (backendVAOIt != VertexArrayImpl::g_backendVertexArrayObjects.end()) {
backendVAOIt->second->SyncClientSideAttributesForDrawArrays(currentVAO, first[i], count[i]);
auto* backendVAOSlot = VertexArrayImpl::g_backendVertexArrayObjects.Find(currentVAO.get());
if (backendVAOSlot && *backendVAOSlot) {
(*backendVAOSlot)->SyncClientSideAttributesForDrawArrays(currentVAO, first[i], count[i]);
}
}
g_GLESFuncs.glDrawArrays(mode, first[i], count[i]);
@@ -3401,11 +3430,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ConvertTextureTargetToString(textureTarget).c_str());
}
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
Bool exist = (backendTextureIt != TextureImpl::g_backendTextureObjects.end());
auto& backendObj =
exist ? backendTextureIt->second : TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject);
if (!exist) {
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
auto& backendObj = backendTextureSlot
? *backendTextureSlot
: TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject);
if (!backendObj) {
backendObj = MakeShared<TextureImpl::BackendTextureObject>();
}
backendObj->Bind(TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget), unit);
@@ -3682,17 +3711,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
const auto backendTextureIt = TextureImpl::g_backendTextureObjects.find(texture.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end() || !backendTextureIt->second) {
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(texture.get());
if (!backendTextureSlot || !*backendTextureSlot) {
return;
}
const GLuint backendTextureId = backendTextureIt->second->GetBackendTextureId();
const GLuint backendTextureId = (*backendTextureSlot)->GetBackendTextureId();
for (auto it = FramebufferImpl::g_backendFramebufferObjects.begin();
it != FramebufferImpl::g_backendFramebufferObjects.end(); ++it) {
auto* stateFBO = it->first;
const auto& backendFBO = it->second;
if (stateFBO == nullptr || !backendFBO || stateFBO->IsDefaultFramebuffer()) {
const auto& backendFBO = it->second.backend;
// An entry whose state object died is only waiting for the next collection;
// the key is a dangling address, so it must not be dereferenced here.
if (stateFBO == nullptr || !backendFBO || it->second.stateRef.expired() ||
stateFBO->IsDefaultFramebuffer()) {
continue;
}
@@ -3977,13 +4009,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& textureObject = MG_State::pGLContext->GetTextureUnitObject((Int)activeTextureUnit)
.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target))
.GetBoundObject();
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) {
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
if (!backendTextureSlot || !*backendTextureSlot) {
MGLOG_E("CopyTexSubImage2D: No backend texture found for texture %u.",
textureObject ? textureObject->GetExternalIndex() : 0);
return;
}
backendTextureIt->second->Bind(target, activeTextureUnit);
(*backendTextureSlot)->Bind(target, activeTextureUnit);
auto mgInternalFormat = textureObject->GetFormat();
GLenum format = GL_DEPTH_COMPONENT;
@@ -4019,7 +4051,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
});
auto currentTex = (GLint)backendTextureIt->second->GetBackendTextureId();
auto currentTex = (GLint)(*backendTextureSlot)->GetBackendTextureId();
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
});
@@ -4072,13 +4104,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& textureObject = MG_State::pGLContext->GetTextureUnitObject(activeTextureUnit)
.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target))
.GetBoundObject();
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) {
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
if (!backendTextureSlot || !*backendTextureSlot) {
MGLOG_E("CopyTexSubImage2D: No backend texture found for texture %u.",
textureObject ? textureObject->GetExternalIndex() : 0);
return;
}
backendTextureIt->second->Bind(target, activeTextureUnit);
(*backendTextureSlot)->Bind(target, activeTextureUnit);
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
@@ -4100,7 +4132,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
});
} else {
MGLOG_D("%s: Backend depth", __func__);
auto currentTex = backendTextureIt->second->GetBackendTextureId();
auto currentTex = (*backendTextureSlot)->GetBackendTextureId();
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
});
@@ -5416,15 +5448,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("GetTexImage: bound texture object = %p (name=%u)", textureObject.get(),
textureObject ? textureObject->GetExternalIndex() : 0);
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) {
if (!backendTextureSlot || !*backendTextureSlot) {
MGLOG_E("GetTexImage: No backend texture found for texture %u.",
textureObject ? textureObject->GetExternalIndex() : 0);
return;
}
GLuint backendTexId = backendTextureIt->second->GetBackendTextureId();
GLuint backendTexId = (*backendTextureSlot)->GetBackendTextureId();
MGLOG_D("GetTexImage: backend texture id = %u", backendTexId);
// Force pending rendering to complete before reading the texture back through the temp READ FBO.
+31 -26
View File
@@ -2740,6 +2740,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
void InvalidateFramebufferBindingCache() {
g_driverFBOBindings = {0, 0};
g_driverFBOBindingKnown = {false, false};
// The per-target "already synced" memo describes work pushed into the ES context
// that is being left or replaced. Both callers (MakeCurrent, DestroyEGLContext)
// mean the context may have been reset under us, so claim nothing is synced:
// a null object never matches a real binding, so SyncCurrentFBO re-pushes.
g_fboSyncedSlotVersions = {0};
g_fboSyncedObjectVersions = {0};
g_fboSyncedObjects = {};
}
void BackendFramebufferObject::InvalidateSyncedState() {
@@ -2768,15 +2775,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (attachmentObject.IsTexture()) {
const auto& textureObject = attachmentObject.GetTexture();
SharedPtr<TextureImpl::BackendTextureObject> backendTextureObject;
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) {
auto& backendTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject);
if (!backendTextureSlot) {
backendTextureSlot = MakeShared<TextureImpl::BackendTextureObject>();
}
backendTextureObject = backendTextureSlot;
if (auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get())) {
backendTextureObject = *backendTextureSlot;
} else {
backendTextureObject = backendTextureIt->second;
auto& newTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreate(textureObject);
if (!newTextureSlot) {
newTextureSlot = MakeShared<TextureImpl::BackendTextureObject>();
}
backendTextureObject = newTextureSlot;
}
if (!backendTextureObject) {
MGLOG_E("%s: No backend texture found for FBO attachment, cannot bind texture.", __func__);
@@ -2818,18 +2824,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
} else if (attachmentObject.IsRenderbuffer()) {
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
const auto& backendRenderbufferIt =
RenderbufferImpl::g_backendRenderbufferObjects.find(renderbufferObject.get());
SharedPtr<RenderbufferImpl::BackendRenderbufferObject> backendRenderbufferObject;
if (backendRenderbufferIt == RenderbufferImpl::g_backendRenderbufferObjects.end()) {
auto& backendRenderbufferSlot =
RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreate(renderbufferObject);
if (!backendRenderbufferSlot) {
backendRenderbufferSlot = MakeShared<RenderbufferImpl::BackendRenderbufferObject>();
}
backendRenderbufferObject = backendRenderbufferSlot;
if (auto* backendRenderbufferSlot =
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get())) {
backendRenderbufferObject = *backendRenderbufferSlot;
} else {
backendRenderbufferObject = backendRenderbufferIt->second;
auto& newRenderbufferSlot =
RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreate(renderbufferObject);
if (!newRenderbufferSlot) {
newRenderbufferSlot = MakeShared<RenderbufferImpl::BackendRenderbufferObject>();
}
backendRenderbufferObject = newRenderbufferSlot;
}
backendRenderbufferObject->SyncToBackend(renderbufferObject);
@@ -3189,10 +3194,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Verify that the backend object's name and parameters match the frontend attachment state
if (attachmentObject.IsTexture()) {
const auto& textureObject = attachmentObject.GetTexture();
auto backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
MOBILEGL_ASSERT(backendTextureIt != TextureImpl::g_backendTextureObjects.end(),
auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.Find(textureObject.get());
MOBILEGL_ASSERT(backendTextureSlot != nullptr && *backendTextureSlot != nullptr,
"No backend texture found while framebuffer reports texture attachment.");
GLuint backendTexId = backendTextureIt->second->GetBackendTextureId();
GLuint backendTexId = (*backendTextureSlot)->GetBackendTextureId();
MOBILEGL_ASSERT(static_cast<GLint>(backendTexId) == objectName,
"Attachment texture name mismatch between GLES (%d) and backend texture object "
"(%d), frontend texture object ID=%d.",
@@ -3205,12 +3210,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
"Attachment texture level mismatch between GLES and state object.");
} else if (attachmentObject.IsRenderbuffer()) {
const auto& renderbufferObject = attachmentObject.GetRenderbuffer();
auto backendRboIt =
RenderbufferImpl::g_backendRenderbufferObjects.find(renderbufferObject.get());
auto* backendRboSlot =
RenderbufferImpl::g_backendRenderbufferObjects.Find(renderbufferObject.get());
MOBILEGL_ASSERT(
backendRboIt != RenderbufferImpl::g_backendRenderbufferObjects.end(),
backendRboSlot != nullptr && *backendRboSlot != nullptr,
"No backend renderbuffer found while framebuffer reports renderbuffer attachment.");
GLuint backendRboId = backendRboIt->second->GetBackendRenderbufferId();
GLuint backendRboId = (*backendRboSlot)->GetBackendRenderbufferId();
MOBILEGL_ASSERT(static_cast<GLint>(backendRboId) == objectName,
"Attachment renderbuffer name mismatch between GLES and state object.");
}
@@ -3236,7 +3241,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
g_backendFramebufferObjects;
Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions = {0};
Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedSlotVersions = {0};
// Tracks the bound FBO's object version (bumped on any attachment/drawbuffer change)
// per target: re-attaching textures or changing draw buffers on an already-bound FBO
// must re-sync it even when the binding-slot version has not moved.
+49 -40
View File
@@ -27,39 +27,54 @@ namespace MobileGL::MG_Backend::DirectGLES {
using StatePtr = SharedPtr<StateObject>;
using StateWeakPtr = std::weak_ptr<StateObject>;
using BackendPtr = SharedPtr<BackendObject>;
using BackendMap = UnorderedMap<StateObject*, BackendPtr>;
using StateRefMap = UnorderedMap<StateObject*, StateWeakPtr>;
// The backend twin and the weak reference that decides whether the raw key still
// names the state object the twin was built for. Both live in one entry: a
// separate liveness map answered nothing the backend probe had not already found
// and cost a second hash lookup on every Find, which the draw path runs ~10 times.
struct Entry {
BackendPtr backend;
StateWeakPtr stateRef;
};
using BackendMap = UnorderedMap<StateObject*, Entry>;
using iterator = typename BackendMap::iterator;
using const_iterator = typename BackendMap::const_iterator;
BackendPtr& GetOrCreate(const StatePtr& stateObj) {
MOBILEGL_ASSERT(stateObj != nullptr, "State object must not be null");
auto* key = stateObj.get();
auto trackedStateIt = m_stateRefs.find(key);
if (trackedStateIt != m_stateRefs.end() && trackedStateIt->second.expired()) {
EraseByKey(key);
auto& entry = m_entries[stateObj.get()];
if (entry.stateRef.expired()) {
// The previous owner of this address is gone and the allocator handed it
// to a new object: its twin describes ids the new state object never made.
entry.backend.reset();
}
m_stateRefs[key] = stateObj;
return m_backendObjects[key];
entry.stateRef = stateObj;
return entry.backend;
}
iterator find(StateObject* stateObj) {
if (!IsAlive(stateObj)) {
EraseByKey(stateObj);
return m_backendObjects.end();
// Null when no live state object owns this key. The result points into the map, so
// it stays valid only until the next GetOrCreate/Find/CollectGarbage on this registry.
BackendPtr* Find(StateObject* stateObj) {
const auto entryIt = m_entries.find(stateObj);
if (entryIt == m_entries.end()) {
return nullptr;
}
return m_backendObjects.find(stateObj);
if (entryIt->second.stateRef.expired()) {
m_entries.erase(entryIt);
return nullptr;
}
return &entryIt->second.backend;
}
const_iterator find(StateObject* stateObj) const {
return const_cast<StateBackendObjectRegistry*>(this)->find(stateObj);
const BackendPtr* Find(StateObject* stateObj) const {
return const_cast<StateBackendObjectRegistry*>(this)->Find(stateObj);
}
iterator begin() { return m_backendObjects.begin(); }
const_iterator begin() const { return m_backendObjects.begin(); }
iterator end() { return m_backendObjects.end(); }
const_iterator end() const { return m_backendObjects.end(); }
iterator begin() { return m_entries.begin(); }
const_iterator begin() const { return m_entries.begin(); }
iterator end() { return m_entries.end(); }
const_iterator end() const { return m_entries.end(); }
void CollectGarbageIfNeeded() {
++m_gcTick;
@@ -73,19 +88,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
void CollectGarbageNow() { CollectGarbage(); }
private:
bool IsAlive(StateObject* stateObj) const {
const auto trackedStateIt = m_stateRefs.find(stateObj);
if (trackedStateIt == m_stateRefs.end()) {
return false;
}
return !trackedStateIt->second.expired();
}
void EraseByKey(StateObject* stateObj) {
m_stateRefs.erase(stateObj);
m_backendObjects.erase(stateObj);
}
void CollectGarbage() {
if (m_isCollecting) {
return;
@@ -94,16 +96,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_isCollecting = true;
Vector<StateObject*> staleKeys;
staleKeys.reserve(m_stateRefs.size());
for (const auto& [stateKey, stateWeakRef] : m_stateRefs) {
if (stateWeakRef.expired()) {
staleKeys.reserve(m_entries.size());
for (const auto& [stateKey, entry] : m_entries) {
if (entry.stateRef.expired()) {
staleKeys.push_back(stateKey);
}
}
for (auto* stateKey : staleKeys) {
m_stateRefs.erase(stateKey);
m_backendObjects.erase(stateKey);
m_entries.erase(stateKey);
}
m_isCollecting = false;
@@ -111,8 +112,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
private:
static constexpr Uint32 kGCInterval = 1024;
StateRefMap m_stateRefs;
BackendMap m_backendObjects;
BackendMap m_entries;
Uint32 m_gcTick = 0;
Bool m_isCollecting = false;
};
@@ -484,11 +484,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
// has to apply the clamp itself.
Bool IsFixedPointFallbackReadAttachment();
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
// What SyncCurrentFBO last pushed for each target, as a (binding, object, revision)
// triple; it re-syncs unless all three still match. Stamped by SyncCurrentFBO and
// ForceBindCurrentFBO, cleared by InvalidateFramebufferBindingCache. The three are
// only meaningful together - see SyncCurrentFBO.
//
// The binding slot's own version, which changes whenever a different object is bound
// to this target. Distinguishes a rebind from an in-place edit, and keeps the raw
// pointer below from matching an address the allocator recycled for a new FBO.
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedSlotVersions;
// Tracks the bound FBO's object version (bumped on any attachment/drawbuffer change)
// per target: re-attaching textures or changing draw buffers on an already-bound FBO
// must re-sync it even when the binding-slot version has not moved.
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedObjectVersions;
// Which object was synced. Raw and never dereferenced: only compared for identity.
extern Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
g_fboSyncedObjects;