Skip to content

Speed up QuadSurface::gen() warps by hoisting the separable x axis#1076

Open
robert-kofler42 wants to merge 1 commit into
ScrollPrize:mainfrom
robert-kofler42:perf/quadsurface-warp-hoist
Open

Speed up QuadSurface::gen() warps by hoisting the separable x axis#1076
robert-kofler42 wants to merge 1 commit into
ScrollPrize:mainfrom
robert-kofler42:perf/quadsurface-warp-hoist

Conversation

@robert-kofler42

Copy link
Copy Markdown

What

QuadSurface::gen()'s four warp helpers (warpBilinearReplicateVec3f, warpNearestConstU8, warpNearestConstVec3f, warpBilinearConstVec3f) implement an axis-aligned scale+translate resample: src_x = ox + dx*sx, src_y = oy + dy*sy.

The mapping is separable: the source-x sample (x0/x1/wx for bilinear, the rounded source index for nearest) depends only on dx, not dy. But the inner loop recomputed it for every one of the dh rows — including an std::lround per pixel in the nearest variants. This hoists the x-axis computation into a per-column array computed once per call and reused across all rows (and hoists iwy = 1 - wy to per-row).

Correctness — bit-identical

Bit-identical for all finite warp parameters (the only kind gen() produces): the same float expressions are evaluated in the same order, just memoized.

Honest edge note: on non-finite source coordinates (not reachable from gen(), whose warp params are always finite), the bilinear-const variant now writes the border value instead of the original's out-of-bounds read (int(NaN)row[INT_MIN]) — i.e. strictly safer, not a regression.

Verified with:

  • an FNV-1a checksum over all output float bits across a sweep of scales/offsets — identical before/after (18b53e9196982b08);
  • the existing render/quadsurface unit tests (16 pass).

Performance

Profiled with perf on a synthetic 4000×4000 surface: the warp helpers were ~53% of gen() self-time, __lroundf alone 8.9%.

Benchmark (gen() with normals, median of 5, 32-core machine, RelWithDebInfo):

scenario before after speedup
interactive, 512×512 tile (one gen() call, internally parallel) 2.54 ms 1.43 ms 1.78×
interactive, 1024×1024 tile 10.13 ms 6.09 ms 1.66×
tiled batch render (outer OMP over tiles) 0.90 ms 0.90 ms ~0 (memory-bandwidth bound)

The win lands on the compute-bound interactive render path (the VC3D viewer issues one gen() per view, which parallelizes internally) — i.e. pan/zoom latency. The tiled batch renderer (vc_render_tifxyz) is memory-bandwidth bound and unchanged, but does the same total work with fewer instructions.

Notes

  • Independent of Make QuadSurface::gen() safe for concurrent rendering #1075 (touches different functions in the same file); applies cleanly on main.
  • The per-column arrays are small function-local std::vectors filled single-threaded before the row loop and read-only inside it (safe under the warps' internal OpenMP).

The four warp helpers (warpBilinearReplicateVec3f, warpNearestConstU8,
warpNearestConstVec3f, warpBilinearConstVec3f) implement an axis-aligned
scale+translate resample: src_x = ox + dx*sx, src_y = oy + dy*sy. The
mapping is separable, so the source x sample (x0/x1/wx for bilinear, the
rounded source index for nearest) depends only on dx, not dy -- yet the
inner loop recomputed it for every one of the dh rows, including an
std::lround per pixel in the nearest variants.

Hoist the x-axis computation into a per-column array computed once per
call and reuse it across all rows; also hoist iwy = 1 - wy to per-row.

Bit-identical for all finite warp parameters (the only kind gen()
produces): the same float expressions are evaluated in the same order,
just memoized. On non-finite source coordinates -- unreachable here --
the bilinear-const variant now writes the border value instead of the
original's out-of-bounds read, i.e. strictly safer.

Profiled with perf on a synthetic 4000x4000 surface: the warp helpers
were ~53% of gen() self-time, __lroundf alone 8.9%.

Benchmark (gen() with normals, median of 5, 32-core machine):
  interactive (one gen() call, internally parallel):
    512x512   tile: 2.54 -> 1.43 ms/tile  (1.78x)
    1024x1024 tile: 10.13 -> 6.09 ms/tile (1.66x)
  tiled batch render (outer OMP over tiles): unchanged (~0.90 ms/tile,
    memory-bandwidth bound).

Verified bit-identical via an FNV-1a checksum over all output float bits
(identical before/after) and the existing render/quadsurface unit tests.
@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