iOS Metal: fix stale framebuffer on rotation (#4954)#4961
Merged
Conversation
viewWillTransitionToSize: fires before UIKit updates the view's bounds, so METALView.updateFrameBufferSize: -- which ignored its caller-supplied size and read self.bounds -- saw the previous orientation's dimensions, matched the cached framebuffer size, and early-returned without recreating screenTexture, the projection matrix or the stencil texture. The CAMetalLayer drawable is meanwhile auto-resized by UIKit, so the subsequent drawFrame blits the old-sized persistent screenTexture into the new-sized drawable: the previous orientation's content lands in a corner and the remaining pixels read back uninitialised, producing the smeared/pink frames in the issue. Trust the parameters (now consistently physical pixels: the three GLViewController callers multiply by scaleValue) and only fall back to bounds when the caller passes 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Collaborator
Author
|
Compared 107 screenshots: 107 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 107 screenshots: 107 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.
Summary
Fixes #4954 — Metal rendering shows smeared / pink frames when an app is launched in one orientation and rotated to another.
Root cause
viewWillTransitionToSize:inCodenameOne_GLViewController.mfires before UIKit updates the view's bounds. It forwards the upcoming size toMETALView.updateFrameBufferSize:h:, but that function ignored its parameters and readself.bounds.sizeinstead — which still reflects the previous orientation at that lifecycle point. The function then sees(pw, ph) == (framebufferWidth, framebufferHeight)and early-returns without recreating anything.Result:
screenTexture(the persistentMTLLoadActionLoadrender target) stays at the previous orientation's size.projectionMatrix,stencilTexture,framebufferWidth/Heightall stay stale.drawableSizeis auto-resized by UIKit, so the nextdrawFrameblits the old-sizedscreenTextureinto a new-sized drawable. The previous orientation's content lands in a corner, the rest of the drawable reads uninitialised, and the user sees smeared/pink frames.layoutSubviewseventually fires with correct bounds and self-heals, which is why the artefact is transient — but the bad frame(s) reach the screen on real hardware.Fix
Two parts, both surgical:
METALView.m—updateFrameBufferSize:h:now trusts the caller-supplied physical-pixel dimensions and only falls back toself.boundswhen the caller passes 0. Old comment about ignoring the parameters has been updated to reflect the new contract and the rotation race that motivates it.CodenameOne_GLViewController.m— three call sites (viewWillTransitionToSize:,didRotateFromInterfaceOrientation:, andupdateCanvas:) now multiply byscaleValueso the parameter is unambiguously in physical pixels.EAGLView.updateFrameBufferSize:is a no-op so the GL path is unaffected; the existingMETALViewcallers (initWithCoder,layoutSubviews) already passed physical pixels.Why no screenshot test
We discussed adding a launch-in-landscape → rotate-to-portrait screenshot test, but the artefact only shows up in the 1–2 frames between
viewWillTransitionToSize:andlayoutSubviews(i.e. before the self-heal). Catching that window reliably from a CI screenshot harness would be flaky; theOrientationLockScreenshotTestalready waits 1500 ms for layout to settle precisely because earlier captures produce run-over-run jitter. The fix is small, local, and ships with the diagnosis in the commit message so a future regression can be traced quickly.Test plan
codename1.arg.ios.metal=true, launch in landscape, rotate to portrait — confirm no smear/pink frame.build-ios-metal) — no goldens should drift; the changed path is only exercised on actual rotation.🤖 Generated with Claude Code