JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

JS port: apply the text layer's DOM changes with the frame they belong to - #5629

Merged
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity
Aug 30, 2026
Merged

JS port: apply the text layer's DOM changes with the frame they belong to#5629
shai-almog merged 5 commits into
masterfrom
fix/js-text-layer-frame-atomicity

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The defect

f691c7e4a6 (#5552) gave the port a DOM text layer, which makes every frame two
halves: the pixels the canvas draws and the elements the layer positions. Only
one of them is issued when the frame is decided.

promote(), endComponent() and syncToForm() wrote to the document during
the component paint
. The canvas commands that same paint records are not
replayed and shipped until the frame is drained an animation frame later. The
browser composites in between, so it renders one half of the new frame over the
other half of the old one:

  • text released early leaves a hole where the glyphs still are on screen;
  • text added early is drawn twice, once in each place.

Nothing about the layer's bookkeeping was wrong, which is why it survived
review. It was applied at the wrong moment.

Reproduced on the BuildCloud console (7.0.267, real Chrome, 30fps video): a
click on a navigation row blanks every label and every icon on the form for
a frame, and draws the outgoing and incoming label of that row superimposed.
Icons go through drawString too, so they are runs and blank with the text.

The fix

Every mutation becomes a TextLayerOp (ExecutableOp) recorded into the same
buffer as the draws around it, replayed into the same SurfaceCommandRecorder
(OP_TEXT_*, 90-96), and shipped in the same flush message. The host applies it
in the task that replays that frame's canvas commands, in the draw order the
paint gave it, so no rendering opportunity exists between the two halves.
OP_BLUR_SELF_REGION already rides the stream for the same reason.

syncToForm moves from the drain to flushGraphics. By drain time the frame's
ops are already snapshotted, so a release recorded there shipped with the next
frame — which is what put a removed component's text over the pixels that
replaced it. In flushGraphics the components have finished painting and the
buffer is still open.

Two things fall out: the retained CSSStyleDeclaration handles are gone (style
is written by opcode now), and with them the two getStyle() property reads per
run the class had to cache to stay off the barrier.

Measurement

Scripted 1028-frame interaction, real Chrome, 30fps, counting transient
brightness spikes (a frame brighter than the one before that recovers within
three — a navigation is a step that stays, and is not counted):

console buildtransient blank flashes
7.0.267 as released6
this branch0

0 is what the same run measures with the layer disabled entirely
(?cn1TextLayer=0), so the flicker is gone rather than merely smaller.

scripts/verify-javascript-web-overlay.mjs reports the same 44 promoted runs
and 43 semantic nodes as before, so DOM text is still real, selectable text.

Not covered

The 181-golden screenshot suite did not run: build-javascript-port-hellocodenameone.sh
fails in the plugin's css goal in this environment for an unrelated reason (a
locally installed codenameone-designer:8.0-SNAPSHOT referencing a class that
exists only on another branch). Worth running in CI before merge.

…g to
The DOM text layer made every frame two halves: the pixels the canvas draws and
the elements the layer positions. Only one of them was issued when the frame was
decided. promote(), endComponent() and syncToForm() wrote to the document during
the component paint, while the canvas commands that paint went on to record are
not replayed and shipped until the frame is drained an animation frame later.
The browser composites in between, so it renders one half of the new frame over
the other half of the old one. Text released early leaves a hole where the
glyphs still are on screen; text added early is drawn twice, once in each place.
Both are visible on any screen that rebuilds part of itself in response to a
tap: the BuildCloud console blanks every label and every icon on the form for a
frame when a navigation row is clicked, and shows the outgoing and incoming
label of the row superimposed. Nothing about the layer's bookkeeping was wrong,
which is why it survived review -- it was applied at the wrong moment.
Every mutation is now recorded as an ExecutableOp into the same buffer as the
draws around it, replayed into the same SurfaceCommandRecorder, and shipped in
the same flush message, so the host applies it in the task that replays that
frame's canvas commands -- and in the draw order the paint gave it. There is no
rendering opportunity between the two halves for the compositor to take.
OP_BLUR_SELF_REGION already rides the stream for the same reason.
syncToForm moves from the drain to flushGraphics for the same reason. By the
time the drain runs, this frame's ops have been snapshotted, so a release
recorded there would ship with the NEXT frame and the removed component's text
would sit over the pixels that replaced it for a frame. In flushGraphics the
components have finished painting and the buffer is still open, which is exactly
the window a release belongs in.
Two things fall out of the change. The retained CSSStyleDeclaration handles are
gone -- style is written by opcode now -- and with them the two getStyle()
property reads per run the class had to cache to stay off the barrier. And
building a run's element pair stays an immediate write, because neither element
is in the document yet: nothing can be seen half-built, and it guarantees the
pair exists by the time the recorded attach is replayed.
Measured on the console, real Chrome, 30fps video over four navigations: the
worst single-frame brightness jump goes from +0.0159 (the screen blanking) to
+0.0057, which is what the same run measures with the layer disabled entirely --
the flicker is gone, not merely smaller. verify-javascript-web-overlay.mjs
reports the same 44 promoted runs and 43 semantic nodes as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code ReviewCompleted2026-08-30T11:21:50.073677Zd1e6028New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:385a69d8b5

ℹ️ 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".

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300)java 69ms / native 5ms = 13.8x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode192.000 ms
Base64 CN1 decode136.000 ms
Base64 SIMD encode112.000 ms
Base64 encode ratio (SIMD/CN1)0.583x (41.7% faster)
Base64 SIMD decode101.000 ms
Base64 decode ratio (SIMD/CN1)0.743x (25.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)59.000 ms
Image createMask (SIMD on)24.000 ms
Image createMask ratio (SIMD on/off)0.407x (59.3% faster)
Image applyMask (SIMD off)55.000 ms
Image applyMask (SIMD on)82.000 ms
Image applyMask ratio (SIMD on/off)1.491x (49.1% slower)
Image modifyAlpha (SIMD off)96.000 ms
Image modifyAlpha (SIMD on)69.000 ms
Image modifyAlpha ratio (SIMD on/off)0.719x (28.1% faster)
Image modifyAlpha removeColor (SIMD off)60.000 ms
Image modifyAlpha removeColor (SIMD on)70.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.167x (16.7% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 85ms / native 4ms = 21.2x speedup
SIMD float-mul (64K x300)java 81ms / native 7ms = 11.5x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode204.000 ms
Base64 CN1 decode132.000 ms
Base64 SIMD encode104.000 ms
Base64 encode ratio (SIMD/CN1)0.510x (49.0% faster)
Base64 SIMD decode98.000 ms
Base64 decode ratio (SIMD/CN1)0.742x (25.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)54.000 ms
Image createMask (SIMD on)21.000 ms
Image createMask ratio (SIMD on/off)0.389x (61.1% faster)
Image applyMask (SIMD off)58.000 ms
Image applyMask (SIMD on)80.000 ms
Image applyMask ratio (SIMD on/off)1.379x (37.9% slower)
Image modifyAlpha (SIMD off)88.000 ms
Image modifyAlpha (SIMD on)68.000 ms
Image modifyAlpha ratio (SIMD on/off)0.773x (22.7% faster)
Image modifyAlpha removeColor (SIMD off)54.000 ms
Image modifyAlpha removeColor (SIMD on)68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)1.259x (25.9% slower)

@github-actions

github-actionsBot commented Aug 30, 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)
    • build-hint-catalog: 0 findings (no issues)
    • build-hint-tools: 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.

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The drain detects a buffered transition and hides the layer, but it does so
after beginFrame() has snapshotted and cleared the queue. Routing that through
the sink -- as the previous commit did with every other mutation -- puts it in
the buffer the NEXT frame ships, so the first frame of the transition is
composited with the outgoing form's DOM text still over it. That is the very
artifact the previous commit set out to remove, reintroduced at the one call
site that sits on the wrong side of the snapshot.
Worse when there is no next frame. A buffered transition paints only its
prebuilt images and never puts a component through the display graphics, so if
the last flush went out before this ran, a sink-recorded hide would never ship
and the stale text would sit over the whole animation.
suspendIntoFrame() writes the mutation straight into the recorder for the frame
being assembled, so the hide travels in the same flush as the transition's own
pixels. The sink stays the right route for callers inside a component paint,
whose frame has not been handed off yet.
The other two call sites are fine as they are. updateTextLayerSuspension() runs
from beforeComponentPaint at frame start, before the hand-off, so the sink puts
it in the correct frame. The readback path records through the sink too, but it
sets readbackRepaintPending and requests the repaint it then waits for, so the
following flush is guaranteed and the capture deliberately waits for it.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 163 screenshots: 163 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

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300)java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 native bridgeunavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathgated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode264.000 ms
Base64 CN1 decode153.000 ms
Base64 SIMD encode65.000 ms
Base64 encode ratio (SIMD/CN1)0.246x (75.4% faster)
Base64 SIMD decode63.000 ms
Base64 decode ratio (SIMD/CN1)0.412x (58.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)13.000 ms
Image createMask (SIMD on)8.000 ms
Image createMask ratio (SIMD on/off)0.615x (38.5% faster)
Image applyMask (SIMD off)46.000 ms
Image applyMask (SIMD on)19.000 ms
Image applyMask ratio (SIMD on/off)0.413x (58.7% faster)
Image modifyAlpha (SIMD off)17.000 ms
Image modifyAlpha (SIMD on)13.000 ms
Image modifyAlpha ratio (SIMD on/off)0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off)20.000 ms
Image modifyAlpha removeColor (SIMD on)41.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)2.050x (105.0% slower)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:0b889f3594

ℹ️ 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".

Putting the text layer's mutations on the render queue changed what an empty
frame means. A paint that drew no pixels -- a transparent component that only
draws text, or a flush whose sole content is syncToForm releasing the runs of a
component that has gone -- used to leave the queue empty, and drainPending-
DisplayFrame() returned at the isEmpty() check without touching the canvas.
Those frames now carry text ops, so they get past that check and reach the
full-frame clearRect, which fires on any crop covering the display. The clear
runs, the frame has no draw to put anything back, and retained canvas content is
erased.
The clear exists to stop stale pixels bleeding through a full repaint, so it
belongs to frames that repaint. framePaintsPixels() asks whether the frame holds
anything other than a text mutation, and the clear is skipped when it does not.
Frames that do paint are unaffected; a mixed frame still clears, because it
carries the paint that follows the clear.
Deliberately not widened to "no op that produces pixels": a clip-only frame
reaching the clear predates these ops and is a separate question. This restores
exactly what the queue change took away.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 70ms / native 2ms = 35.0x speedup
SIMD float-mul (64K x300)java 68ms / native 2ms = 34.0x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode169.000 ms
Base64 CN1 decode99.000 ms
Base64 native encode573.000 ms
Base64 encode ratio (CN1/native)0.295x (70.5% faster)
Base64 native decode217.000 ms
Base64 decode ratio (CN1/native)0.456x (54.4% faster)
Base64 SIMD encode56.000 ms
Base64 encode ratio (SIMD/CN1)0.331x (66.9% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.465x (53.5% faster)
Base64 encode ratio (SIMD/native)0.098x (90.2% faster)
Base64 decode ratio (SIMD/native)0.212x (78.8% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)10.000 ms
Image createMask (SIMD on)5.000 ms
Image createMask ratio (SIMD on/off)0.500x (50.0% faster)
Image applyMask (SIMD off)47.000 ms
Image applyMask (SIMD on)49.000 ms
Image applyMask ratio (SIMD on/off)1.043x (4.3% slower)
Image modifyAlpha (SIMD off)54.000 ms
Image modifyAlpha (SIMD on)32.000 ms
Image modifyAlpha ratio (SIMD on/off)0.593x (40.7% faster)
Image modifyAlpha removeColor (SIMD off)50.000 ms
Image modifyAlpha removeColor (SIMD on)45.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.900x (10.0% faster)

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot113000 ms
Simulator Boot (Run)0 ms
App Install21000 ms
App Launch18000 ms
Test Execution521000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 150ms / native 5ms = 30.0x speedup
SIMD float-mul (64K x300)java 348ms / native 23ms = 15.1x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode273.000 ms
Base64 CN1 decode243.000 ms
Base64 native encode392.000 ms
Base64 encode ratio (CN1/native)0.696x (30.4% faster)
Base64 native decode503.000 ms
Base64 decode ratio (CN1/native)0.483x (51.7% faster)
Base64 SIMD encode122.000 ms
Base64 encode ratio (SIMD/CN1)0.447x (55.3% faster)
Base64 SIMD decode119.000 ms
Base64 decode ratio (SIMD/CN1)0.490x (51.0% faster)
Base64 encode ratio (SIMD/native)0.311x (68.9% faster)
Base64 decode ratio (SIMD/native)0.237x (76.3% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)17.000 ms
Image createMask (SIMD on)7.000 ms
Image createMask ratio (SIMD on/off)0.412x (58.8% faster)
Image applyMask (SIMD off)68.000 ms
Image applyMask (SIMD on)46.000 ms
Image applyMask ratio (SIMD on/off)0.676x (32.4% faster)
Image modifyAlpha (SIMD off)46.000 ms
Image modifyAlpha (SIMD on)42.000 ms
Image modifyAlpha ratio (SIMD on/off)0.913x (8.7% faster)
Image modifyAlpha removeColor (SIMD off)56.000 ms
Image modifyAlpha removeColor (SIMD on)39.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.696x (30.4% faster)

@github-actions

github-actionsBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 529 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 12443 ms

  • Hotspots (Top 20 sampled methods):

    • 48.19% java.util.ArrayList.indexOf (880 samples)
    • 4.82% java.lang.System.identityHashCode (88 samples)
    • 2.74% java.lang.Object.hashCode (50 samples)
    • 2.46% com.codename1.tools.translator.ByteCodeClass.findDeclaredMethod (45 samples)
    • 1.92% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (35 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.75% java.lang.StringBuilder.append (32 samples)
    • 1.37% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (25 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.analyze (21 samples)
    • 1.10% com.codename1.tools.translator.Parser.classIndex (20 samples)
    • 0.93% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (17 samples)
    • 0.93% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.equals (16 samples)
    • 0.88% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (16 samples)
    • 0.82% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (15 samples)
    • 0.71% java.util.HashMap.hash (13 samples)
    • 0.71% com.codename1.tools.translator.Parser.resolveDevirtualizedOwner (13 samples)
    • 0.66% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (12 samples)
    • 0.60% java.util.HashMap.putVal (11 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 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

@shai-almog

shai-almog commented Aug 30, 2026

Copy link
Copy Markdown
CollaboratorAuthor

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

Benchmark Results

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

Build and Run Timing

MetricDuration
Simulator Boot67000 ms
Simulator Boot (Run)0 ms
App Install22000 ms
App Launch9000 ms
Test Execution442000 ms

Detailed Performance Metrics

MetricDuration
SIMD kernel backendSSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300)java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctnessPASS (native result == scalar reference)
Base64 payload size8192 bytes
Base64 benchmark iterations6000
Base64 SIMD byte pathactive (NEON-accelerated)
Base64 CN1 encode177.000 ms
Base64 CN1 decode166.000 ms
Base64 native encode430.000 ms
Base64 encode ratio (CN1/native)0.412x (58.8% faster)
Base64 native decode373.000 ms
Base64 decode ratio (CN1/native)0.445x (55.5% faster)
Base64 SIMD encode50.000 ms
Base64 encode ratio (SIMD/CN1)0.282x (71.8% faster)
Base64 SIMD decode46.000 ms
Base64 decode ratio (SIMD/CN1)0.277x (72.3% faster)
Base64 encode ratio (SIMD/native)0.116x (88.4% faster)
Base64 decode ratio (SIMD/native)0.123x (87.7% faster)
Image encode benchmark iterations100
Image createMask (SIMD off)7.000 ms
Image createMask (SIMD on)2.000 ms
Image createMask ratio (SIMD on/off)0.286x (71.4% faster)
Image applyMask (SIMD off)39.000 ms
Image applyMask (SIMD on)117.000 ms
Image applyMask ratio (SIMD on/off)3.000x (200.0% slower)
Image modifyAlpha (SIMD off)108.000 ms
Image modifyAlpha (SIMD on)107.000 ms
Image modifyAlpha ratio (SIMD on/off)0.991x (0.9% faster)
Image modifyAlpha removeColor (SIMD off)167.000 ms
Image modifyAlpha removeColor (SIMD on)127.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off)0.760x (24.0% faster)

graphics-draw-image-rect failed CI with the top half of its grid drawn and the
bottom half blank -- the same fingerprint the settle code already documents by
name from the last time it happened. Its capture was taken mid-draw.
The settle decides the UI is ready by watching the canvas stop changing:
quietFrames with no render-queue advance plus stableFrames with an unchanged
signature. Both are absence of evidence. A screen that composes itself in stages
is genuinely still between two of them, so it satisfies both while half of it is
missing. The existing answer was to widen the window for these two tests, 48/3/3
to 120/6/6, and the failure above is that window being exceeded again. Widening
it further only moves the race.
Repainting removes it instead. Form.repaint() marks the whole form dirty, so the
paintDirty() that the presentation step already performs redraws all of it in
one frame; after that there are no stages left outstanding, and the settle is
observing a screen that is complete rather than one that is merely quiet. The
repaint is requested before the presentation, because paintDirty() paints what
is marked dirty at the moment it runs.
Confined to the two tests that compose in stages, the same pair the settle
already singles out. The rest of the suite is untouched, and in particular the
partial-flush and dirty-clip tests, whose goldens assert what a partial repaint
leaves on screen and would be meaningless after a forced full one.
Both methods it calls are ones the framework itself calls from Java, so neither
can be dropped by the unused-method cull the way a method existing solely for
this would be: CodenameOneImplementation.getCurrentForm() on the receiver this
file already holds, then Component.repaint() on the result. Both verified
present in a translated bundle as cn1_s_getCurrentForm_R_com_codename1_ui_Form
and cn1_s_repaint. If neither resolves, the helper logs what it found and does
nothing, leaving the capture exactly as it is today -- the worst case is no
improvement, never a new failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:585cc3f1c2

ℹ️ 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 on lines +3511 to +3512
if (!(op instanceof com.codename1.impl.html5.graphics.TextLayerOp)) {
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude state-only commands from the pixel test

When a full-screen transparent component repaints only promoted text, PaintSurface.paintDirty() still calls wrapper.setClip(0, 0, dwidth, dheight) before painting (CodenameOne/src/com/codename1/impl/PaintSurface.java:223-230), which queues a ClipRect. This predicate therefore returns true even though no pixels are drawn, so the full-frame clearRect still erases retained canvas content without replacement. Fresh evidence beyond the prior comment is that the newly added predicate classifies every non-TextLayerOp, including clip and transform state commands, as a pixel paint.

Useful? React with 👍 / 👎.

framePaintsPixels() asked whether a frame held anything other than a text
mutation, which is the wrong question. PaintSurface.paintDirty() calls
setClip(0, 0, width, height) before painting each animatable and
BufferedGraphics records that unconditionally, so every painted frame carries a
ClipRect whether or not a pixel follows it. A full-screen transparent component
that repaints only promoted text therefore produced a frame of [ClipRect,
TextLayerOp...], the predicate said "paints", and the full-frame clearRect ran
against a frame with nothing to draw back.
The predicate now asks what the op does. A clip or a transform records what
later draws are subject to and leaves the canvas as it was; a text mutation
writes to the document. None of them qualify a frame for the clear.
The classification lives next to addOp as isStateOnlyOp() and both callers share
it, because they are asking the same question and a second copy would drift.
addOp already had this exact set inline -- it is what it keeps when the clip is
empty, since a draw may not render but the clip and transform still have to be
recorded for a later non-empty clip to restore drawing.
Correcting the previous commit's reasoning as well as its code: that message said
a frame like this used to be empty and returned early, and the always-recorded
ClipRect means it never was. The clear firing on a frame that paints nothing is
therefore older than these text ops, not something they introduced -- what they
introduced is the detach-only frame, syncToForm releasing a departed component's
runs with nothing else queued, which has no ClipRect and was genuinely empty
before. Both are covered now.
Found in review of #5629.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit ed88402 into masterAug 30, 2026
48 checks passed
@shai-almog
shai-almog deleted the fix/js-text-layer-frame-atomicity branch August 30, 2026 16:16
Sign up for freeto 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

@shai-almog