Fix mutable images losing pixels across app suspension on Metal (#5153)#5157
Merged
Conversation
…#5153) On the iOS Metal port every mutable image is backed by an MTLStorageModePrivate texture. The system can discard the contents of those textures while the app is suspended in the background, so any mutable image that survives the suspension and is re-displayed without being redrawn samples uninitialized GPU memory on resume. The reported symptom is the FloatingActionButton's cached RoundBorder shadow showing a violet/garbage background after returning from a long pause, but the problem affects every mutable image the app holds, not just the FAB. Fix it at the source: keep a weak registry of every GLUIImage that owns a mutable texture and, on applicationWillResignActive (while the app is still active and GPU use is legal), read each one back into its CPU-side UIImage backing and drop the volatile texture. The texture is rebuilt transparently from that backing the next time the image is painted or sampled - CN1MetalEnsureMutableTexture and GLUIImage.getMTLTexture both re-seed from getImage - so the pixels survive the round trip. - CN1Metalcompat: weak mutable-image registry + CN1MetalBackupMutableImagesForSuspend - GLUIImage: register on mutable-texture assignment, unregister on dealloc - CodenameOne_GLAppDelegate: invoke the backup from cn1ApplicationWillResignActive (covers both the UIScene and legacy app-delegate paths) - tests: mutable-image draw -> read-back round-trip fidelity (the read-back the restore relies on) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 122 screenshots: 122 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Collaborator
Author
|
Compared 122 screenshots: 122 matched. Benchmark Results
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 122 screenshots: 122 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 122 screenshots: 122 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Issue #5153: with Metal active, the
FloatingActionButtonis sometimes drawn with a violet/garbage background after the app returns to the foreground following a long pause.The FAB is just the visible symptom. The real bug is broader: on the iOS Metal port every mutable image is backed by an
MTLStorageModePrivatetexture (CN1MetalEnsureMutableTexture,CN1Metalcompat.m). The system is free to discard the contents of those private textures while the app is suspended. Any mutable image that survives the suspension and is then re-displayed without being redrawn (a cachedRoundBorder/RoundRectBordershadow, an offscreen buffer, a pre-rendered sprite, …) samples uninitialized GPU memory on resume — which renders as violet/garbage. It's intermittent because it depends on the system actually reclaiming that texture memory, which only happens under memory pressure during a long suspension.The GL path never had this because GL textures (and CPU-backed images) survive suspension — this is specific to Metal private-storage textures.
Fix
Preserve mutable-image content across the suspend/resume cycle at the source:
GLUIImagethat currently owns a mutable texture (CN1Metalcompat.m). Registered when a mutable texture is assigned (GLUIImage.setMtlMutableTexture:), auto-dropped via weak refs /dealloc.CN1MetalBackupMutableImagesForSuspend()runs fromcn1ApplicationWillResignActive(covers both the UIScene and legacy app-delegate paths). For each live mutable image it reads the current GPU pixels back into the image's CPU-sideUIImagebacking and drops the volatile texture. This runs while the app is still active, so GPU use is legal (unlikedidEnterBackground, where off-screen GPU work risks termination).UIImagebacking the next time the image is painted or sampled.CN1MetalEnsureMutableTextureandGLUIImage.getMTLTexturealready re-seed fromgetImage, so no eager resume work is needed and content is byte-for-byte preserved.Tests
MutableImageReadbackTest(portableAbstractTest) draws a known pattern into a mutable image and reads it back, asserting pixel fidelity — including accumulation across a second draw. This pins the draw → read-back contract the restore depends on. On JavaSE it covers the generic mutable-image path; on an iOS device build it exercisesCN1MetalReadMutableImagePixels, the exact read-back used by the suspend backup.Verification
CN1_USE_ARCundefined). The native sources are only fully compiled during an actual iOS app build, so on-device verification of the full suspend/resume round trip is still recommended before release.Notes / trade-offs
willResignActive. For a handful of mutable images this is negligible; an app holding many/large mutable images would pay a larger one-time cost per resign. A future optimization could batch the read-backs into fewer command buffers.willResignActivealso fires for transient interruptions (Control Center, notifications). In that case the textures are backed up and lazily rebuilt without harm — correct, just slightly wasteful.willResignActivewas chosen overdidEnterBackgroundspecifically to keep all GPU work in the active state.🤖 Generated with Claude Code