perf(IBA): precompute the resample mapping into per-axis tables - #5395
Open
wingfiring wants to merge 1 commit into
Open
perf(IBA): precompute the resample mapping into per-axis tables#5395wingfiring wants to merge 1 commit into
wingfiring wants to merge 1 commit into
Conversation
wingfiring
force-pushed
the
feat/resample-axis-map
branch
3 times, most recently
from
August 11, 2026 01:42
c2e62fb to
d5b2c74
Compare
resample_scalar() recomputes the destination-to-source mapping for every
pixel, but the mapping is separable and axis aligned: the source column a
destination column reads depends only on x, and likewise for rows. Building
one table per axis makes that O(width + height) instead of O(width *
height), and leaves an inner loop that indexes rather than computes.
Both sampling modes are covered. Nearest stores one tap per destination
coordinate plus the interval over which the source is in range, so the
in-range span needs no per-pixel bounds test. Bilinear stores both taps and
the weight; it keeps the two clamped taps rather than deriving the second
from the first so that the boundary needs no branch.
The tables serve any source with resident pixels whose channels and pixels
are packed within a scanline; note that this permits a gap between rows, so
the row table is built from the actual scanline stride rather than from
width * nchannels. Bilinear additionally declines sources whose data window
differs from their display window, because WrapClamp folds to the display
window and only then goes black for anything still outside the data window,
which does not decompose into two independent per-axis tables. Anything not
served falls back to the per-pixel path, which is unchanged.
Measured on a 1920x1080 to 1024x512 RGBA resample, best of 10 trials:
scalar axis map
u8->u8 nearest 581us 314us 1.9x
f->f nearest 612us 284us 2.2x
u8->u8 bilinear 1854us 646us 2.9x
f->f bilinear 2060us 579us 3.6x
Both are bit-identical to the per-pixel path. The new test compares them by
resampling the same pixels twice, once from a source the tables accept and
once from one padded so that they decline it, so the comparison needs no
switch to flip and covers the fallback selection as well.
Signed-off-by: Jackson Sun <jackson.sun@autodesk.com>
wingfiring
force-pushed
the
feat/resample-axis-map
branch
from
August 11, 2026 03:44
d5b2c74 to
fc372fb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
resample_scalar()recomputes the destination-to-source mapping for everypixel. But the mapping is separable and axis aligned: which source column a
destination column reads depends only on
x, and which source row it readsdepends only on
y. Computing it once per axis makes itO(width + height)instead of
O(width * height), and leaves an inner loop that indexes ratherthan computes.
Both sampling modes are covered.
which the source is in range. Inside that interval no per-pixel bounds test
is needed; outside it the destination is black, which becomes a run rather
than a branch per pixel.
rather than deriving the second as
tap + 1, so the boundary costs no branchin the hot loop.
The tables serve any source with resident pixels whose channels and pixels are
packed within a scanline. Note that
contiguous_scanline()permits a gapbetween rows, so the row table is built from
scanline_stride()rather thanfrom
width * nchannels. Bilinear additionally declines sources whose datawindow differs from their display window:
WrapClampfolds to the displaywindow and only then goes black for anything still outside the data window,
which does not decompose into two independent per-axis tables. Anything not
served falls back to
resample_scalar(), which is unchanged.Performance
Against
resample_scalar(), which is what this replaces. 1920x1080 to1024x512, RGBA, best of 10 trials, Windows 11 / MSVC 14.50 / x64 Release:
resample_scalaru8 -> u8nearestu16 -> u16nearestf -> fnearestu8 -> u8bilinearu16 -> u16bilinearf -> fbilinearMixed-type cases such as
f -> u8barely move, but that is not this code: whenRtype != Atype && Rtype != TypeFloat,OIIO_DISPATCH_COMMON_TYPES2roundtrips the destination through a temporary float
ImageBuf— copying inpixels
resample()is about to overwrite — and that copy pair costs roughly3.5 ms regardless of which kernel runs underneath. Worth fixing, separately.
Relationship to the Highway path
This is orthogonal to the Highway work and does not compete with it. It changes
how the mapping is computed; Highway changes how the arithmetic is
vectorized. The dispatch puts the tables after the Highway check and
before
resample_scalar(), so Highway keeps every case it already serves andthe tables pick up the rest — all of nearest, which Highway does not implement,
plus everything Highway declines.
To answer the obvious question of why not simply widen Highway's coverage
instead: the two are complementary, not alternatives. I have a four-channel
SSE2 specialization of these same tables working locally, and against
resample_hwyon the bilinear cases above it runs 1.7x to 2.3x faster —because the tables remove the address arithmetic that SIMD cannot help with,
not because of anything about the vector width. My plan after this lands is to
extend the tables to the other resample modes and then apply them inside the
Highway path, which should carry that factor to every target Highway supports.
Keeping that out of this PR is what makes this one reviewable.
Note on ordering: because the tables sit behind the Highway check, nearest
must not reach
resample_hwy— it ignores itsinterpolateargument andalways bilerps. That is fixed in #5393, which this PR should land
after (or alongside).
Tests
test_resample_correctness()covers two things.Against independently evaluated expected values: a magnification and a
reduction where source pixel
(x,y)holdsx + 10*y, so each destinationpixel names the source pixel it came from; plus a source whose data window is a
2x2 crop of a 4x4 display window, where the outer ring must come out black.
Against
resample_scalar(), bit for bit. There is no runtime switch to flip —dispatch is by capability — so the two paths are selected by input shape
instead: the same pixels are resampled twice, once from a source with padded
scanlines (which the tables accept) and once from a source with padded pixels
(which they decline).
memcmp, not a tolerance, because the failure a mistakein the mapping arithmetic produces is a one-pixel shift, and a tolerance would
hide it. This also gets the fallback selection under test.
testsuite/oiiotool-xformgains a--resample:interp=0case and its referenceimage, generated by
resample_scalar()before this change.Checklist:
and if I used AI coding assistants, I have an
Assisted-by: TOOL / MODELline in the pull request description above.
behavior.
PR, by pushing the changes to my fork and seeing that the automated CI
passed there. (Exceptions: If most tests pass and you can't figure out why
the remaining ones fail, it's ok to submit the PR and ask for help. Or if
any failures seem entirely unrelated to your change; sometimes things break
on the GitHub runners.)
fixed any problems reported by the clang-format CI test.
corresponding Python bindings. If altering ImageBufAlgo functions, I also
exposed the new functionality as oiiotool options.