Skip to content

Screenshot test for clip under scale+translate (#4907)#4917

Closed
shai-almog wants to merge 3 commits into
masterfrom
clip-under-scale-translate-test
Closed

Screenshot test for clip under scale+translate (#4907)#4917
shai-almog wants to merge 3 commits into
masterfrom
clip-under-scale-translate-test

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

  • Adds ClipUnderScaleTranslate screenshot test that reproduces the magnifier-style render path from issue glitchy metal drawing #4907 (set a small clip, then g.scale(2,2) + g.translate(-ax,-ay), then a fillRect covering the whole post-transform cell).
  • A navy outline of the expected clip rect anchors the diff; a corner green sentinel after popClip catches broken pop/transform-reset.
  • Uses the standard AbstractGraphicsScreenshotTest 2×2 grid so the form-graphics path and the mutable-image path are captured separately, narrowing the suspect target if only one cell fails.

This is purely a test addition -- no framework code is touched. We're letting CI render the test on each platform to see whether iOS Metal currently clips correctly or leaks outside the rect.

Test plan

  • JavaSE simulator cells render gray cell, navy outline, centred red square, small green corner dot.
  • Android renders the same.
  • iOS legacy renderer renders the same.
  • iOS Metal renderer: check whether red leaks outside the navy outline -- if so, glitchy metal drawing #4907 is reproduced and the failing cell (form-graphics vs mutable-image) localises the bug.

🤖 Generated with Claude Code

Repro for issue #4907 (glitchy metal drawing). The issue reports that a
magnifier-style render path -- set a small clip rect, then apply
g.scale(scale, scale) + g.translate(-ax, -ay) and draw a much larger
surface -- correctly clips on the legacy iOS pipeline / JavaSE / Android
but leaks outside the clip rect under iOS Metal.

The new ClipUnderScaleTranslate test runs the AbstractGraphicsScreenshotTest
2x2 (AA off/on, form-graphics vs mutable-image) and renders the exact
sequence from ddyer0's snippet:

  g.pushClip();
  g.clipRect(centred small rect);
  g.scale(2.0f, 2.0f);
  g.translate(-ax, -ay);
  g.fillRect(<covers entire post-transform cell>);   // should clip to rect
  g.translate(ax, ay);
  g.scale(0.5f, 0.5f);
  g.popClip();

A navy outline of the expected clip rect is drawn before the clipped
fill so the screenshot diff has a stable anchor; a tiny green sentinel
is drawn after popClip so a broken pop/transform-reset is also caught.
On a working pipeline the cell shows a centred red square inside the
navy outline; on the buggy pipeline the red leaks outside (worst case:
the entire cell is red). Splitting the form-graphics path from the
mutable-image path lets the diff narrow the suspect target without
extra instrumentation.

Wired into Cn1ssDeviceRunner after Clip and into the HTML5 skip list to
match the other graphics screenshot tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 11, 2026

JavaScript port screenshot updates

Compared 16 screenshots: 15 matched, 1 missing reference.

  • graphics-clip-under-scale-translate — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/javascript/screenshots/graphics-clip-under-scale-translate.png.

    graphics-clip-under-scale-translate
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-clip-under-scale-translate.png in workflow artifacts.

…4907)

The previous version drew a solid red fillRect covering the entire
post-transform area, with a navy outline of the expected clip rect.
When the clip is honoured the red square sits exactly on the navy
outline, so a small bleed at the edges is hard to distinguish from
normal antialiasing -- on the JavaSE simulator the result looked
correct but it was hard to tell whether the test would catch a
subtle leak.

Switch to a 5-patch pattern sized in source (pre-scale) coordinates:

  - Central red patch sized 1/scale * clipW by 1/scale * clipH so
    that, under the 2x scale, it exactly fills the clip rect.
  - Four side patches (green N, blue S, orange W, magenta E) placed
    one patch-width / height away from the centre. With the 2x scale
    they end up one full clipW / clipH off-centre, entirely outside
    the clip rect under a correct clip and invisible.

A correct render shows only a red square inside a black clip-rect
outline (drawn last, on top of the fill so its position is stable).
A bleed shows a coloured patch outside the outline, and the colour
identifies which post-transform direction leaked. The clip rect is
now 1/5 of the cell (was 1/3) so there is plenty of gray buffer
around it to make even a thin bleed visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 11, 2026

Android screenshot updates

Compared 106 screenshots: 105 matched, 1 missing reference.

  • graphics-clip-under-scale-translate — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/android/screenshots/graphics-clip-under-scale-translate.png.

    graphics-clip-under-scale-translate
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-clip-under-scale-translate.png in workflow artifacts.

Native Android coverage

  • 📊 Line coverage: 11.27% (6225/55246 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.95% (30822/344248), branch 3.79% (1241/32726), complexity 4.92% (1545/31377), method 8.65% (1270/14679), class 14.56% (289/1985)
    • 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)
      • 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)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 960.000 ms
Base64 CN1 encode 141.000 ms
Base64 encode ratio (CN1/native) 0.147x (85.3% faster)
Base64 native decode 864.000 ms
Base64 CN1 decode 315.000 ms
Base64 decode ratio (CN1/native) 0.365x (63.5% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 11, 2026

iOS Metal screenshot updates

Compared 106 screenshots: 105 matched, 1 missing reference.

  • graphics-clip-under-scale-translate — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-metal/graphics-clip-under-scale-translate.png.

    graphics-clip-under-scale-translate
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-clip-under-scale-translate.png in workflow artifacts.

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 109000 ms
Simulator Boot (Run) 1000 ms
App Install 16000 ms
App Launch 7000 ms
Test Execution 275000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1687.000 ms
Base64 CN1 encode 1886.000 ms
Base64 encode ratio (CN1/native) 1.118x (11.8% slower)
Base64 native decode 1717.000 ms
Base64 CN1 decode 2254.000 ms
Base64 decode ratio (CN1/native) 1.313x (31.3% slower)
Base64 SIMD encode 574.000 ms
Base64 encode ratio (SIMD/native) 0.340x (66.0% faster)
Base64 encode ratio (SIMD/CN1) 0.304x (69.6% faster)
Base64 SIMD decode 510.000 ms
Base64 decode ratio (SIMD/native) 0.297x (70.3% faster)
Base64 decode ratio (SIMD/CN1) 0.226x (77.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 62.000 ms
Image createMask (SIMD on) 15.000 ms
Image createMask ratio (SIMD on/off) 0.242x (75.8% faster)
Image applyMask (SIMD off) 195.000 ms
Image applyMask (SIMD on) 116.000 ms
Image applyMask ratio (SIMD on/off) 0.595x (40.5% faster)
Image modifyAlpha (SIMD off) 187.000 ms
Image modifyAlpha (SIMD on) 102.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.545x (45.5% faster)
Image modifyAlpha removeColor (SIMD off) 200.000 ms
Image modifyAlpha removeColor (SIMD on) 137.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.685x (31.5% faster)
Image PNG encode (SIMD off) 3897.000 ms
Image PNG encode (SIMD on) 1291.000 ms
Image PNG encode ratio (SIMD on/off) 0.331x (66.9% faster)
Image JPEG encode 578.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 11, 2026

iOS screenshot updates

Compared 106 screenshots: 105 matched, 1 missing reference.

  • graphics-clip-under-scale-translate — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots/graphics-clip-under-scale-translate.png.

    graphics-clip-under-scale-translate
    Preview info: JPEG preview quality 50; JPEG preview quality 50; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-clip-under-scale-translate.png in workflow artifacts.

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 98000 ms
Simulator Boot (Run) 2000 ms
App Install 15000 ms
App Launch 6000 ms
Test Execution 309000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 3006.000 ms
Base64 CN1 encode 3034.000 ms
Base64 encode ratio (CN1/native) 1.009x (0.9% slower)
Base64 native decode 1631.000 ms
Base64 CN1 decode 2200.000 ms
Base64 decode ratio (CN1/native) 1.349x (34.9% slower)
Base64 SIMD encode 669.000 ms
Base64 encode ratio (SIMD/native) 0.223x (77.7% faster)
Base64 encode ratio (SIMD/CN1) 0.221x (77.9% faster)
Base64 SIMD decode 919.000 ms
Base64 decode ratio (SIMD/native) 0.563x (43.7% faster)
Base64 decode ratio (SIMD/CN1) 0.418x (58.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 81.000 ms
Image createMask (SIMD on) 35.000 ms
Image createMask ratio (SIMD on/off) 0.432x (56.8% faster)
Image applyMask (SIMD off) 411.000 ms
Image applyMask (SIMD on) 120.000 ms
Image applyMask ratio (SIMD on/off) 0.292x (70.8% faster)
Image modifyAlpha (SIMD off) 180.000 ms
Image modifyAlpha (SIMD on) 233.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.294x (29.4% slower)
Image modifyAlpha removeColor (SIMD off) 391.000 ms
Image modifyAlpha removeColor (SIMD on) 205.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.524x (47.6% faster)
Image PNG encode (SIMD off) 1566.000 ms
Image PNG encode (SIMD on) 1040.000 ms
Image PNG encode ratio (SIMD on/off) 0.664x (33.6% faster)
Image JPEG encode 1063.000 ms

…#4907)

The previous version picked ax = clipCenterX and ay = clipCenterY, which
made the magnifier-origin the fixed point of the transform rather than
the clip-rect centre. After  g.scale(s); g.translate(-ax, -ay)  a source
point p ends up at native screen coord  s * (p - (ax, ay))  (Graphics
adds xTranslate/yTranslate before the impl applies the scale; every
active port has isTranslationSupported() == false), so the old math sent
the centre patch and all four side patches to screen positions outside
the cell on every port. The CI output for the previous PR showed empty
cells on iOS GL and iOS Metal -- no red, no leak signal, just the clip
outline and the corner sentinel.

Set ax = clipCenterX / scale and ay = clipCenterY / scale instead. With
that choice the transform's fixed point is the clip-rect centre, and a
source patch centred at (clipCenterX, clipCenterY) of size
(clipW / scale, clipH / scale) maps exactly onto the clip rect on
screen. The four side patches placed one source-patch-size off-centre
land in the cell rows / columns immediately adjacent to the clip rect.

Co-Authored-By: Claude Opus 4.7 (1M context) <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