Skip to content

test(raster): lock POLY spans against a 4×4 sample oracle - #333

Draft
qianiaoo wants to merge 2 commits into
pocket-stack:mainfrom
qianiaoo:test/poly-span-oracle
Draft

test(raster): lock POLY spans against a 4×4 sample oracle#333
qianiaoo wants to merge 2 commits into
pocket-stack:mainfrom
qianiaoo:test/poly-span-oracle

Conversation

@qianiaoo

Copy link
Copy Markdown
Contributor

Why

The POLY interior-span solver used div_euclid on a negative edge step as if it floored. For a negative divisor it ceils, so one extra boundary column was handed to fill_opaque and a partial-coverage pixel became solid. Existing tests only count interior partials, which cannot see that direction of error.

What

  • Floor the s < 0 span bound with (-k).div_euclid(-s).
  • Compare the span fill to a per-pixel 4×4 oracle (same edge functions, no interior shortcut) across 7° rotation steps plus 45/90/135/180, and over left/right/top/bottom/corner clips so Sutherland-Hodgman produces 5..=8-gons.
  • Raster the rotated rounded-full box (rounded-[999px] + rotate-28) and a solid 3D face (perspective + rotate-x/y), which previously only asserted DrawList ops.

cargo test --release in engine/core: 132 passed.

qianiaoo and others added 2 commits August 25, 2026 10:58
`TRI` has no coverage field, so a rotated solid box resolves to two grey levels
at any resolution — `emit_box` -> Sutherland-Hodgman -> `emit_tri` rounds every
vertex to an integer pixel and there is nowhere to put a partial one. Recorded
at `draw.rs:10-18` as a v1 degradation; what was missing was the price.

`POLY` (opcode 10, `3 + N` words) carries the whole clipped convex polygon and
one flat colour, so coverage is computed over the shape rather than per
triangle. Per-triangle coverage is the wrong fix and the guard says so: two
sequential blends are not one blend, and the shared diagonal of a rotated box
keeps 68 interior partial pixels. Over the polygon it keeps none.

It is not slower. `poly()` solves each scanline for the fully-interior x-range,
fills it as one run and samples 4x4 only at the ends — O(perimeter), not
O(area) — against a `tri` that evaluated three `orient()` calls for every pixel
of the bounding box with no incremental stepping. Measured at 0.99x on a
standalone bench and 22% faster end to end on eight rotated bars at 1080p.

The inner loop stays integer: edge functions in 4*F fixed point, quarter-pixel
offsets as +/-1 and +/-3, `div_euclid` for the span solve. No float enters it,
so the frame-hash contract carries over.

Hardware backends without per-pixel coverage decode `POLY` to a triangle fan —
today's binary fill, byte-identical output. `Fill::Grad` keeps its TRI fan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
div_euclid ceils on a negative divisor, so the s<0 span solver was
handing one extra boundary column to fill_opaque. Compare the optimized
fill to a per-pixel 4×4 oracle across rotations and clip cases, and
raster the rotated rounded-box and solid 3D-face paths that previously
only checked DrawList ops.
@qianiaoo
qianiaoo force-pushed the test/poly-span-oracle branch from 18e8c58 to 1c7c914 Compare August 25, 2026 03:23
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