GPU: build the warp coverage mask per block with interval bounds

The coverage mask from 94fe300 evaluated the f64 warp for every output
pixel. coverage_mask now asks Warp::block_coverage for each 32 x 16 block
(the shared warp formulas evaluated with outward-rounded intervals, no
edge margin) and evaluates only undecided blocks near the edge per pixel
with Warp::covers; the bits are unchanged
(coverage_mask_matches_per_pixel_decision, incl. partial words and
padding bits; coverage_mask_with_the_horizon_in_view, a perspective
denominator exactly 0 inside the image, so the clamp and
undecided-division paths go through block_coverage and the mask).

Measured (release, synthetic source, embedded lens warp + CA +
perspective as in embedded_lens_perspective_edges_match):
6000x4000 ~30 ms -> 2.7 ms (32 threads, 11x), ~580 ms -> 24.5 ms
(1 thread, 24x); 2048x1365 3.6 ms -> 1.0 ms (3.5x), 67.5 ms -> ~5 ms
(12-14x). Rotate270 the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
radialmonster
2026-10-07 19:29:29 -04:00
co-authored by Claude Opus 5.5
parent 2b2ebfc7ca
commit 1237e198d5
2 changed files with 115 additions and 18 deletions
+11 -1
View File
@@ -9,7 +9,17 @@ The CPU reference evaluates that boundary in double precision; GPU float
rounding otherwise can turn a blank edge pixel into a photo pixel. The mask
uses one bit per output pixel (rows padded to 32 bits). Sampling and color
corrections remain on the GPU, and existing geometry stage caching reuses the
result. This adds CPU coordinate evaluation only when warped geometry rebuilds.
result. The mask is built per 32 × 16 block (`Warp::block_coverage`): the warp
formulas are written once, generic over `lightcraft_geom::Real`, and evaluated
with outward-rounded `Interval`s over the block. Those bounds enclose the f64
result of every pixel in the block, so a block whose bounds lie inside the
image edges (the same f64 comparisons, no margin), or beyond one of them, is
filled at once; only blocks across the edge, or whose bounds are undecided (a
perspective denominator that may vanish), evaluate each pixel (`Warp::covers`,
the framing chain `Warp::frame` the CPU resample uses too). The bits equal the
per-pixel decision. At 6000 × 4000 with lens warp + perspective the mask takes
~2.7 ms (32 threads; ~24.5 ms on one) instead of ~30 ms (~580 ms), and only
when warped geometry rebuilds.
## Backends, environment variables and troubleshooting (issue #136)
wgpu loads the driver of **every** backend in an instance's set while it enumerates adapters — even