Skip to content

Parallelize + hoist invariants in QuadSurface resample/rotate warps (bit-identical)#1080

Open
robert-kofler42 wants to merge 1 commit into
ScrollPrize:mainfrom
robert-kofler42:perf/quadsurface-resample-rotate-bbox
Open

Parallelize + hoist invariants in QuadSurface resample/rotate warps (bit-identical)#1080
robert-kofler42 wants to merge 1 commit into
ScrollPrize:mainfrom
robert-kofler42:perf/quadsurface-resample-rotate-bbox

Conversation

@robert-kofler42

Copy link
Copy Markdown

What

The two linear point-warp helpers in QuadSurface.cpp were fully serial and recomputed loop-invariant work per pixel. Both are now parallelized over output rows with their invariants hoisted, bit-identically:

  • resamplePointsLinearPreservingInvalids — the source-x sample (x0/x1/fx) depends only on dstX, so precompute it once per column instead of per pixel (same separable-axis technique as the gen() warp helpers in Speed up QuadSurface::gen() warps by hoisting the separable x axis #1076), then run the row loop under OpenMP.
  • warpAffinePointsLinearPreservingInvalids (rotate path) — the affine is not x-separable (srcX depends on both dstX and dstY), so only the six dstToSrc.at<double>() coefficients are lifted into locals (the compiler reloaded them every pixel because it can't prove the cv::Mat doesn't alias the store) and the row loop is parallelized. The explicit m*dstX + m*dstY + m form is kept to preserve float rounding.

Correctness — bit-identical

Same float ops in the same order, only memoized / CSE'd; rows are independent so OpenMP doesn't reorder any accumulation. Verified with an FNV-1a checksum over all output floats across a sweep of resample factors {0.5, 0.37, 2.0} and rotation angles {30, 90, 12.5, −47}° — identical before/after (9b62b8c86523bfb0). Existing quadsurface/render tests (10) pass.

Performance

End-to-end timing of resample() / rotate() on a 1500×1500 surface (8 threads, RelWithDebInfo). These include the fixed surface-rebuild/clone overhead, so the warp-only speedup is larger:

op before after speedup
resample() 38.8 ms 31.6 ms −19%
rotate() 95.6 ms 63.8 ms −33%

These are user-triggered (resample/rotate/ABF-regrid), off the per-frame gen() hot path, so the absolute wins are modest — but free given they're bit-identical. Independent of #1075/#1076; applies cleanly on main.

The two linear point-warp helpers were fully serial and recomputed
loop-invariant work per pixel. Both are now parallelized over output rows
and have their invariants hoisted, with bit-identical output:

- resamplePointsLinearPreservingInvalids: the source-x sample (x0/x1/fx)
  depends only on dstX, so precompute it once per column instead of per
  pixel (same technique as the gen() warp helpers), then run the row loop
  under OpenMP.
- warpAffinePointsLinearPreservingInvalids (rotate path): the affine is not
  x-separable, so only lift the six dstToSrc.at<double>() coefficients into
  locals (the compiler reloaded them every pixel because it can't prove the
  Mat doesn't alias the store) and parallelize the row loop. The explicit
  m*dstX + m*dstY + m form is kept to preserve float rounding.

No numeric change: output is bit-for-bit identical to the serial code
(verified with an FNV-1a checksum over all output floats across a sweep of
resample factors and rotation angles, identical before/after; existing
quadsurface/render tests pass).

End-to-end timing of resample()/rotate() on a 1500x1500 surface (8 threads,
includes the fixed surface-rebuild overhead, so the warp-only speedup is
larger): resample 38.8 -> 31.6 ms (-19%), rotate 95.6 -> 63.8 ms (-33%).
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the scroll Team on Vercel.

A member of the Team first needs to authorize it.

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