mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Fix] (DirectGLES, DirectVulkan, MG_State): close stale-cache, A-B-A and state-leak holes across the memo layers
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.
This commit is contained in:
@@ -3986,7 +3986,7 @@ void main() {
|
||||
// Skips the per-draw GetBackendResource chase into a cold resource object.
|
||||
Bool sliceStillValid = false;
|
||||
const Uint64 frameSerial = m_bufferManager.GetFrameSerial();
|
||||
if (indexMemo->indexFrameSerial == frameSerial &&
|
||||
if (indexMemo->indexFrameSerial == frameSerial && !indexMemo->indexBufferMapped &&
|
||||
indexMemo->indexSliceEpochCounter == m_bufferManager.GetSliceEpochCounter()) {
|
||||
sliceStillValid = true;
|
||||
}
|
||||
@@ -4049,6 +4049,9 @@ void main() {
|
||||
indexMemo->indexVkBuffer = slice.buffer;
|
||||
indexMemo->indexSliceOffset = slice.offset;
|
||||
indexMemo->indexFrameSerial = m_bufferManager.GetFrameSerial();
|
||||
// A host-mapped EBO can mutate its shadow with no epoch bump; the hit
|
||||
// path declines on this flag (mirror of anyBufferMapped).
|
||||
indexMemo->indexBufferMapped = indexBufferShared->IsMapped();
|
||||
}
|
||||
}
|
||||
const VkDeviceSize indexBindOffset =
|
||||
@@ -5718,6 +5721,22 @@ void main() {
|
||||
if (program.GetBackendStateVersion() != snap.programVersion) {
|
||||
return false;
|
||||
}
|
||||
// glBegin/EndTransformFeedback moves no key this fast path otherwise observes
|
||||
// (the design makes capture a compile-option FLAG precisely because no version
|
||||
// bumps, VulkanRenderer.h's pipeline-memo note) - but the snapshot bakes that
|
||||
// flag into resolvedTransformFlags and the pipeline. Recompute the one dynamic
|
||||
// bit (the full path's exact predicate) and decline on a mismatch, or the first
|
||||
// captured draw after glBeginTransformFeedback would bind the undecorated
|
||||
// variant and silently capture nothing while the CPU bookkeeping advances.
|
||||
const Bool wantsXfbCapture = m_transformFeedbackFeatureEnabled &&
|
||||
MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||
program.GetTransformFeedbackVaryingCount() > 0;
|
||||
const Bool snapHasXfbCapture =
|
||||
static_cast<Bool>(ProgramFactory::CompileOptionFlags(snap.resolvedTransformFlags) &
|
||||
ProgramFactory::CompileOptionBit::XfbCapture);
|
||||
if (wantsXfbCapture != snapHasXfbCapture) {
|
||||
return false;
|
||||
}
|
||||
// A changed VAO does NOT decline: the VAO only feeds the pipeline's vertex
|
||||
// input state (re-resolved below through the layout-keyed memo, so N VAOs
|
||||
// sharing one attribute layout share one pipeline) and the vertex/index
|
||||
@@ -5731,6 +5750,7 @@ void main() {
|
||||
const auto& drawFbo =
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
if (static_cast<const void*>(drawFbo.get()) != snap.drawFbo ||
|
||||
drawFbo->GetLifetimeId() != snap.drawFboLifetimeId ||
|
||||
drawFbo->GetObjectVersion() != snap.fboVersion) {
|
||||
return false;
|
||||
}
|
||||
@@ -5914,8 +5934,14 @@ void main() {
|
||||
}
|
||||
const Uint64 samplingResolutionGeneration = MG_State::pGLContext->GetSamplingResolutionGeneration();
|
||||
if (samplingResolutionGeneration != snap.samplingResolutionGeneration) {
|
||||
snap.samplingResolutionGeneration = samplingResolutionGeneration;
|
||||
samplerDescriptorsUnchanged = false;
|
||||
// Decline, not re-arm: snap.resolvedTransformFlags bakes the
|
||||
// ExplicitLod0Sampling verdict, which reads the effective sampler's
|
||||
// filters/aniso/LOD range - exactly the state this counter tracks.
|
||||
// Re-arming the stamp here would rebuild the descriptors but keep the
|
||||
// stale SPIR-V variant forever (every later draw compares equal again).
|
||||
// Same shape as the erase-epoch declines above; costs one full-path draw
|
||||
// per sampler/shape change, and the full path's LOD memo re-probes.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Everything the full path would re-resolve is provably unchanged - or, for
|
||||
@@ -6095,11 +6121,18 @@ void main() {
|
||||
const Uint64 lodProgramLifetimeId = program.GetLifetimeId();
|
||||
const Uint32 lodProgramVersion = program.GetBackendStateVersion();
|
||||
const Uint64 lodBindGeneration = MG_State::pGLContext->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_State::pGLContext->GetSamplingResolutionGeneration();
|
||||
Bool lodMemoHit = false;
|
||||
if (m_lastLodDecisionValid && m_lastSampledSetValid &&
|
||||
m_lastLodProgramLifetimeId == lodProgramLifetimeId &&
|
||||
m_lastLodProgramVersion == lodProgramVersion &&
|
||||
m_lastLodBindGeneration == lodBindGeneration && m_lastLodBaseFlags == transformFlags &&
|
||||
m_lastLodBindGeneration == lodBindGeneration &&
|
||||
m_lastLodSamplingGeneration == lodSamplingGeneration && m_lastLodBaseFlags == transformFlags &&
|
||||
m_lastSampledSetProgramLifetimeId == lodProgramLifetimeId &&
|
||||
m_lastSampledSetProgramVersion == lodProgramVersion &&
|
||||
m_lastSampledSetBindGeneration == lodBindGeneration) {
|
||||
@@ -6124,6 +6157,7 @@ void main() {
|
||||
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
|
||||
@@ -6467,6 +6501,7 @@ void main() {
|
||||
snap.vaoLifetimeId = vao.GetLifetimeId();
|
||||
snap.vaoConfigVersion = vao.GetConfigVersion();
|
||||
snap.drawFbo = drawFbo.get();
|
||||
snap.drawFboLifetimeId = drawFbo->GetLifetimeId();
|
||||
snap.fboVersion = drawFbo->GetObjectVersion();
|
||||
snap.drawFboIsDefault = drawFboIsDefault;
|
||||
snap.viewportCount = ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin);
|
||||
@@ -13825,6 +13860,15 @@ void main() {
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VK_VERIFY(vkCreateComputePipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline),
|
||||
"GetOrCreateComputePipeline, vkCreateComputePipelines");
|
||||
// A failed creation must never be memoized - same contract as
|
||||
// PipelineFactory::GetOrCreatePipeline: caching the null would serve it back
|
||||
// for the rest of the process and every dispatch of this program would be
|
||||
// silently skipped. Retrying costs one failed vkCreateComputePipelines per
|
||||
// dispatch, which is the correct price.
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
MGLOG_E("GetOrCreateComputePipeline: vkCreateComputePipelines failed; not caching the failure");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
m_computePipelines.emplace(programObj.hash, pipeline);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user