Skip to content

iOS: cut idle memory and startup cost in the Metal renderer and the VM - #5598

Open
shai-almog wants to merge 7 commits into
masterfrom
ios-memory-perf
Open

iOS: cut idle memory and startup cost in the Metal renderer and the VM#5598
shai-almog wants to merge 7 commits into
masterfrom
ios-memory-perf

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

What this is

Six independent memory/startup fixes in the iOS Metal renderer and the VM, plus
the diagnostics that found them. Four are default-on, one is opt-in, and the
tooling is compiled out unless you define it.

Every number below is a paired A/B inside a single binary, toggled by an env
var. That matters more than it sounds: the same binary's physical footprint
varies by tens of megabytes between launches depending on machine state, so
"measure, change code, measure again" cannot resolve a 5MB effect and will
happily report noise as a win. Two of the fixes below were previously dismissed
on exactly that kind of measurement.

Default-on

Stencil attachments become Memoryless on tile-based GPUs. Every pass that
binds the stencil is already loadAction=Clear / storeAction=DontCare, which
is precisely the Memoryless contract. The code chose Private and reasoned the
cost was "tiny (1 byte/pixel)" — it is 5.6MB per allocation at 2048x1536.
~3.8MB, 3/3 paired reps, Metal validation clean. Intel Macs and the
Intel-Mac CI simulators keep Private via a runtime family probe.

The glyph atlas starts at 256x256 rather than 1024x1024. The atlas cache
keys on postScriptName|SIZE, so every distinct text size reserved a megabyte
of R8 before rasterising a single glyph — and a Material-style text theme has
fifteen of them across two or three families. tryGrowAtlas already doubles on
demand and correctly drops slots so the next reference re-rasterises, so
starting small is self-correcting. 6.19MB -> 0.47MB, and no atlas in the
test app ever needed to grow.

cn1SetupMetal no longer sizes the framebuffer before layout. It read
self.bounds, and an unlaid-out view reports the whole display: every launch
allocated a 3456x2234 / 30MB screen texture, cleared it, blitted it into the
real 2048x1536 one and threw it away. layoutSubviews always follows and sizes
it correctly, and createRenderPassDescriptor already treats a nil
screenTexture as "no frame this pass". ~14MB, and it removes a +-20MB
launch-to-launch swing that was making every other measurement in this area
untrustworthy.

Eager accessibility projection is gated on assistive technology running.
The port sets isAccessibilityTreeSupported() = true and never overrode
isAccessibilityTreeUpdateRequired(), which defaults to returning it — so the
portable semantic tree was rebuilt on every invalidation: every layout,
every scroll, every text setter, on every device, whether or not VoiceOver was
listening. The base class documents the opposite for pull-based ports, and UIKit
pulls. Measured at 4.0MB of live allocation on an idle app with no assistive
technology running at all, ~6MB of footprint, plus the CPU to build it.
Turning VoiceOver on mid-session is picked up on the next invalidation.

This last one affects every Codename One iOS application, not only the one it
was found on.

Opt-in: CN1_DIRECT_DRAWABLE=1

A direct-to-drawable path with no retained screen texture — the frame's pass
targets the drawable itself and present is a bare presentDrawable with no
blit. ~8-11MB and ~11ms off first frame (13 paired reps, t = 2.74, lower in
11/13).

The layer vends a different buffer each frame, so partial repaint is incorrect
there — a region left unpainted shows a frame from two or three presents ago.
IOSImplementation.paintDirty therefore enqueues the whole Form whenever
anything is dirty, reusing two behaviours that already exist rather than
rewriting paintDirty: a component queued with a null dirty region already
paints under a full-screen clip, and repaint(Animation) already drops a child
whose ancestor is queued, so it collapses the queue instead of growing it.
isDirectToDrawable() asks the renderer rather than deciding independently, so
the two halves cannot disagree about which buffer is being drawn into.

It also sets maximumDrawableCount to 2 in that mode only, where the third
slot really is taken (21.8MB vs a 21.8/27.2MB swing). The cap is inert in the
retained path — that was measured twice, and the comment now says so.

Diagnostics (compiled out unless defined)

CN1_ALLOC_CENSUS — allocation volume by class, counted at all three
allocation entry points, so unlike a walk of allObjectsInHeap it does not
silently miss the BiBOP and nursery objects, which is exactly where small
high-churn objects live. Plus cn1HeapAccounting, which separates Java-object
storage from native allocation — vmmap cannot, because BiBOP arenas and every
Metal/CoreGraphics buffer share the same malloc zones.

CN1_TEXTURE_CENSUS — GPU memory by creation site, logging each large texture's
true dimensions. Dimensions matter: a per-site cumulative total divided by its
allocation count invents a texture that does not exist, and that fabricated
figure sent me down a wrong path until the real sizes were printed.

CN1_VERIFY_PRESENT — reads back a patch of the drawable actually being
presented
. This is the only thing that can prove a renderer change did not
blank the screen: with a nil drawable the render pass is nil, every op no-ops
against a null encoder, and Metal validation stays clean while FIRSTFRAME
still prints. An offscreen repaint-based screenshot cannot catch it either,
because it repaints the scene graph instead of reading the framebuffer.

Verification

  • hellocodenameone builds as a Mac native target with zero errors, both
    with default settings and with all three diagnostics defined (the latter is
    necessary — a default build compiles straight past the guarded code).
  • Metal API validation clean in both render paths.
  • Retained and direct paths produce the identical presented-frame hash.

🤖 Generated with Claude Code

Six independent fixes, each measured with a paired A/B inside one binary
(separate builds cannot resolve these: the same binary's footprint varies by
tens of megabytes between launches depending on machine state, which is how
several of these went unnoticed).

Default-on:

* Stencil attachments become Memoryless on tile-based GPUs. Every pass that
  binds the stencil is already loadAction=Clear / storeAction=DontCare, which
  is exactly the Memoryless contract; the code chose Private and reasoned the
  cost was "tiny (1 byte/pixel)". It is 5.6MB per allocation at 2048x1536.
  Worth ~3.8MB, 3/3 paired reps, Metal validation clean. Intel Macs and the
  Intel-Mac CI simulators still get Private via a runtime family probe.

* The glyph atlas starts at 256x256 instead of 1024x1024. The atlas cache keys
  on postScriptName|SIZE, so every distinct text size reserved a megabyte of R8
  before rasterising a single glyph, and a Material-style text theme has fifteen
  of them. tryGrowAtlas already doubles on demand and re-rasterises correctly,
  so starting small is self-correcting. 6.19MB -> 0.47MB, and no atlas in the
  test app ever needed to grow.

* cn1SetupMetal no longer sizes the framebuffer from self.bounds before layout.
  An unlaid-out view reports the whole DISPLAY, so every launch allocated a
  3456x2234 / 30MB screen texture, cleared it, blitted it into the real
  2048x1536 one and threw it away. layoutSubviews always follows and sizes it
  correctly. Worth ~14MB, and it removes a +-20MB launch-to-launch swing that
  made every other memory measurement in this area unreliable.

* Eager accessibility projection is gated on assistive technology actually
  running. The port sets isAccessibilityTreeSupported() = true and never
  overrode isAccessibilityTreeUpdateRequired(), which defaults to returning it,
  so the portable semantic tree was rebuilt on EVERY invalidation -- every
  layout, every scroll, every text setter -- on every device, whether or not
  VoiceOver was listening. The base class documents the opposite for pull-based
  ports, and UIKit pulls. 4.0MB of live allocation on an idle app with no
  assistive technology, ~6MB of footprint, plus the CPU to build it.

Opt-in (CN1_DIRECT_DRAWABLE=1):

* A direct-to-drawable render path with no retained screen texture: the frame's
  pass targets the drawable itself and present is a bare presentDrawable with no
  blit. Because the layer vends a different buffer each frame, partial repaint
  is incorrect there, so IOSImplementation.paintDirty enqueues the whole Form
  whenever anything is dirty -- reusing two existing behaviours rather than
  rewriting paintDirty. Worth ~8-11MB and ~11ms off first frame. Also sets
  maximumDrawableCount to 2 in that mode only, where the third slot really is
  taken (the cap is inert in the retained path).

Diagnostics, compiled out unless defined:

* CN1_ALLOC_CENSUS -- allocation volume by class, counted at all three
  allocation entry points so it does not silently miss the BiBOP and nursery
  objects a walk of allObjectsInHeap cannot see, plus cn1HeapAccounting, which
  separates Java-object storage from native allocation (vmmap cannot: BiBOP
  arenas and every Metal/CG buffer share the same malloc zones).

* CN1_TEXTURE_CENSUS -- GPU memory by creation site with each large texture's
  true dimensions, and CN1_VERIFY_PRESENT, which reads back a patch of the
  drawable actually being presented. That last one matters: a renderer change
  can leave the screen blank while Metal validation stays clean and an
  offscreen repaint-based screenshot still looks perfect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1451368cd6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/iOSPort/nativeSources/METALView.m Outdated
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java
Comment thread Ports/iOSPort/nativeSources/IOSNative.m Outdated
Comment thread Ports/iOSPort/nativeSources/METALView.m
… probe

General ParparVM memory/startup work that has been sitting unmerged, plus the
fixes for the first review pass.

* Lazy string constant pool. Every string constant in the binary was
  materialised into a java.lang.String before main ran -- on a large transpiled
  application, 38,238 of them. They are now created on first reference.

* The force-visited table allocates its entries from arena blocks and prunes on
  sweep, instead of growing without bound and doing per-entry allocation on the
  hot path.

* CN1_STARTUP_PHASES, a compiled-out probe that times the phases before the
  first frame, and the heap histogram alongside the allocation census.

Review fixes:

* cn1StencilStorageMode probes respondsToSelector: before sending
  supportsFamily:. That selector is iOS 13, ios.deployment_target is a build
  hint, and IPhoneBuilder will emit targets well below it -- where the message
  would have terminated the app during view initialisation.

* updateFrameBufferSize: returns early in direct mode instead of building a
  render pass with a nil colour attachment and no explicit dimensions. That is
  invalid Metal; it survived only because the encoder came back nil and every
  message to it was a no-op. The polygon-clip stencil both paths need is
  factored into buildStencilTextureForWidth:height:layer:.

* The assistive-technology check adds AssistiveTouch, and the VoiceOver /
  SwitchControl / AssistiveTouch status notifications now force a projection.
  Without that the native tree -- which is push-only, populated solely by
  accessibilityTreeChanged -> updateAccessibilityTree -- stayed empty when a
  technology started after a static screen was already up.

  UIKit publishes running flags for exactly those three and nothing else, so no
  flag can describe Voice Control or Full Keyboard Access. Rather than leave
  those users without a tree, any status notification latches eager projection
  on for the rest of the process: one process's worth of projection is the right
  side to err on for an accessibility feature.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79971b3cce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/iOSPort/nativeSources/IOSNative.m Outdated
Comment thread Ports/iOSPort/nativeSources/METALView.m
@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 64ms / native 5ms = 12.8x speedup
SIMD float-mul (64K x300) java 75ms / native 5ms = 15.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 197.000 ms
Base64 CN1 decode 136.000 ms
Base64 SIMD encode 102.000 ms
Base64 encode ratio (SIMD/CN1) 0.518x (48.2% faster)
Base64 SIMD decode 104.000 ms
Base64 decode ratio (SIMD/CN1) 0.765x (23.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 29.000 ms
Image createMask (SIMD on) 68.000 ms
Image createMask ratio (SIMD on/off) 2.345x (134.5% slower)
Image applyMask (SIMD off) 77.000 ms
Image applyMask (SIMD on) 48.000 ms
Image applyMask ratio (SIMD on/off) 0.623x (37.7% faster)
Image modifyAlpha (SIMD off) 49.000 ms
Image modifyAlpha (SIMD on) 35.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.714x (28.6% faster)
Image modifyAlpha removeColor (SIMD off) 50.000 ms
Image modifyAlpha removeColor (SIMD on) 37.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.740x (26.0% faster)

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 528 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 13961 ms

  • Hotspots (Top 20 sampled methods):

    • 22.26% com.codename1.tools.translator.Parser.addToConstantPool (303 samples)
    • 6.39% java.util.ArrayList.indexOf (87 samples)
    • 4.19% com.codename1.tools.translator.ByteCodeClass.fillVirtualMethodTable (57 samples)
    • 3.38% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (46 samples)
    • 3.16% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (43 samples)
    • 2.28% com.codename1.tools.translator.Parser.classIndex (31 samples)
    • 2.20% com.codename1.tools.translator.BytecodeMethod.optimize (30 samples)
    • 2.13% org.objectweb.asm.tree.analysis.Analyzer.analyze (29 samples)
    • 2.06% java.lang.Object.hashCode (28 samples)
    • 1.91% java.lang.System.identityHashCode (26 samples)
    • 1.91% java.lang.StringBuilder.append (26 samples)
    • 1.62% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (22 samples)
    • 1.54% com.codename1.tools.translator.BytecodeMethod.equals (21 samples)
    • 1.25% java.lang.String.equals (17 samples)
    • 1.25% java.util.HashMap.hash (17 samples)
    • 1.18% java.io.UnixFileSystem.getBooleanAttributes0 (16 samples)
    • 1.10% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (15 samples)
    • 1.03% sun.nio.fs.UnixNativeDispatcher.open0 (14 samples)
    • 0.96% com.codename1.tools.translator.BytecodeMethod.addInstruction (13 samples)
    • 0.96% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (13 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 66ms / native 4ms = 16.5x speedup
SIMD float-mul (64K x300) java 73ms / native 5ms = 14.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 196.000 ms
Base64 CN1 decode 147.000 ms
Base64 SIMD encode 100.000 ms
Base64 encode ratio (SIMD/CN1) 0.510x (49.0% faster)
Base64 SIMD decode 100.000 ms
Base64 decode ratio (SIMD/CN1) 0.680x (32.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 52.000 ms
Image createMask (SIMD on) 17.000 ms
Image createMask ratio (SIMD on/off) 0.327x (67.3% faster)
Image applyMask (SIMD off) 43.000 ms
Image applyMask (SIMD on) 27.000 ms
Image applyMask ratio (SIMD on/off) 0.628x (37.2% faster)
Image modifyAlpha (SIMD off) 28.000 ms
Image modifyAlpha (SIMD on) 20.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.714x (28.6% faster)
Image modifyAlpha removeColor (SIMD off) 31.000 ms
Image modifyAlpha removeColor (SIMD on) 21.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.677x (32.3% faster)

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300) java 57ms / native 4ms = 14.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 245.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.265x (73.5% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.492x (50.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 14.000 ms
Image createMask (SIMD on) 51.000 ms
Image createMask ratio (SIMD on/off) 3.643x (264.3% slower)
Image applyMask (SIMD off) 24.000 ms
Image applyMask (SIMD on) 19.000 ms
Image applyMask ratio (SIMD on/off) 0.792x (20.8% faster)
Image modifyAlpha (SIMD off) 18.000 ms
Image modifyAlpha (SIMD on) 11.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.611x (38.9% faster)
Image modifyAlpha removeColor (SIMD off) 22.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.591x (40.9% faster)

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

…gy flags

The previous round gated eager projection on UIAccessibilityIsVoiceOverRunning /
IsSwitchControlRunning / IsAssistiveTouchRunning and latched on their status
notifications. Those are the only running flags UIKit publishes, so nothing in
that set can see Voice Control or Full Keyboard Access -- and when one of them is
already enabled at launch no notification fires either, so the latch never
engaged and those users got no tree at all.

METALView now overrides accessibilityElements. UIKit asks a container for its
elements only when something is actually consuming the semantic tree, so the
query itself is the signal that a client exists, and it depends on no
per-technology flag. The first query may see a tree that has not been projected
yet; noting the client schedules that projection and the resulting
layout-changed notification brings the client back for the real one.

Not covered: CodenameOne_GLViewController re-roots self.view to a plain UIView
when a peer component is added mid-transition, and the elements are then set on
that view instead. There the gate falls back to the flags and notifications --
the behaviour without this hook, not something worse. Noted at the override.

Also documents why cn1_copyMetalScreenTextureImage returns NULL in
direct-to-drawable mode: there is no retained screen texture to read, so
Display.screenshot() falls through to drawViewHierarchyInRect:. That is correct
on device but samples the presented drawable, so it can lag a frame and, on
headless Catalyst with no display link, can be stale indefinitely. Reading the
live drawable is not the fix -- retaining it past present starves nextDrawable,
and after present the buffer is recycled. A deterministic capture wants a
one-shot render into a scratch target, worth doing when the mode stops being
opt-in. The default path is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d98105c56

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/iOSPort/nativeSources/IOSNative.m Outdated
Comment thread Ports/iOSPort/nativeSources/CN1MetalGlyphAtlas.m
…ility APIs

Two fixes from review.

The shelf packer only ever tested growth against HEIGHT. That was safe while the
atlas started at 1024x1024 and glyphs were capped at CN1_METAL_ATLAS_GLYPH_MAX
(256): a fresh shelf always had room across, so the missing width test could
never fire. Starting the atlas at 256 makes it reachable with ordinary glyphs --
a 256-wide glyph placed at x=1 in a 256-wide texture runs past the edge, and the
region handed to replaceRegion: is out of bounds. Growth is now driven by a loop
that tests both dimensions and terminates either by fitting or by hitting the
growth ceiling, where the glyph is dropped exactly as an oversized one already
was.

UIAccessibilityAssistiveTouchStatusDidChangeNotification and
UIAccessibilityIsAssistiveTouchRunning() are iOS 10, and ios.deployment_target
lets IPhoneBuilder emit older targets. On those the weakly-linked constant is nil
-- and a nil inside an @[] literal raises, so the observer registration would
have taken the app down on the first accessibility invalidation -- while the
running check would call through a null symbol. The notification list is now
built up skipping absent constants, and the running check tests the function
pointer before calling it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0669758bc4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/iOSPort/nativeSources/METALView.m Outdated
…r restart

blurScreenRegionX, glassScreenRegionX and lensScreenRegionX each end the frame's
encoder and reopen one on the SAME drawable via setFramebuffer. Direct mode set
loadAction=Clear unconditionally, so every one of those effects wiped everything
painted before it and presented only the effect and whatever followed.

The restart site already carried the comment "loadAction Load preserves
screenTexture" -- true of the retained path, and exactly the assumption direct
mode broke.

Only the first pass of a frame clears now: directFrameCleared is reset when a
fresh drawable is vended (its contents are two or three presents old, so there
is nothing to preserve) and set once the clear has happened, after which
mid-frame restarts load.

Why the earlier verification missed it: direct mode was checked by comparing the
presented-frame hash against the retained path, which matched exactly -- on a
screen with no glass or blur component in view.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2ddeaa43c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vm/ByteCodeTranslator/src/cn1_globals.h Outdated
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java Outdated
Comment thread vm/ByteCodeTranslator/src/nativeMethods.m
…fixes

All four are in code this PR introduces.

* The lazy constant pool was a data race. The writer stored under
  constantPoolMutex while every fast-path reader loaded plain, and those readers
  never take the mutex -- so it serialised writers and established nothing with
  them. A reader could observe the published pointer while the String's fields
  were still invisible, which on arm64 is not theoretical. Entries are now
  released by the writer and acquired by readers, the GC's mark scan included:
  the collector marks through that pointer and must see a constructed object
  behind it.

* EAGLView gets the same accessibilityElements override METALView has.
  CodenameOne_GLViewController installs EAGLView whenever CN1_USE_METAL is
  absent, so on the GL backend the query latch was never installed and every
  portable-tree invalidation was discarded. Both call sites are gated on
  !TARGET_OS_WATCH, matching the note function, or the watch build fails to
  link.

* paintDirty paints the full form directly instead of enqueueing it. repaint(f)
  appends and the superclass drains in order, so the full-frame paint landed on
  top of overlay animations already queued -- Container.TransitionAnimation
  queues its Transition through Display.repaint(t), and painting the form over
  it makes a component transition vanish or snap to its end state. The
  background has to go down first and the queue drain on top of it.

* Class.getSuperclass() returns null for an interface. A class file records
  java/lang/Object as an interface's super_class and Parser.visit copies it into
  baseClass, so the isInterface flag is the only thing that separates them --
  the function's own comment already claimed this behaviour while the code
  returned Object.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30d4c327e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java
…le clear

Follow-up to the previous commit, which fixed the transition ordering by
painting the Form directly instead of enqueueing it -- and left
super.paintDirty() still deriving its flush rectangle from the queued components
alone.

That rectangle is not advisory. CodenameOne_GLViewController hands it to
ClipRect.setDrawRect and the Metal path clamps every screen op to it. So with a
single partially dirty Component queued, direct mode cleared the ENTIRE drawable,
repainted the whole Form, and then clipped that paint to the component's rect --
presenting a mostly black frame.

Two changes each correct alone: clearing the whole drawable is right for a buffer
that is two or three presents old, and a partial flush rect is right when only
dirty regions are repainted. Direct mode makes them mutually exclusive -- clearing
everything obliges you to flush everything -- and flushGraphics is where the two
meet, so it widens to the full screen there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1626 seconds

Build and Run Timing

Metric Duration
Simulator Boot 96000 ms
Simulator Boot (Run) 1000 ms
App Install 18000 ms
App Launch 1000 ms
Test Execution 626000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 166ms / native 3ms = 55.3x speedup
SIMD float-mul (64K x300) java 183ms / native 4ms = 45.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 805.000 ms
Base64 CN1 decode 283.000 ms
Base64 native encode 1849.000 ms
Base64 encode ratio (CN1/native) 0.435x (56.5% faster)
Base64 native decode 1778.000 ms
Base64 decode ratio (CN1/native) 0.159x (84.1% faster)
Base64 SIMD encode 313.000 ms
Base64 encode ratio (SIMD/CN1) 0.389x (61.1% faster)
Base64 SIMD decode 196.000 ms
Base64 decode ratio (SIMD/CN1) 0.693x (30.7% faster)
Base64 encode ratio (SIMD/native) 0.169x (83.1% faster)
Base64 decode ratio (SIMD/native) 0.110x (89.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 149.000 ms
Image createMask (SIMD on) 30.000 ms
Image createMask ratio (SIMD on/off) 0.201x (79.9% faster)
Image applyMask (SIMD off) 492.000 ms
Image applyMask (SIMD on) 350.000 ms
Image applyMask ratio (SIMD on/off) 0.711x (28.9% faster)
Image modifyAlpha (SIMD off) 202.000 ms
Image modifyAlpha (SIMD on) 199.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.985x (1.5% faster)
Image modifyAlpha removeColor (SIMD off) 354.000 ms
Image modifyAlpha removeColor (SIMD on) 210.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.593x (40.7% faster)

@shai-almog

shai-almog commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 2083 seconds

Build and Run Timing

Metric Duration
Simulator Boot 106000 ms
Simulator Boot (Run) 1000 ms
App Install 17000 ms
App Launch 11000 ms
Test Execution 416000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 61ms / native 3ms = 20.3x speedup
SIMD float-mul (64K x300) java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 261.000 ms
Base64 CN1 decode 102.000 ms
Base64 native encode 846.000 ms
Base64 encode ratio (CN1/native) 0.309x (69.1% faster)
Base64 native decode 476.000 ms
Base64 decode ratio (CN1/native) 0.214x (78.6% faster)
Base64 SIMD encode 129.000 ms
Base64 encode ratio (SIMD/CN1) 0.494x (50.6% faster)
Base64 SIMD decode 75.000 ms
Base64 decode ratio (SIMD/CN1) 0.735x (26.5% faster)
Base64 encode ratio (SIMD/native) 0.152x (84.8% faster)
Base64 decode ratio (SIMD/native) 0.158x (84.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 41.000 ms
Image createMask (SIMD on) 252.000 ms
Image createMask ratio (SIMD on/off) 6.146x (514.6% slower)
Image applyMask (SIMD off) 75.000 ms
Image applyMask (SIMD on) 247.000 ms
Image applyMask ratio (SIMD on/off) 3.293x (229.3% slower)
Image modifyAlpha (SIMD off) 245.000 ms
Image modifyAlpha (SIMD on) 213.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.869x (13.1% faster)
Image modifyAlpha removeColor (SIMD off) 236.000 ms
Image modifyAlpha removeColor (SIMD on) 340.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.441x (44.1% slower)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant