Skip to content

Add cross-platform AR and VR support#5335

Merged
shai-almog merged 25 commits into
masterfrom
ar-vr-support
Jul 7, 2026
Merged

Add cross-platform AR and VR support#5335
shai-almog merged 25 commits into
masterfrom
ar-vr-support

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Summary

Adds deeper AR/VR support to the framework as two new core packages, with native backends, a simulated backend for the simulator, build-pipeline integration, unit tests and developer guide chapters.

com.codename1.ar (ARKit on iOS, ARCore on Android)

  • World tracking, plane detection (horizontal/vertical), screen-point hit testing, anchors, glTF / gpu.Mesh content placement, light estimation, image tracking and face tracking.
  • API modeled on the camera package: AR.isSupported() / AR.getCapabilities() / AR.open(ARSessionOptions); single active ARSession owning an ARView, anchors and planes.
  • SPI com.codename1.impl.ARImpl mirrors CameraImpl (one impl per session, Display.getARBackend() accessor). Implementation events may arrive on any thread; the core bridge marshals them to the EDT and coalesces high-frequency refinements (anchor/plane updates, camera pose, light) to latest-per-id.
  • Content model: ports never see glTF bytes - ARModel parses via the (new) device-free GltfLoader overloads and hands raw interleaved buffers + a decoded base-color image to the backend.

com.codename1.vr (pure core, no platform dependency)

  • VRView: side-by-side stereo rendering of a VRRenderer on the existing gpu pipeline, per-eye cameras from VRCameraRig, head tracking from HeadTracker (deterministic complementary fusion in OrientationFilter).
  • Media360View: equirectangular panorama viewer with drag and gyroscope look, mono and cardboard-style stereo. TextureSource is the documented extension point for dynamic content; 360 video and lens distortion are deliberately scoped out (no placeholder API) pending a media-to-texture path and render-to-texture support.

Platform backends

  • iOS: CN1AR.{h,m} wraps an ARKit ARSession composited through an ARSCNView; content buffers become SCNGeometry. Gated by a new INCLUDE_CN1_AR define (uncommented by the builder only for apps referencing the API), compiled out on tvOS/watchOS, with unconditional stub bridge symbols so non-AR apps link cleanly.
  • Android: typed ARCore code in the deletable com.codename1.impl.android.ar package (external-OES camera background + lit/textured mesh renderer on a GLSurfaceView); reached reflectively from AndroidImplementation, deleted by the builder for non-AR apps. The port maven module excludes the package from its own compile (ARCore is not in cn1-binaries) while still shipping it in android_port_sources.jar.
  • Simulator: JavaSEARImpl renders a virtual room (floor/wall planes detected after a realistic delay, mouse-look + WASD camera, deterministic ray-cast hit tests) through the software 3D device, so the full AR loop is debuggable with breakpoints. A new Simulate > AR Simulation window drives tracking failures, light estimates, reference image detection and face toggling. VR/360 run in the simulator through the desktop GL backend with the existing Motion Simulation window feeding the head tracker.

Build pipeline

  • New AiDependencyTable entry for com/codename1/ar/: camera permission + NSCameraUsageDescription, ARCore gradle dependency, android.hardware.camera.ar feature and com.google.ar.core=optional manifest meta-data (new androidMetaData table capability); android.ar.required=true flips to AR-only. ARKit/SceneKit linked explicitly by IPhoneBuilder and weak-linked on the tv/watch/mac slices. Apps that never reference the package pay no size, permission or store-visibility cost.

Test plan

  • mvn test -Punittests -pl core-unittests: 3830 tests pass, including new suites for the AR session/event bridge/pose math, VR fusion determinism, camera rig, sphere primitive and device-free glTF parsing.
  • mvn test -pl codenameone-maven-plugin: 197 tests pass, including new AiDependencyTableTest coverage of the AR entry.
  • iOS: generated hellocodenameone Xcode project (which now references the API) compiles and links with ARKit enabled (BUILD SUCCEEDED); the compiled-out stub path is verified clean with a standalone clang syntax check.
  • Android: generated Gradle project compiles the ARCore backend against the injected dependency; manifest/gradle output verified (meta-data, permission, feature, minSdk 24).
  • Simulator: scratch app verified the full loop end to end - plane events, hit test at the mathematically expected point, anchor + sphere placement rendered in the virtual room.

🤖 Generated with Claude Code

Adds two new core packages behind the existing optional-platform-feature
patterns:

com.codename1.ar - augmented reality backed by ARKit (iOS) and ARCore
(Android): world tracking, plane detection, screen-point hit testing,
anchors, glTF/Mesh content placement, light estimation, image tracking
and face tracking. The SPI (com.codename1.impl.ARImpl) mirrors
CameraImpl with one impl per session and a Display.getARBackend()
accessor; all events are marshaled to the EDT with per-id coalescing of
high-frequency refinements.

com.codename1.vr - pure-core VR on the portable gpu pipeline: VRView
stereo rendering with per-eye cameras, HeadTracker sensor fusion
(deterministic complementary OrientationFilter over gyro/accel/
magnetometer), VRCameraRig eye math and a Media360View equirectangular
panorama viewer with drag/gyro look. No platform dependency, so it runs
wherever the GPU backend does, including the simulator.

Platform backends:
- iOS: CN1AR.{h,m} wraps an ARKit ARSession composited through an
  ARSCNView; anchored content arrives as raw interleaved vertex buffers
  and becomes SCNGeometry. Gated by INCLUDE_CN1_AR (compiled out on
  tvOS/watchOS with unconditional stub bridge symbols so non-AR apps
  link cleanly).
- Android: typed ARCore code in the deletable
  com.codename1.impl.android.ar package (camera background + mesh
  renderer on a GLSurfaceView); AndroidImplementation reaches it via
  reflection only, and the build deletes the package for non-AR apps.
- Simulator: JavaSEARImpl renders a virtual room (detected floor/wall
  planes, mouse-look + WASD camera, ray-cast hit testing) through the
  software 3D device, plus a Simulate > AR Simulation window to drive
  tracking failures, lighting, image detection and face toggling.

Build pipeline: referencing com.codename1.ar injects the camera
permission/plist, links ARKit/SceneKit on iOS (weak-linked on the
tv/watch/mac slices) and adds the ARCore gradle dependency, the
android.hardware.camera.ar feature and the com.google.ar.core manifest
meta-data on Android (a new androidMetaData capability in
AiDependencyTable). The android.ar.required build hint flips the app
from AR-optional to AR-only.

Supporting gpu additions: Quaternion math utilities, a tessellated
sphere primitive with equirectangular UVs, and device-free GltfLoader
overloads that parse geometry and decode the base-color image without a
GraphicsDevice.

Covered by unit tests in maven/core-unittests (AR session/event
bridge/pose math, VR fusion determinism and camera rig, sphere
geometry, glTF device-free parsing) and two new developer guide
chapters including the simulator debugging workflow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.

Native Android coverage

  • 📊 Line coverage: 10.03% (10203/101720 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (50209/565326), branch 4.40% (2263/51456), complexity 4.44% (2415/54434), method 6.73% (1900/28245), class 10.83% (435/4017)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 10.03% (10203/101720 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.88% (50209/565326), branch 4.40% (2263/51456), complexity 4.44% (2415/54434), method 6.73% (1900/28245), class 10.83% (435/4017)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 211ms / native 81ms = 2.6x speedup
SIMD float-mul (64K x300) java 125ms / native 127ms = 0.9x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 317.000 ms
Base64 CN1 decode 269.000 ms
Base64 native encode 765.000 ms
Base64 encode ratio (CN1/native) 0.414x (58.6% faster)
Base64 native decode 1012.000 ms
Base64 decode ratio (CN1/native) 0.266x (73.4% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 141 screenshots: 141 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 Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 70ms / native 5ms = 14.0x speedup
SIMD float-mul (64K x300) java 71ms / native 4ms = 17.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 277.000 ms
Base64 CN1 decode 172.000 ms
Base64 SIMD encode 128.000 ms
Base64 encode ratio (SIMD/CN1) 0.462x (53.8% faster)
Base64 SIMD decode 130.000 ms
Base64 decode ratio (SIMD/CN1) 0.756x (24.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 35.000 ms
Image createMask (SIMD on) 14.000 ms
Image createMask ratio (SIMD on/off) 0.400x (60.0% faster)
Image applyMask (SIMD off) 57.000 ms
Image applyMask (SIMD on) 31.000 ms
Image applyMask ratio (SIMD on/off) 0.544x (45.6% faster)
Image modifyAlpha (SIMD off) 60.000 ms
Image modifyAlpha (SIMD on) 21.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.350x (65.0% faster)
Image modifyAlpha removeColor (SIMD off) 61.000 ms
Image modifyAlpha removeColor (SIMD on) 22.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.361x (63.9% faster)

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 95ms / native 6ms = 15.8x speedup
SIMD float-mul (64K x300) java 70ms / native 4ms = 17.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 400.000 ms
Base64 CN1 decode 315.000 ms
Base64 native encode 1541.000 ms
Base64 encode ratio (CN1/native) 0.260x (74.0% faster)
Base64 native decode 1111.000 ms
Base64 decode ratio (CN1/native) 0.284x (71.6% faster)
Base64 SIMD encode 96.000 ms
Base64 encode ratio (SIMD/CN1) 0.240x (76.0% faster)
Base64 SIMD decode 65.000 ms
Base64 decode ratio (SIMD/CN1) 0.206x (79.4% faster)
Base64 encode ratio (SIMD/native) 0.062x (93.8% faster)
Base64 decode ratio (SIMD/native) 0.059x (94.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 45.000 ms
Image createMask (SIMD on) 32.000 ms
Image createMask ratio (SIMD on/off) 0.711x (28.9% faster)
Image applyMask (SIMD off) 312.000 ms
Image applyMask (SIMD on) 354.000 ms
Image applyMask ratio (SIMD on/off) 1.135x (13.5% slower)
Image modifyAlpha (SIMD off) 326.000 ms
Image modifyAlpha (SIMD on) 160.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.491x (50.9% faster)
Image modifyAlpha removeColor (SIMD off) 310.000 ms
Image modifyAlpha removeColor (SIMD on) 326.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.052x (5.2% slower)

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 58000 ms
Simulator Boot (Run) 1000 ms
App Install 10000 ms
App Launch 0 ms
Test Execution 403000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 66ms / native 5ms = 13.2x speedup
SIMD float-mul (64K x300) java 131ms / native 4ms = 32.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 554.000 ms
Base64 CN1 decode 477.000 ms
Base64 native encode 1174.000 ms
Base64 encode ratio (CN1/native) 0.472x (52.8% faster)
Base64 native decode 519.000 ms
Base64 decode ratio (CN1/native) 0.919x (8.1% faster)
Base64 SIMD encode 79.000 ms
Base64 encode ratio (SIMD/CN1) 0.143x (85.7% faster)
Base64 SIMD decode 124.000 ms
Base64 decode ratio (SIMD/CN1) 0.260x (74.0% faster)
Base64 encode ratio (SIMD/native) 0.067x (93.3% faster)
Base64 decode ratio (SIMD/native) 0.239x (76.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 66.000 ms
Image createMask (SIMD on) 5.000 ms
Image createMask ratio (SIMD on/off) 0.076x (92.4% faster)
Image applyMask (SIMD off) 276.000 ms
Image applyMask (SIMD on) 79.000 ms
Image applyMask ratio (SIMD on/off) 0.286x (71.4% faster)
Image modifyAlpha (SIMD off) 132.000 ms
Image modifyAlpha (SIMD on) 92.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.697x (30.3% faster)
Image modifyAlpha removeColor (SIMD off) 243.000 ms
Image modifyAlpha removeColor (SIMD on) 194.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.798x (20.2% faster)

- The Ant build compiles the core against the CLDC bootclasspath which
  has no Math.acos/atan2; route Quaternion.slerp and the
  OrientationFilter corrections through com.codename1.util.MathUtil.
- SpotBugs: extract ARSession's hit-test error forwarder into a named
  static class and drop the unread textureFromSource field from
  Media360View.
- Developer guide: resolve the Vale and LanguageTool findings in the
  two new chapters (contractions, heading capitalization, adverbs,
  hyphenation, sentence restructuring) and accept the raycast /
  equirectangular terms.
- run-ios-native-tests.sh: when xcodebuild -downloadPlatform iOS fails
  with "Unable to connect to simulator" (wedged CoreSimulator service
  on some runner instances), restart the service and retry instead of
  silently continuing into a doomed destination match; when falling
  back to an existing simulator, prefer a device on the newest iOS
  runtime since older-runtime devices are rejected by destination
  matching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 12 screenshots: 12 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

shai-almog and others added 3 commits July 4, 2026 20:39
The EDT-completion Runnables in IOSARImpl captured only locals, so
SpotBugs (which gates the ios module too) flagged them as
SIC_INNER_SHOULD_BE_STATIC_ANON. Replace both with a named static
CompleteOnEdt helper and mirror the same shape in AndroidARImpl for
consistency (it is compiled inside user app builds).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The core is gated on PMD rules that forbid volatile; replace every
volatile field with the locking convention the codebase already uses:

- ARSession: the cross-thread stop signal moves into the event bridge,
  guarded by the bridge lock the producers already hold; the session's
  own closed flag stays EDT-confined like CameraSession.
- VRView / Media360View: the settings the EDT writes and the render
  thread reads each frame (stereo, tracking, look angles, pending
  image/texture source, clear color, position) move behind a state lock
  with a single per-frame snapshot on the render thread.

Also resolves the remaining findings: foreach conversions in
ARSession's dispatch loops, an intent-documented suppression for the
identity comparison in ARAnchor.setNode (the Camera precedent),
dropping the identity-compare fast path in Quaternion.integrateGyro
(self-copy is harmless), removing OrientationFilter's redundant
constructor and making VRCameraRig's scratch vector a local.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- run-mac-native-ui-tests.sh: the Mac Catalyst destination of an
  SDKROOT=iphoneos target needs the iOS platform installed in the
  active Xcode. Some runner instances ship Xcode without it, so no
  amount of destination warming helps - the scheme lists only
  watchOS/tvOS destinations and the build dies with "Unable to find a
  destination". Detect the missing iphoneos SDK, download the platform
  and restart a wedged CoreSimulator service when the download fails
  with "Unable to connect to simulator" (the same recovery
  run-ios-native-tests.sh gained). Verified locally that the generated
  project itself is healthy: the Catalyst destination lists and the
  slice builds with the AR natives.

- SimulatorWindowModeVerifier: the fixed warmup sleep raced the
  simulator's first paint on slow runners, failing the run with
  "Screenshot appears blank/flat". Keep the scenario warmup as a floor
  and then poll the desktop capture until it shows actual content
  before validating.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 53000 ms
Simulator Boot (Run) 1000 ms
App Install 9000 ms
App Launch 2000 ms
Test Execution 352000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 84ms / native 4ms = 21.0x speedup
SIMD float-mul (64K x300) java 101ms / native 3ms = 33.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 486.000 ms
Base64 CN1 decode 207.000 ms
Base64 native encode 1050.000 ms
Base64 encode ratio (CN1/native) 0.463x (53.7% faster)
Base64 native decode 652.000 ms
Base64 decode ratio (CN1/native) 0.317x (68.3% faster)
Base64 SIMD encode 55.000 ms
Base64 encode ratio (SIMD/CN1) 0.113x (88.7% faster)
Base64 SIMD decode 49.000 ms
Base64 decode ratio (SIMD/CN1) 0.237x (76.3% faster)
Base64 encode ratio (SIMD/native) 0.052x (94.8% faster)
Base64 decode ratio (SIMD/native) 0.075x (92.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 82.000 ms
Image createMask (SIMD on) 45.000 ms
Image createMask ratio (SIMD on/off) 0.549x (45.1% faster)
Image applyMask (SIMD off) 212.000 ms
Image applyMask (SIMD on) 253.000 ms
Image applyMask ratio (SIMD on/off) 1.193x (19.3% slower)
Image modifyAlpha (SIMD off) 212.000 ms
Image modifyAlpha (SIMD on) 143.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.675x (32.5% faster)
Image modifyAlpha removeColor (SIMD off) 246.000 ms
Image modifyAlpha removeColor (SIMD on) 220.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.894x (10.6% faster)

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

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

Generated automatically by the PR CI workflow.

shai-almog and others added 11 commits July 4, 2026 22:27
The legacy Ant project compiles all of Ports/Android/src without the
ARCore dependency; mirror the maven-compiler exclusion so the
deletable com.codename1.impl.android.ar package (compiled inside user
app builds where the gradle dependency exists) stays out of the port
jar here too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The custom jar target in Ports/Android/build.xml runs its own raw
javac over the port sources, bypassing the nbproject excludes
property; give it the same com/codename1/impl/android/ar exclusion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds end-to-end screenshot coverage for the com.codename1.vr stereo and
360 viewers plus an assertion test for the com.codename1.ar contract, an
AR scenario for the JavaSE simulator integration harness, and the first
developer-guide images for both chapters.

- VRStereoSceneScreenshotTest / Media360PanoramaScreenshotTest render a
  fixed VR stereo scene and a generated equirectangular panorama through
  the same GPU peer and ready-callback pattern as the Gpu3D tests, with
  head tracking frozen for determinism. They capture wherever
  CN.isGpuSupported() is true (the five 3D-capable cn1ss platforms).
- ARApiTest exercises the AR API against whichever port backend is
  present: the unsupported contract on the CI platforms (no AR runtime)
  and a full session round trip when a backend exists. No screenshot.
- The javase-simulator harness gains an ar-demo scenario that opens a
  simulated session, waits for the virtual floor plane, hit-tests it and
  anchors a model, asserting the full EDT event pipeline ran.
- Primitives.sphere flips u for inside-out spheres so 360 panoramas read
  correctly instead of mirror-imaged, covered by a new SpherePrimitiveTest
  case.
- Wires the AR simulator and VR stereo images into the guide chapters.

Goldens for the new screenshot tests (five cn1ss platforms) and the
ar-demo baseline are adopted from the CI capture in a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the VRStereoScene and Media360Panorama goldens captured by the
first CI run on the five 3D-capable cn1ss platforms (mac-native, android,
javascript, windows, and linux x64 + arm64) plus the javase-single-ar-demo
baseline from the JavaSE simulator integration suite. The GPU-backed
platforms carry a tolerance sidecar (matching the Gpu3D convention) since
software rasterizers vary slightly between runners; the linux native
goldens are bit-exact and need none.

Also wires the CI-captured 360 panorama into the Virtual Reality chapter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lakes

The simulator skin is resolved through an unauthenticated
api.github.com release query, whose quota is 60 requests/hour per IP. A
PR that fans out many jobs (plus re-runs) can exhaust that on a shared
runner IP, failing skin resolution with "HTTP Error 403: rate limit
exceeded" before the suite even starts. Pass the workflow's GITHUB_TOKEN
(already in the step env) as a Bearer header when present, raising the
quota to 5000/hour; the call still works unauthenticated when no token is
available (local runs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Android instrumentation @test is a trivial launcher; the CN1SS suite
runs asynchronously in the app process and signals completion with the
CN1SS:SUITE:FINISHED marker, which the runner script waits for before
comparing screenshots. That wait was 60s, which left almost no margin:
master detects the marker with a few seconds to spare, but adding two GPU
screenshot tests (VRStereoScene, Media360Panorama) - each a few seconds of
readback on the emulator's software rasterizer - pushed completion just
past 60s. The marker was then never detected in time, the app was
force-stopped mid-suite, and the last two tests (DesktopMode,
VideoIODecodedFrames) reported missing screenshots.

Raise the wait to 240s. A suite that finishes sooner still breaks out
immediately on marker detection (so the healthy path is unchanged), the
extra ceiling only applies while a larger suite finishes, and a genuine
hang is still caught by the missing-screenshot count guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses review feedback on the immersive screenshot tests:

- Stereo VR targets a headset; on tvOS it is redundant, so
  VRStereoScene now self-skips there. Media360Panorama still runs on
  tvOS, where panning a 360 image is a sensible use (its tvOS golden is
  adopted from CI).
- Both views now capture in landscape, where a stereo scene and a
  panorama read naturally. A small LandscapeCapture helper locks landscape
  and waits for the rotation to land on phones, and is a no-op on
  desktop/browser/tvOS (guarded by canForceOrientation), so only the phone
  goldens change orientation.
- Both tests move to the suite tail, after the orientation test and every
  portrait screenshot, and leave the device in landscape - safe because the
  VideoIO tests that follow capture off-screen grids or make no capture.

The existing VR/360 goldens are removed; the new landscape captures (and
the tvOS Media360Panorama golden) are adopted from the CI run that follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ARApiTest opened a real AR session in its supported branch. On Android that
routes into AndroidARImpl.open(), which calls ArCoreApk.requestInstall():
when ARCore is absent - the CI emulator's state - that launches the Play
Store install flow and backgrounds the app before AR.open() throws. The
thrown exception is caught by the runner, but the backgrounding freezes the
whole screenshot suite: the app never resumes, so every test after ARApiTest
(DesktopMode, VideoIODecodedFrames, and the VR/360 captures) never runs and
CN1SS:SUITE:FINISHED is never emitted. It looked like an intermittent
Android hang because ArCoreApk.checkAvailability() - hence AR.isSupported() -
varies per run, so the open path was hit only some of the time.

The full live-session round trip is covered where it is safe and
deterministic: the JavaSE simulator integration test drives JavaSEARImpl end
to end, and the unit tests exercise RecordingARImpl. ARApiTest now only
verifies what is safe on every port - the capability query is non-null and
the unsupported contract holds (where AR.open throws before reaching any
backend) - and never opens a live session.

Reproduced locally on an arm64 emulator booted with -gpu
swiftshader_indirect: with this change the suite runs to completion and the
VR/360 views capture in landscape on Android.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reposition to the suite tail put the VR/360 tests immediately before
VideoIODecodedFrames and left the device in landscape, and that test then
exceeded even its generous tolerance on Android - a GPU-heavy neighbor plus
the leftover orientation perturbed its decoded-frame grid.

Move the two immersive tests to right after the orientation test (which is
also where the review asked for them) so the video tests keep their
master-identical context: portrait, and no GPU-heavy test next to them. The
last of the two (Media360Panorama) now restores portrait once its capture is
done via LandscapeCapture.restorePortrait, waiting for the rotation to land,
so every test after it runs portrait exactly as before. Landscape locking
and the restore remain no-ops on desktop/browser/tvOS (guarded by
canForceOrientation), so only the phone captures rotate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the iOS Metal backend the DesktopMode screenshot grabbed the VR/360
form's lingering 3D layer under the late-present race (it captured the
"360 Panorama" form's stale, black Metal drawable instead of DesktopMode's
own). Both immersive tests now remove their view once the capture is done -
detaching the GPU peer so no stale layer survives to be grabbed by a later
test - and Media360Panorama then restores portrait as before.

Seeds the landscape VR/360 goldens captured by CI across every platform
that renders them: android, mac-native, javascript, windows, linux
(x64 + arm64) and iOS Metal, plus the tvOS Media360Panorama golden
(VRStereoScene skips tvOS, so it has none there). GPU-backed platforms
carry the usual tolerance sidecar; linux and the tvOS simulator are
bit-exact and need none. Also refreshes the developer-guide 360 image.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ure)

Detaching the view after capture was aimed at the iOS Metal DesktopMode
mismatch, but that mismatch is a pre-existing flake: DesktopMode and the
OrientationLock "landscape" capture differ on the GL iOS build too, where
the VR/360 tests do not even run (the late-present race grabs a random
wrong form, and OrientationLock is a long-standing ~50% flake on the iOS
runners). The detach did not help it, and on the Metal backend it perturbed
the GPU state enough to change Media360Panorama's own capture, so its
committed golden no longer matched.

Restore the tests to their prior form: VRStereoScene via createForm,
Media360Panorama as a plain Form that restores portrait after capture. This
matches the committed goldens again. The pre-existing iOS DesktopMode /
landscape flakes are orthogonal to this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shai-almog and others added 9 commits July 6, 2026 22:37
Two iOS Metal fixes, both scoped to Media360Panorama so nothing else shifts:

- The iOS Metal DesktopMode screenshot deterministically grabbed this test's
  full-screen 3D view as a stale black Metal drawable under the late-present
  race. Detach the peer (removeAll) once the capture is done so no such
  drawable survives. Only this test detaches - VRStereoScene keeps createForm -
  so the detach cannot perturb the GPU state that feeds this test's own
  capture (applying it to both previously shifted Media360Panorama's pixels).

- iOS Metal and tvOS render the smooth 360 gradient with more per-pixel
  variance run-to-run than the default delta=8 GPU tolerance (measured ~20%
  of pixels shifting 8-16, <0.2% beyond). Widen the per-pixel threshold so
  gradient noise does not fail the build while a real regression still trips
  the mismatch percent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the iOS Metal backends DesktopMode's screenshot deterministically grabbed
the Media360Panorama form under the late-present race (it captured that
form's frame, not its own), because the immersive tests ran well before it.
The GL tests capture their own content with an explicit render, so they are
position-independent - it is only the non-GL neighbor whose screenshot can
grab a lingering 3D frame.

Run VR/360 near the end instead: after DesktopMode and the video screenshot
(so neither has a GPU-heavy or 3D neighbor), and before the media round-trip
test (so its color-space perturbation cannot reach them). Nothing captures
the screen after them, so their landscape state cannot leak either. Their own
goldens are unchanged by the move.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ace)

With the immersive tests at the suite tail their own iOS Metal captures
began reading a previous form's still-current drawable (Media360Panorama
captured the OrientationLock form; VRStereoScene shifted) - the same
late-present race DesktopMode already guards against. Apply that guard here:
override extraSettleBeforeCaptureMillis() to repaint and wait before the
capture so the current frame is fully presented first. A no-op cost on the
other backends, where these captures were already correct.

Also widen the iOS Metal VRStereoScene tolerance to match Media360Panorama,
since that backend renders the scene with more per-pixel variance than the
default delta=8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lator

extraSettle fixed VRStereoScene on every Metal backend and Media360Panorama
on Mac Catalyst, but on the iOS/tvOS Metal simulators the panorama capture
still read the previous landscape frame (the OrientationLock form). Those
simulators upload the 1024x512 panorama texture and present its first frame
slower than the plain 3D cubes, especially at the suite tail where the video
tests leave the GPU busier. Warm the texture with an early render and wait
substantially longer (an extra render plus ~3.5s, on top of the 700ms
extraSettle) before capturing, so the textured frame has landed. The panorama
is static, so the longer settle does not change the captured pixels on any
backend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VRStereoScene captures correctly on every Metal backend; Media360Panorama did
not on the iOS/tvOS Metal simulators - it read the previous landscape frame
(the OrientationLock form) no matter how long the settle. The one structural
difference was the capture path: VRStereoScene uses createForm while
Media360Panorama used a hand-rolled Form so its completion could detach the
peer and restore portrait. At the suite tail neither is needed (DesktopMode
runs before it, nothing captures after), so switch to the same createForm +
registerReadyCallback + extraSettle path that works for VRStereoScene, keeping
only the extra texture-warm render for the 1024x512 upload. The captured pixels
are unchanged on the backends that already matched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Media360 screenshot grabbed a stale landscape frame (the
OrientationLock form) on the iOS/tvOS Metal simulators: the panorama
was a mutable Graphics image, and createTexture(Image) reads it via
Image.getRGB(), which on the iOS Metal simulator returns white / drops
the first present (the same readback the MutableImageReadbackTest
already flags on iOS). So the 360 view never presented a frame and the
capture fell back to the last cached landscape content.

Compose the panorama pixels directly into an ARGB int[] and hand it to
Image.createImage(int[], w, h): texturing an array-backed image only
reads the backing array, never a surface, so the first frame presents
on iOS exactly like the working Gpu3DTexturedCube (which also uploads
its texture from an int[]). Building the pixels by hand also drops the
fillArc anti-aliasing, keeping the texture bit-identical across
platforms. Media360 goldens are regenerated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The immutable, array-backed panorama textures slightly brighter on the
android (swiftshader) and JavaScript (WebGL) backends than the old
mutable-image path did, enough to exceed the 3% tolerance on those two
platforms (iOS, mac, Windows and Linux already matched within
tolerance). Adopt the new deterministic captures as the baselines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The developer-guide snippet validator requires every code block to be
include-backed by a compiled source in docs/demos, not inline. Extract
the seven AR/VR snippets into ArSnippets.java / VrSnippets.java (tagged
regions) and include them by tag, so the guide code is compiled against
the real API on every docs build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit 56dcaed into master Jul 7, 2026
57 of 58 checks passed
@shai-almog shai-almog deleted the ar-vr-support branch July 7, 2026 16:15
shai-almog added a commit that referenced this pull request Jul 10, 2026
…posts (#5357)

* Release week 2026-07-10 blog series: ParparVM vs HotSpot + 3 feature posts

Parent post (Fri): "How We Beat HotSpot Performance (By Cheating, But Not
Like That)" -- the PR #5327 performance story from 4.21x slower than warmed
Java 25 to geomean 1.00x parity, with the GC/tagged-Integer deep dives and
the PR #5338 synchronized-on-wrapper build check.

Daily posts: standalone certificate wizard (PR #5339, Sat), AR/VR support
and simulation (PR #5335, Sun), automated store submissions as code incl.
Huawei AppGallery plus organization accounts and self-service account
deletion (PR #5353, Mon).

Includes 1024x512 hero images, post screenshots, and VM/performance
vocabulary for the LanguageTool blog accept-list. Prose gate passes; all
mermaid diagrams verified against the site's mermaid@10 loader.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Blog series: Oxford commas + numeral percentage for the Vale gate

CI runs Vale's Microsoft.OxfordComma and Microsoft.Percentages rules that
the local styles cache was missing. Sweep every serial list in the four
posts and switch "Ninety percent" to "90%".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Restructure the HotSpot post around architecture

Founder feedback pass: add the memory story (table + chart, standalone
ParparVM benchmark note, Java 25 AOT fairness note), explain the HotSpot
big-block heap vs our malloc/free philosophy and the BiBOP page heap,
tease the twists up top, drop the bug-anecdote sections and the warmup/
GraalVM/what-I-got-wrong filler, rework "Going Deeper" around the
readable one-file-per-class tradeoff, add a TL;DR, and close on the
two-big-PRs slow-week note. Charts split into two clean single-series
xycharts (render-verified on mermaid@10 and mermaid.ink for syndication).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant