render: Improve hairline strokes and scaling strokes on WebGL and WGPU#23011
render: Improve hairline strokes and scaling strokes on WebGL and WGPU#23011darktohka wants to merge 6 commits intoruffle-rs:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request significantly improves rendering quality of hairline and scaled strokes in Ruffle's WebGL and WGPU backends by implementing scale-aware tessellation. The implementation adds an LRU tessellation cache that stores up to 4 different tessellations per graphic at different scales, retessellating only when shapes grow or shrink by more than 2x. This approach addresses numerous long-standing rendering issues where strokes appeared too thick or too thin when graphics were scaled.
Changes:
- Introduces
TessellationCachewith LRU eviction to cache tessellated shapes at different scales - Adds
register_shape_with_scale()method to render backends to support scale-aware tessellation - Modifies tessellator to adjust hairline stroke width and tessellation tolerance based on scale
- Updates
Graphicdisplay objects to calculate current scale and retrieve or create appropriately scaled tessellations
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| core/src/tessellation_cache.rs | New LRU cache for storing up to 4 tessellated shapes per graphic at different scales |
| core/src/lib.rs | Adds tessellation_cache module to the core library |
| core/src/display_object/graphic.rs | Integrates tessellation cache; calculates scale from transform matrix and retrieves/creates scaled tessellations |
| render/src/backend.rs | Adds register_shape_with_scale() trait method with default implementation |
| render/wgpu/src/backend.rs | Implements scale-aware shape registration for WGPU backend |
| render/webgl/src/lib.rs | Implements scale-aware shape registration for WebGL backend |
| render/src/tessellator.rs | Adjusts hairline stroke width and tessellation tolerance based on scale to prevent artifacts |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Tests that have improved:
Slightly different but visually indistinguishable (mostly due to precision increasing):
Broken:
|
| [image_comparisons."output.03"] | ||
| trigger = 3 | ||
| tolerance = 128 | ||
| max_outliers = 3 |
There was a problem hiding this comment.
Why do you need to update edittext tests? They don't use strokes
There was a problem hiding this comment.
You are right. I updated the edittext tests since they were failing on my local machine, but we might not have to edit them at all.
| [image_comparisons.output] | ||
| tolerance = 15 | ||
| max_outliers = 24 | ||
| max_outliers = 62 |
There was a problem hiding this comment.
I'm a bit worried that we're getting farther away from FP after this PR. Have you investigated why?
Maybe the output is not from FP and we could fix up those tests before this PR.
There was a problem hiding this comment.
There might be a mismatch between software rendering and hardware rendering...
Maybe the tests don't have to be edited in any way (or maybe it's fine to edit them) -
8 tests fail on the PR's CI (https://github.com/ruffle-rs/ruffle/actions/runs/21928846875/job/63327966747).
While the macOS job is 100% successful (the only one with hardware rendering !!!)
On my laptop, 11 tests failed: the 8 tests + the 3 "edittext" tests, you did point out that it is unnecessary to edit those tests.
Today, the same tests, all of them are successful on my laptop (NixOS, Linux, 64-bit AMD Ryzen 5 5600H + RTX 3060).
It's possible that commit dd53f0b (before the tests were edited) might run the tests 100% successfully with hardware rendering enabled:
cargo nextest run --profile ci --cargo-profile ci --workspace --locked --no-fail-fast -j 4 avm1 visual from_gnash from_shumway --features imgtests
Any help appreciated.
|
@darktohka Just a general remark about visual tests: the tolerance/max_outliers are set so that tests pass on CI and on devs' machines. You should either:
If you make changes and edit tests to pass locally in the same PR, it will result in an unmergeable mess. If there are any changes to tests, we want them to be well documented, and well-thought-out. I'd recommend to stick to the 2nd option for now. It should be relatively easy—set 0 tolerance, push, download image diffs, set appropriate tolerance and outliers based on image diffs. At the end of the day, if your PR brings us closer to Flash Player, you shouldn't need to increase tolerance/outliers. If you do, it could mean a bad test that didn't have output from Flash Player. I can then take care of those tests and fix them before merging this PR. TL;DR: my recommendation is to revert changes to tolerance/outliers in tests and see what happens. |
|
Good point. I will remove the change to the tests from this PR and keep only the functional changes, let's see what happens. |
6d8a2af to
dd53f0b
Compare
|
I think those failures are caused by the fact that we are using Ruffle's and not FP's output. I'll take a look in my free time and I'll try fixing them up. |
|
Worth noting that something similar seems to be true for #22961. That PR caused a bunch of changes to images because lyon changed a little bit about its rendering methods and most of those tests were from Ruffle, not FP. I fixed that up by downloading the images from CI, but ideally we'd replace those tests with FP images and then see whether the lyon update improves their consistency with Flash. |
|
Made 2 PRs which fix tests failing here:
Hopefully after merging them and rebasing this PR, they should stop failing, and they should even improve a bit (but don't worry about that, we can lower tolerance later). After those, there are few known failures failing, I'll try looking into them, but I think we're just closer to Flash Player and that's why they are failing. |
|
@darktohka Can you rebase the PR on top of main? The majority of tests should stop failing. |
dd53f0b to
fee6a8e
Compare
|
Only one non-known-failure test is failing: |
|
This should fix the missing ninja body and barely visible outlines of coins and mines in the N, right? swf: N.zip |
It fixes the barely visible outlines and improves on the mines, but does not improve the player character:
Strangely enough, JPEXS doesn't like the player character either: |
|
This PR Supersedes and Close #9981 ? Would you test this #9981 (comment) |







This pull request improves both hairline strokes and scaling strokes on the Web (WGPU, WebGL renderers) and Desktop (WGPU renderer) targets.
The main idea is to keep track of the scale of the graphics that are being tessellated on the rendering backends. The tessellated shapes are then stored in a tessellation cache, which is a simple LRU cache that keeps track of the most frequently tessellated shapes (4 max per shared graphic). This means that the last 4 uniquely used tessellated scale buckets will be left cached. Shapes will only be retessellated if they grow or shrink by 2x relative to a cached variant (controlled by
RETESSELLATION_SCALE_THRESHOLD).When a shape grows disproportionately, it is re-tessellated. The re-tessellation precision (threshold) is specified by the scale. The larger the scale, the more precise the tessellation will be: small objects are expected to have less detail either way.
Tessellation cache is reused between graphic instances that use the same graphic as an optimization.
Hairline stroke rendering is also improved.
This fixes issues such as (tested them): #18852 #21803 #751 #7369 #14268 #13984 #1955 #3216 #9044 #2023 #11704 #12360 #14551 #20211 #1412
Partially (composite issues - not all from these are fixed, just the strokes): #10524 #12057
Could not test (site locks, missing SWF, etc): #20345 #3216 #18855 #1625 #9309
Relevant technical discussions: #7042 #7369 #751
Before:


After:
Before:


After:
Before:


After:
Before:


After: