Repository navigation
Fix stale framebuffer attachments, leaked renderbuffers, and nonzero-default copies - #2984
toaster0123 wants to merge 2 commits into
Conversation
Track native framebuffer attachments and all owned renderbuffers across target mutation, allocation failure, explicit deletion, managed cleanup, and context reset. Preserve existing storage and guard stale references when native names are reused. Use discovered presentation framebuffer names for copies, restoration, and overrides. Build directly on upstream master without the mipmap/null-storage or GLES3 capability changes from PR jMonkeyEngine#2983. Add 34 recording-GL regressions in three dedicated test classes. Local core/effects validation: 522 passed, one existing test skipped, zero failures.
jaime-jmebot
left a comment
There was a problem hiding this comment.
Nice work on this one — the three fixes map cleanly onto real API expectations, and the recording-GL tests (attachments, renderbuffer lifetimes, blit endpoints) are the right kind of proof for this: they assert the actual GL calls rather than just Java-side bookkeeping.
A few things worth a look:
NativeObjectManagerqueue guard — the newif (refMap.get(ref.objClone.getUniqueId()) != ref) continue;is a good guard, but it dereferencesobjClonebefore checking it's still there. SinceunregisterObjectnow clears the reference, a stale queuedReferencecould reach this branch withobjClone == null. A cheapref.objClone == null ||on the same condition makes it null-safe with no behavior change otherwise.- Track map cleanup — could you confirm the renderbuffer bookkeeping keyed by framebuffer ID is also dropped in
deleteFrameBuffer, alongside theglDeleteFramebuffersEXTcall? If an entry survives and a later framebuffer is handed the same GL name, it would inherit renderbuffer IDs it never allocated — the exact id-reuse hazard this PR already guards against inrefMap. - Minor nits (no action strictly required):
FrameBuffer.clearColorTargets(): theif (colorBufIndex >= 0)branch reads like a leftover from an older version — the field starts at0, so the condition is always true andcolorBufIndex = 0;could be a plain assignment.- In
mipmappedReplacementGeneratesOldRenderedTextureBeforeAllocatingNewOne, the old texture's mipmaps are generated twice (once when the slot is replaced, again on unbind). Harmless, but it's a fullglGenerateMipmapon a texture nothing will read again.
When this gets combined with #2983, the mipmap loop is the one spot worth merging by hand: keep this PR's actual-attachment snapshot iteration with #2983's image-binding/generation body, as you noted.
|
I checked these points against f1646c1 and reran the 34 attachment/lifecycle/default-framebuffer regressions; all pass.
For the eventual combination with #2983, agreed: retain this PR's actual-attachment snapshot iteration and #2983's direct image binding, mip-range restoration and generated-state update. The standalone attachment test checks texture IDs; it does not claim to cover #2983's storage-redefinition or pixel-preservation behavior. No source change was needed for these review points. Both current-head GitHub workflows also pass. |
What is broken
This fixes three framebuffer API behaviors: changing a render target can leave OpenGL using the old attachment, disposing a framebuffer can leave its renderbuffers undeleted, and copying to/from the screen can use the wrong framebuffer when the platform's default ID is nonzero.
1. Replacing or removing an initialized attachment does not update OpenGL
Trigger: Render into a framebuffer, then change its targets through
replaceColorTarget,removeColorTarget, or the related add/depth setters. A caller expects the nextsetFrameBuffer(fb)to apply the new targets.For example, after
fbhas been initialized with texture A:Before: The recording-GL reproduction still has A attached. The setters do not mark the framebuffer for an update; switching away and back does not help. Removal has a second bug: even after an explicit
fb.setUpdateNeeded(), the removed color slot remains attached because the update code only visits surviving targets. Rendering can therefore go to the old texture, and the native attachments can disagree with the Java target list.Fix: Mark target/MRT changes for update, explicitly detach removed slots, and reattach surviving renderbuffers when their slot changes. Keep the selected color target valid after removal. When switching away, generate mipmaps for the textures that were actually attached, rather than a newly replaced Java target. Regression tests assert that B becomes attached and removed slots become zero.
2. Framebuffer disposal misses owned renderbuffers
Trigger: Dispose a framebuffer with several color renderbuffers, or let the native-object manager clean it up. Callers expect every renderbuffer owned by that framebuffer to be released.
Before, measured in the GL-call recording:
fb.dispose(); renderer.postFrame();deletes only 2, leaving the second color renderbuffer live.renderer.cleanup()deletes the framebuffer but 0 renderbuffers: the manager's destructible framebuffer clone contains no attachment information.These missed deletes can retain resources while the GL context remains alive. Copying attachment IDs into the clone once is insufficient: registration occurs before initial attachment allocation, and targets may change later.
Fix: Track allocated renderbuffers in renderer-owned state keyed by framebuffer ID, without retaining the owning
FrameBuffer. Direct disposal, managed cleanup, and the GC-queue path can then delete all owned IDs, including late additions, removed targets not yet rebound, and allocations made before a later failure. Unregister direct deletions and reject stale queued references so a reused GL ID does not cause deletion of a new object. Tests now record all 3/3 and 2/2 deletions, and also cover context reset, ID reuse, and simulated GC cleanup. Texture attachments remain separately owned.3. Screen copies bind framebuffer zero instead of the discovered default
Trigger: The platform starts with framebuffer 77 bound, so initialization records 77 as the screen framebuffer. With no main-framebuffer override, a null copy endpoint should refer to that same framebuffer.
Before: Normal screen binding uses 77, but at
glBlitFramebufferEXTthe recorded draw/read endpoint is 0. The copy therefore targets a different framebuffer; it cannot be relied on to copy to/from the presentation surface.Fix: Use the discovered default ID for null copy endpoints, and keep initialization, restoration, bound-framebuffer deletion, and main-framebuffer overrides consistent with it. The tests now record 77 at the relevant blit endpoint and verify restoration; the default-zero control still passes.
Validation
git diff --checkcompleted. Existing checkstyle warnings remain; no new warnings in the added tests or production lines. Independent review found no blocking issues.These tests record GL calls, attachment IDs, and resource lifetimes. They do not rasterize pixels or measure driver memory. No native GPU, iOS hardware, or visual-output validation is claimed.
Relation to #2983
This branch is based directly on master and excludes #2983's mipmap null-storage fix, GLES3 half-float change, and their tests. It passes independently. Combining the PRs has one expected textual overlap in the mipmap loop: retain this PR's actual-attachment snapshot iteration and #2983's image-binding/generation body.
The earlier fork branch
fix/framebuffer-attachment-resource-lifecycleis retained unchanged; this independent branch supersedes it for upstream review.