SDF RRect Notes
Implementation at src/effect/shape/ge_sdf_rrect_shader_shape.cpp
Variant Selection
There are four shader variants:
- Uniform radius, distance only
- Uniform radius, distance + normal
- Per-corner radius, distance only
- Per-corner radius, distance + normal
The fast path is only used when the rounded rect is both:
- uniform across all corners
- circular, not elliptical
If any corner differs, or if any corner uses different x/y radii, the code
falls back to the more expensive per-corner shader.
CPU-side Radius Adjustment
Two CPU-side details matter more than they first appear:
EXTEND = 0.5
The shader does not use the raw input size and radii directly. The CPU adds
EXTEND to halfSize and to the resolved radii before binding uniforms.
This is a bias to better match Skia's rounded-rect edge placement. If a render looks off by about half a pixel, this is the first thing to remember.
Per-corner radii are normalized as a group
ResolveCornerRadii() does more than clamp each corner independently. It also
scales radii like CSS border-radius resolution when opposite corners would
otherwise overflow the rect width or height.
This means:
- a corner can be valid by itself but still get reduced
- the shader may not receive exactly the same radii that were originally set
Per-corner Radius Selection
The per-corner shader chooses the active radius from the sign of the fragment position relative to the center, not from any explicit corner index.
That means asymmetric test cases like TL=0, TR=30, BR=0, BL=30 are a good way
to validate the quadrant mapping visually.
Normal Variant Output Layout
The normal-capable shaders do not output a color image. They pack data as:
vec4(normal.x, normal.y, packedDir, sdf)
The important consequence is:
randgare the 2D normalbis packed auxiliary direction/position dataais the signed distance
If the result is viewed as a normal RGBA image, it can look "wrong" or "boxy"
even when the SDF in a is correct. Consumers that only need the shape should
read a.
Why The Interior Normal Looks Flat In Regions
Inside the core rectangle, the shader does not compute a smooth vector pointing to the nearest edge. It picks a dominant axis instead.
That creates a piecewise-constant interior normal field with wedge-like regions. This is intentional in the current implementation. If a future effect needs a fully smooth interior normal, this is the place that would need to change.
Performance Notes
The uniform-radius shaders are clearly cheaper than the per-corner shaders. The main reason is that the per-corner normal path always evaluates extra corner math for every fragment:
- corner selection
- ellipse distance in
sdEllipse() - ellipse gradient in
ellipseGrad()
Because those values are computed before the final mix(), even center pixels
pay for the ellipse path.
A practical estimate:
- Per-corner normal vs uniform normal: about
1.7xto2.5xfragment ALU cost - Per-corner distance-only vs uniform distance-only: about
1.5xto2.0x
These are source-level estimates, not measured GPU cycle counts. Real hardware cost depends on the backend shader compiler and the target GPU.