No longer needed with AVRational64. Now we can just apply_op_q(), on
everything except floating point types (which are still not representable
in AVRational64 and likely never will be, though this is mostly a moot
point as the uops layer can still optimize this further).
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
1. This is currently impossible to trigger
2. We're about to switch to AVRational64, eliminating this concern
3. The AVRational64 API intentionally doesn't expose av_reduce64()
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
Can be used to extract a reduced subset of operations affecting only certain
output planes, e.g. splitting an op list into a "memcpy" and a "non-memcpy"
part, or splitting apart op lists for independent or subsampled planes.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
Replaces a few "nan" value ranges by real values, and drops a bunch of
redundant non-FMA variants that resulted from this bug.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
I want to start adding more data layouts, like semiplanar formats (nv12), or
palette formats. I made an effort to distinguish existing checks for rw.packed
into "mode != PLANAR" and "mode == PACKED", based on the intent of the
surrounding code, in anticipation of these new layouts.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
Using ff_sws_rw_op_planes(), to directly encode the relevant condition (i.e.
physical access to multiple planes).
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
Instead of hard-coding SWS_PIXEL_F32 here. This is not really useful
yet, but I wanted to clean up the semantics here regardless.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
This one failed to adjust prev->type to the result of filtering,
leading to basically broken intermediate op lists. Fortunately, the
optimizer usually ended up eliminating these cases altogether.
Replace it by a fixed check to merge filters with any prior conversion
op that satisfies the criteria (and is deemed beneficial).
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
This is a minor cosmetic improvement that allows me to use more
convenient names for a filter-related metadata fields, without
confusion.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
Instead of implicitly testing for NaN values. This is mostly a straightforward
translation, but we need some slight extra boilerplate to ensure the mask
is correctly updated when e.g. commuting past a swizzle.
Signed-off-by: Niklas Haas <git@haasn.dev>
This accidentally unconditionally overwrote the entire clear mask, since
Q(n) always set the denominator to 1, resulting in all channels being
cleared instead of just the ones with nonzero denominators.
Signed-off-by: Niklas Haas <git@haasn.dev>
Fixes a pre-existing latent bug in the subpass splitting, that was
made worse / exposed by 048ca3b367.
Fixes: cba54e9e3b
Signed-off-by: Niklas Haas <git@haasn.dev>
It was a bit clunky, lacked semantic contextual information, and made it
harder to reason about the effects of extending this struct. There should be
zero runtime overhead as a result of the fact that this is already a big
union.
I made the changes in this commit by hand, but due to the length and noise
level of the commit, I used Opus 4.6 to verify that I did not accidentally
introduce any bugs or typos.
Signed-off-by: Niklas Haas <git@haasn.dev>
Just define these directly as integer arrays; there's really no point in
having them re-use SwsSwizzleOp; the only place this was ever even remotely
relevant was in the no-op check, which any decent compiler should already
be capable of optimizing into a single 32-bit comparison.
Signed-off-by: Niklas Haas <git@haasn.dev>
We have to move the filters out of the way very early to avoid blocking
SWS_OP_LINEAR fusion, since filters tend to be nested in between all the
decode and encode linear ops.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
An operation list containing multiple filter passes, or containing nontrivial
operations before a filter pass, need to be split up into multiple execution
steps with temporary buffers in between; at least for CPU backends.
This helper function introduces the necessary subpass splitting logic
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
This allows reads to directly embed filter kernels. This is because, in
practice, a filter needs to be combined with a read anyways. To accomplish
this, we define filter ops as their semantic high-level operation types, and
then have the optimizer fuse them with the corresponding read/write ops
(where possible).
Ultimately, something like this will be needed anyways for subsampled formats,
and doing it here is just incredibly clean and beneficial compared to each
of the several alternative designs I explored.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
This commit merely adds the definitions. The implementations will follow.
It may seem a bit impractical to have these filter ops given that they
break the usual 1:1 association between operation inputs and outputs, but
the design path I chose will have these filter "pseudo-ops" end up migrating
towards the read/write for CPU implementations. (Which don't benefit from
any ability to hide the intermediate memory internally the way e.g. a fused
Vulkan compute shader might).
What we gain from this design, on the other hand, is considerably cleaner
high-level code, which doesn't need to concern itself with low-level
execution details at all, and can just freely insert these ops wherever
it needs to. The dispatch layer will take care of actually executing these
by implicitly splitting apart subpasses.
To handle out-of-range values and so on, the filters by necessity have to
also convert the pixel range. I have settled on using floating point types
as the canonical intermediate format - not only does this save us from having
to define e.g. I32 as a new intermediate format, but it also allows these
operations to chain naturally into SWS_OP_DITHER, which will basically
always be needed after a filter pass anyways.
The one exception here is for point sampling, which would rather preserve
the input type. I'll worry about this optimization at a later point in time.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
Makes various pieces of code that expect to get a SWS_OP_READ more robust,
and also allows us to generalize to introduce more input op types in the
future (in particular, I am looking ahead towards filter ops).
Signed-off-by: Niklas Haas <git@haasn.dev>
We often need to dither only a subset of the components. Previously this
was not possible, but we can just use the special value -1 for this.
The main motivating factor is actually the fact that "unnecessary" dither ops
would otherwise frequently prevent plane splitting, since e.g. a copied
alpha plane has to come along for the ride through the whole F32/dither
pipeline.
Additionally, it somewhat simplifies implementations.
Signed-off-by: Niklas Haas <git@haasn.dev>
This was just a minor/pointless optimization in the first place. We keep
the skip on the last component because we can never commute that past the
end of the list.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
When splitting planes, some planes can end up without a read operation
altogether, e.g. when just clearing the alpha plane.
Just return ENOTSUP for such lists instead of EINVAL.
Also fixes the !ops->num_ops check to avoid UB.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
The way this code was written relied on the implicit assumption that no other
row was reading from the same column, which was true in practice so far but
not necessarily true in general. Fix it by precomputing the nonzero component
mask and then adding an explicit check.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
This requires a bit of a manual check in the 32-bit integer case to
make sure we don't exceed the value range of AVRational; but it still allows
quite a number of optimizations despite that restriction.
e.g.
rgb24 -> yuva444p9be:
- [u16 ...X -> ++++] SWS_OP_CLEAR : {_ _ _ 511}
- [u16 .... -> zzzz] SWS_OP_SWAP_BYTES
- [u16 .... -> zzzz] SWS_OP_WRITE : 4 elem(s) planar >> 0
+ [u16 ...X -> zzzX] SWS_OP_SWAP_BYTES
+ [u16 ...X -> zzz+] SWS_OP_CLEAR : {_ _ _ 65281}
+ [u16 .... -> zzz+] SWS_OP_WRITE : 4 elem(s) planar >> 0
gray -> yuv444p12be:
- [u16 .XXX -> +++X] SWS_OP_CLEAR : {_ 2048 2048 _}
- [u16 ...X -> zzzX] SWS_OP_SWAP_BYTES
- [u16 ...X -> zzzX] SWS_OP_WRITE : 3 elem(s) planar >> 0
+ [u16 .XXX -> zzXX] SWS_OP_SWAP_BYTES
+ [u16 .XXX -> z++X] SWS_OP_CLEAR : {_ 8 8 _}
+ [u16 ...X -> z++X] SWS_OP_WRITE : 3 elem(s) planar >> 0
Ultimately, the benefit of this will only become relevant once we start
splitting apart planes, since then we can have planes with only CLEAR
operations.
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
This optimization is lossy, since it removes important information about the
number of planes to be copied. Subsumed by the more correct
Instead, move this code to the new ff_sws_op_list_is_noop().
Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
I think this is ultimately a better home, since the semantics of this are
not really tied to optimization itself; and because I want to make it an
explicitly suported part of the user-facing API (rather than just an
internal-use field).
The secondary motivating reason here is that I intend to use internal
helpers of `ops.c` inside the next commit. (Though this is a weak reason
on its own, and not sufficient to justify this move by itself.)
Instead of blindly interleaving re-ordering and minimizing optimizations,
separate this loop into several passes - the first pass will minimize the
operation list in-place as much as possible, and the second pass will apply any
desired re-orderings. (We also want to try pushing clear back before any other
re-orderings, as this can trigger more phase 1 optimizations)
This restructuring leads to significantly more predictable and stable behavior,
especially when introducing more operation types going forwards. Does not
actually affect the current results, but matters with some upcoming changes
I have planned.
This requires updating the order of the dither matrix offsets as well. In
particular, this can only be done cleanly if the dither matrix offsets are
compatible between duplicated channels; but this should be guaranteed after
the previous commits.
As a side note, this also fixes a bug where we pushed SWS_OP_SWIZZLE past
SWS_OP_DITHER even for very low bit depth output (e.g. rgb4), which led to
a big loss in accuracy due to loss of per-channel dither noise.
Improves loss of e.g. gray -> rgb444 from 0.00358659 to 0.00261414,
now beating legacy swscale in these cases as well.
Otherwise, this is invalid; as the result of applying the next operation
after channel duplication may not commute with the result of duplicating
the result of applying the next operation on only one channel.
In practice, this was using the last seen channel's coefficients.
Note that this did not actually affect anything in practice, because the only
relevant ops (MIN/MAX) were always generated with identical coefficients for
identical channel ranges.
However, it will matter moving forwards, as e.g. dither ops may not be
commuted freely if their matrix offsets differ per channel.