Fix Mac native screenshots to capture the genuine Metal display pipeline#5159
Merged
Merged
Conversation
The cn1ss screenshot helper special-cased Mac native (Mac Catalyst) to bypass Display.screenshot() and instead paint the current form into an off-screen mutable image. That hid a real framework bug and silently degraded coverage: because the off-screen re-paint cannot capture native peers, the committed BrowserComponent mac-native baseline was a blank page, and any screenshot() staleness on Catalyst went untested. Root cause: CodenameOne_GLViewController.drawFrame early-returns whenever the app is not UIApplicationStateActive, doing no render at all. That gate is correct on iOS (a backgrounded app must not touch the GPU) but wrong on Mac Catalyst, where an app keeps drawing its window while it is not the focused application. During a long headless screenshot run the Catalyst app loses "active" status, after which every drawFrame is a no-op and the Metal screenTexture (which the screenshot reads back) freezes on the last active frame for the rest of the suite. Fix, all gated to Mac Catalyst / desktop so iOS device + simulator paths are untouched: - CodenameOne_GLViewController.m: on Catalyst only skip rendering when the app is truly backgrounded, not merely inactive, so screenTexture stays current. (root cause) - IOSNative.m: capture by reading the Metal screenTexture back into a CGImage -- the exact pixels presentFramebuffer blits to the drawable -- via a blit on the renderer's own command queue (FIFO-ordered, then waitUntilCompleted). This is the genuine on-screen content and does not depend on a CADisplayLink present cycle (which never fires headless). - IOSImplementation.screenshot(): before the native capture, force the current form through the real EDT screen-render path (paintComponent to the screen graphics + flushGraphics, exactly what paintDirty() runs) so a static form's texture is fresh. Genuine Metal draw path, not an off-screen re-paint. - Cn1ssDeviceRunnerHelper: remove the off-screen paintComponent hack; Mac native now uses Display.screenshot() like every other port. Re-baselined scripts/mac-native/screenshots from the genuine pipeline (now full 2x display resolution; BrowserComponent shows the real title bar with a blocked/black peer area, which is expected on Catalyst). Verified on a local Mac Catalyst build: 122/122 screenshots render as distinct, genuinely-rendered frames (was ~13 distinct due to the freeze), and an independent strict-comparison run reports 122/122 equal. 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 61 screenshots: 61 matched. Benchmark Results
|
Collaborator
Author
|
Compared 94 screenshots: 94 matched. |
Collaborator
Author
|
Compared 122 screenshots: 122 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 52 screenshots: 52 matched. Benchmark Results
Build and Run Timing
|
…ine-screenshot-capture # Conflicts: # scripts/mac-native/screenshots/LightweightPickerButtons.png # scripts/mac-native/screenshots/LightweightPickerButtons_above_center.png # scripts/mac-native/screenshots/LightweightPickerButtons_below_right.png # scripts/mac-native/screenshots/LightweightPickerButtons_between_mixed.png # scripts/mac-native/screenshots/ValidatorLightweightPicker.png
The genuine screenshot pipeline captures at the host's native scale; my local Retina Mac produced 2x (2048x1370) images while the macos CI runner renders at 1x (1024x685), and GPU rasterization differs between them, so locally-generated goldens cannot pixel-match CI. Adopt the CI runner's own 1024x685 output as the golden set (the authoritative reference for the comparison), restoring the established 1x baseline resolution. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
The cn1ss screenshot helper special-cased Mac native (Mac Catalyst) to bypass
Display.screenshot()and instead paint the current form into an off-screen mutable image. This hid a real framework bug and silently degraded coverage:BrowserComponentmac-native baseline was a blank page (vs the real rendered HTML on the iOS-Metal baseline).screenshot()staleness/correctness issue on Catalyst went completely untested, because the test no longer exercised that path.Root cause
CodenameOne_GLViewController.drawFrameearly-returns whenever the app is notUIApplicationStateActive, doing no render at all:That gate is correct on iOS (a backgrounded app must not touch the GPU) but wrong on Mac Catalyst, where an app keeps drawing its window while it isn't the focused application. During a long headless screenshot run the Catalyst app loses "active" status, after which every
drawFrameis a no-op and the MetalscreenTexture(which the screenshot reads back) freezes on the last active frame for the rest of the suite. The paint hack sidestepped this by never touching the screen pipeline at all.Fix
All changes are gated to Mac Catalyst / desktop, so iOS device + simulator paths are untouched.
CodenameOne_GLViewController.m(root cause) — on Catalyst, only skip rendering when the app is truly backgrounded, not merely inactive, soscreenTexturestays current.IOSNative.m— capture by reading the MetalscreenTextureback into aCGImage(the exact pixelspresentFramebufferblits to the drawable), via a blit on the renderer's own command queue (FIFO-ordered, thenwaitUntilCompleted). Genuine on-screen content, noCADisplayLinkdependency.IOSImplementation.screenshot()— before the native capture, force the current form through the real EDT screen-render path (paintComponentto the screen graphics +flushGraphics, exactly whatpaintDirty()runs) so a static form's texture is fresh. Genuine Metal draw path, not an off-screen re-paint.Cn1ssDeviceRunnerHelper— remove the off-screen paint hack; Mac native now usesDisplay.screenshot()like every other port.Re-baselined
scripts/mac-native/screenshots/from the genuine pipeline (now full 2× display resolution;BrowserComponentshows the real title bar with a blocked/black peer area, which is expected on Catalyst).Verification (local Mac Catalyst build)
drawFramegate fixIndependent strict-comparison run against the new baseline (
CN1SS_FAIL_ON_MISMATCH=1): 122/122equal— deterministic. Spot-checked visually:graphics-fill-rectshows its real rects (was a MorphTransition frame), tail theme screens render their own content (were frozen on FAB-dark),BrowserComponentshows the genuine title bar with a black peer area.🤖 Generated with Claude Code