View Full Version : Internaly multi-threaded resampling functions
Pages :
1
2
3
4
5
6
7
[
8]
9
jpsdr
4th September 2025, 18:07
Thanks, i'll check this later, after i finished the MT version with my threadpool. Things are progressing, except i've discovered i have a strange issue when not in 4:4:4.
I'll have to figure out where i messed things up... :(
DTL
5th September 2025, 20:28
The important issue in current sources is many hard connections to jincresize naming. But it only one of many possible jinc-based resize kernels and dispatched by 1pass 2D resample engine.
If you look into libplacebo resize kernels sources - https://code.videolan.org/videolan/libplacebo/-/blob/v1.18.0/src/filters.c the kernels for 1pass 2D resampling engine looks like have property .polar=true and many have ewa_ prefix in name. To tag this resize engine as 1pass 2D resampling engine it may be renamed to something like 'polar resize library' and may include many resize kernels typically used with this resampling engine only (or more frequently).
In addition with JincResize only we can take all other 'polar' kernels from libplacebo -
haasnsoft
ewa_hann
ewa_robidouxsharp
ewa_robidoux
ewa_ginseng
ewa_lanczos
ewa_jinc
All these may be included as additional named resize filters in addition with JincResize. And also more important downsampling kernels equal to UserDefined2 for 2D resampling. At time of big program redesign it may be good to plan addition of many other resize kernels for this new to AVS resize engine.
jpsdr
6th September 2025, 14:33
Some benchmark:
JincResize(opt=1,threads=0) : 17,561s
JincResize(opt=2,threads=0) : 13,366s
JincResize(opt=3,threads=0) : 13,874s
JincResizeMT(opt=1,threads=0) : 11,952s
JincResizeMT(opt=2,threads=0) : 9,682s
JincResizeMT(opt=3,threads=0) : 10,861s
I didn't expect having better results, i thought it would me more close. But i won't complain :D.
According these results, i will remove the AVX512 automatic and allow it only when selected. No idea why the AVX512 result is worse than AVX2.
Didn't implement DTL's tweak yet, will do in second time after first release. I wanted first check that my MT was at least efficiant similar than actual original JincResize.
DTL
6th September 2025, 16:42
When test AVX2 and AVX512 it is good to check CPU clock rate. At old hosts the AVX512 cause clock rate trottling and this may cause lower performance too.
Also you can test not pure single filter performance but with some more complex script running your shared thread pool and it may make some performance benefit too.
jpsdr
6th September 2025, 17:28
With x265, the use/activation of AVX512 gives me a +30% speed increase.
Also, on my attempt of making ASM in resampler, my ASM AVX512 code was faster than my ASM AVX2 code (don't remember the %), but the AVX2 intrinsic was just a little faster, or similar to my AVX512 ASM... :(
This is why i didn't continue my ASM in resampler. My guess (but it's just a guess) is that the AVX512 code in JincResize has some flaws.
DTL
6th September 2025, 21:35
Also for performance comparison you need to use builds by single compiler. The resampling functions are simple enough and if you make only copy and the resampling program placement in memory is the same the performance of the computing part expected to be equal. Only difference may be in threads control.
SIMD versions from SSE128 to AVX512 may be simple width of the kernel to process in single FMA loop spin increasing. But performance may depend on the row length of filter to process. Short row length like tap=3 may be better to process with SSE and long like tap=16 may be better with AVX2/512. You may try to make tests with different tap settings like 3 to 8 at least.
If you use for performance comparison build 2.1.4 from github - it may be made by significantly different compiler and may have better both threads control and internal SIMD optimizations by compiler from same intrinsics text.
jpsdr
7th September 2025, 09:59
Test was made without setting the tap, so it was default value. I'll test with different settings next time.
Also, as i've made several builds, i'll test Visual studio with AVX512 vs LLVM with -march=znver4 also next time, see if there is difference.
DTL
7th September 2025, 22:18
It looks the coeff table preparation for usage with SIMD up to AVX512 cause additional performance penalty.
At https://github.com/jpsdr/JincResizeMT/blob/50188a51ee5bc292051ba3143829ec2cd3091ff7/Src/JincResizeMT.cpp#L380 it looks coeff_stride always mod16 to be able to load with step of 16 at AVX512 function (example https://github.com/jpsdr/JincResizeMT/blob/50188a51ee5bc292051ba3143829ec2cd3091ff7/Src/resize_plane_avx512.cpp#L85 ). End of coeffs rows in resampling program is padded with zeroes.
But the total coeff table size in RAM is proportional to filter_size*coeff_stride.
For default tap=3 the filter_size is about 7 I think and it can be used with coeff_stride=8 to be mod8 for AVX2 processing for about 2 less RAM usage and possibly about 2x times less coeff table reading time from RAM.
The idea is to use more dense coeff_stride packing in RAM if AVX512 is not used. So at the https://github.com/jpsdr/JincResizeMT/blob/50188a51ee5bc292051ba3143829ec2cd3091ff7/Src/JincResizeMT.cpp#L380 the coeff_stride must be created not always mod16 but depending on the used SIMD resize function (mod8 for AVX2 and mod4 for SSE128). This may cause some better performance at some combinations of tap/SIMD_size_used. And also lower RAM usage (sometime critical for x86 builds too). And user may adjust SIMD size used (via opt option) for best performance (and RAM usage) for current filter size used.
Other possible solution is to use more dense coeffs rows packing in RAM always (like mod4 always for lowest SIMD128) and make more advanced AVX256 and AVX512 functions to save from end of row overread.
Also possible performance/quality optimizations for RAM usage:
1. Make optional int16 coeffs storage/usage. 1D resizers uses int16 coeffs precision and it work not very bad. It will double RAM performance. And make RAM usage about 1/2.
Also fp16 format may be tested (with AVX512 unpacking and/or computing).
2. Make runtime compression/decompression of coeffs blocks (like for single sample or more) to save RAM usage also.
jpsdr
8th September 2025, 13:25
Also possible performance/quality optimizations for RAM usage:
1. Make optional int16 coeffs storage/usage. 1D resizers uses int16 coeffs precision and it work not very bad. It will double RAM performance. And make RAM usage about 1/2.
This one, i already thought of the idea, but not in the first time.
Maybe in second or third time.
As it needs a lot of intrinsic rewrite... :(
But i'll see right now the ajusting padding.
Also fp16 format may be tested (with AVX512 unpacking and/or computing).
2. Make runtime compression/decompression of coeffs blocks (like for single sample or more) to save RAM usage also.
Euh... ... ... ... ... :confused:
Not realy... I mean, i don't know if it's a good idea or not, but not for me.
DTL
8th September 2025, 17:53
fp16 support may be separate of AVX512 - Wiki says Support for conversions with half-precision floats in the x86 instruction set is specified in the F16C instruction set extension, first introduced in 2009 by AMD and fairly broadly adopted by AMD and Intel CPUs by 2012.
https://en.wikipedia.org/wiki/F16C
CPUs with F16C
AMD:
Jaguar-based processors
Puma-based processors
"Heavy Equipment" processors
Piledriver-based processors, Q4 2012[3]
Steamroller-based processors, Q1 2014
Excavator-based processors, Q2 2015
Zen-based processors, Q1 2017, and newer
Intel:
Ivy Bridge processors and newer
It looks all intel and AMD with AVX also have F16C instructions for converting to and from. For 128 and 256 bits words (also expected in AVX512 too) . So we need only add conversion function for resampling program to pack it into F16C format and add expansion instruction before applying read coeffs row from RAM. Small addition to intrinsics. The fp16 data is named as mXXXi (SIMD integer) datatype as I see in the intrinsics reference -
https://www.laruence.com/sse/#othertechs=FP16C
__m128i _mm_cvtps_ph (__m128 a, int sae)
Convert packed single-precision (32-bit) floating-point elements in a to packed half-precision (16-bit) floating-point elements, and store the results in dst.
Exceptions can be suppressed by passing _MM_FROUND_NO_EXC in the sae parameter.
AVS API has special CPUID flag to get CPU support for FP16C instructions -
https://github.com/jpsdr/JincResizeMT/blob/cf3b2d8ae2df8fbfa36f76a6a5a0fc265b9beb03/Src/avs/cpuid.h#L59
CPUF_F16C = 0x8000,
For AVX256 the resampling functions addition for FP16 option is very small. Instead of 8xfloat32 loads from RAM it will load 128bit SIMD word as m128i and before use as 8xfloats add __m256 _mm256_cvtph_ps (__m128i a) instruction.
For https://github.com/jpsdr/JincResizeMT/blob/cf3b2d8ae2df8fbfa36f76a6a5a0fc265b9beb03/Src/resize_plane_avx2.cpp#L46
const __m256 coeff = _mm256_load_ps(coeff_ptr + lx);
it is expected
const __m256 coeff = _mm256_cvtph_ps (_mm_load_si128((_m128i*)(coeff_ptr + lx))); // (i hope start addrs of the coeffs rows are 16byte aligned ?)
But before calling resampling function the resampling programm must be re-packed from float32 to float16 (with 2x data size reduction in RAM). C++ (before C++23 ?) do not support float16 directly so it expected separate function with intrinsics __m128i _mm_cvtps_ph (__m128 a, int sae) . For C/C++ packed 8x fp16 words are treated as 1 abstract _m128i dataword.
For AVX512 - __m256i _mm512_cvtps_ph (__m512 a, int sae) and __m512 _mm512_cvtph_ps (__m256i a) .
So with 3 steps implemented:
1. Coeffs rows padding reduction from 16xfloat32 to 8xfloat32 for AVX256 - about 2x reduction of row size in RAM for tap=3.
2. Reuse single reading of coeffs row for 3 (4) planes resize - 3..4 reduction of total coeffs reading data per frame.
3. 2x reduction of coeffs row size with compression from float32 to float16 - 2x reduction of total coeffs reading data per frame.
2*3*2=12x times reduction of total RAM read traffic for coeffs per frame expected to make about 10x time more performance for JincResize(tap=3).
jpsdr
9th September 2025, 12:38
Made some benchmark:
Original code, reading coeff each time:
YUV16 : 9.947s
YUV24 : 14.764s
RGB : 14.747s
RGBA : 19.647s
Modified code, reading coeff only once:
YUV16 : 8.000s
YUV24 : 7.481s
RGB : 7.630s
RGBA : 9.646s
Results are faster for each cases, even if for subsampled increase speed is small, it's still better. So this is what will be implemented in my JincResizeMT release.
DTL
9th September 2025, 17:48
For subsampled UV formats you can combine processing of UV planes of equal size and also get some performance boost.
Also Y and A planes of YUVA formats may be combined always. So processing will be 2 pass max - for Y+A and for U+V planes for subsampled chroma formats.
jpsdr
9th September 2025, 17:59
That's exactly what i've done, and these are the results of the performance boost for subsampled YUV.
Tested with 720x480 -> 3840x2160 with tap=8.
DTL
10th September 2025, 09:24
Good. Now 2 of planned 3 steps for RAM usage optimization are implemented. Now only 16bit coeffs format left. As int16 of fp16. Fp16 expected to be better for keeping dynamic range (may be more important for longer kernels with more size/taps). Also int16 format only tested with 1D kernel computing and it is much smaller in total coeffs count. 2D kernel is square of 1D kernel size and quantization errors may be more critical.
Also possible for at least some integer use cases - the symmethry kernel optimization. For integer upsampling ratios kernel is dual-symmethrical and can be reduced to about 1/4 of size and dual-mirrored at time of computing resampling. It may be treated as sort of compression too. But it may be not frequent use case. Also for integer upsampling ratios kernel coeffs are static and not need to be pre-computed for each output sample and no RAM throughput problem exist. Some imlementation of this method for fixed 2x (may be 4x too) resize ratios with not very big filter size see second branch of JincResize at Asd-g repository and for AVX2 processing functions - https://github.com/Asd-g/AviSynth-JincResize/blob/master-1/src/KernelRow_avx2.cpp
4x tap=4 example https://github.com/Asd-g/AviSynth-JincResize/blob/b7fbf5d680a2950dff65b907134e6719efd11916/src/KernelRow_avx2.cpp#L446
For downsampling may be too. So for non-integer resize ratios it is possible to use 2 stages resize method (same as with NNEDI ?):
1. Integer-ratio faster resize to closed integer sized size with 2D single pass engine.
2. Old 1D+1D resize to requied output size.
The quality may be close to full-blood single pass 2D resize if resize ratio is about 2 or more for absolute value. Though users may make AVS script function for this method and it not need to be implemented in compiled form. But special integer size resize resampling program (mostly with fixes for edge of frame cases) generation and resize functions need to be created and compiled.
Also what I not like in current JincResize implementation is intermediate integer-argumented LUT for kernel sampling. It may be shadow of the poor past when CPUs were very slow and analytical kernel computing was slow and filter startup time is long. Now CPUs may be much faster in computing bessel-func inside core and caches and with SIMD.
So it may be better either remove LUT completely or make old optional for compatibility (but plugin is new and no old scripts exist). And use max precision double float analytical kernel sampling as we have now in AVS+ kernel with f(x) kernel sampling - https://github.com/jpsdr/ResampleMT/blob/3012a56c2b4be64c15250716849846e547f9ca2d/ResampleMT/resample_functions.cpp#L1119
Now we have some not nice magic number of LUT size (defining and limiting the precision of kernel sampling) - https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L1286
And sampling kernel at resampling program via integer LUT instead of more precise analytical function as double f(double x) - https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L636 and https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L638
While for not-covered by internal bessel/jinc computing cases already offloaded to external C-library bessel _j1() computing - https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L365 This usage of different kernel base functions computing for different tap number may also cause results variation between internal and external library results for bessel _j1() computing. So it may be also make an option to use external kernel computing always (for any tap number, where tap is converted into radius via https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L172 structure). This also will make function https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L318 much more simple.
The good and enough header for any kernel function for resize (and most used 2D resizes with round kernel) is https://github.com/jpsdr/ResampleMT/blob/3012a56c2b4be64c15250716849846e547f9ca2d/ResampleMT/resample_functions.h#L151
and
https://github.com/jpsdr/ResampleMT/blob/3012a56c2b4be64c15250716849846e547f9ca2d/ResampleMT/resample_functions.h#L152
All round and 1D kernels may be used with this simple syntax for resampling program generator function. For non-round we may use
virtual double f(double x, double y) = 0;
jpsdr
10th September 2025, 12:01
One of my personnal requirement for all my plugins, is that the code can be build with Visual Studio 2010, made before C17 implementation, this is why there is and there will be, fallback functions if necessary.
This is for now where i'll stop and make a release very soon. Integers coeffs (like kernel resampler), latter...
DTL
10th September 2025, 14:28
Removing LUT stage do not break compatibility with old C compilers. You can leave internal bessel computing at old C language. It is performed with double precision.
https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L198
https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L236
https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L290
And https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L318 - all is with double precision.
Precision lost expected started at https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L403 where double precision function sampled to LUT_SIZE integer steps only.
Idea is simply to replace LUT request with truncated precision integer argument at https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L416 with real function calculation with double precision argument as performed at lut creation at https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L406
And do not do precision truncation to integer at https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L636 but send original full-precision float/double argument (dx * dx + dy * dy) / radius2) to kernel function calculation (https://github.com/jpsdr/JincResizeMT/blob/a1c2616cb80dd7f12a3e6fffc21fbef213a43cf3/Src/JincResizeMT.cpp#L406) . And remove LUT-size scaling (samples - 1) .
Removing LUT object will make progam more simple and more precise and only can somehow make startup time longer. But for processing long footages it is not significant.
jpsdr
10th September 2025, 18:50
Removing LUT stage do not break compatibility with old C compilers. You can leave internal bessel computing at old C language. It is performed with double precision.
I was thinking only of bessel.
I'll try to look at the lut stuff on a second time, or... if you want to make a PR (and test it in the mean time... :D) but only with just the change in the computation, don't touch all the lut stuff, leave the cleaning/removing part to me.
I don't mind if for a while there is an unused lut, i'm still in finalizing.
The only issue i have with this, it's that i won't have a reference anymore. I won't be able to use original JincResize to compare output being strictly identical to be sure i don't break things...
DTL
10th September 2025, 21:38
I am on vacation until about September 18th. Only about that date or later can try to do version without LUT usage.
jpsdr
14th September 2025, 12:00
Add JincResizeMT, see first post.
DTL
15th September 2025, 06:41
Started to do planned things. Implemented switching 3 different weighting types and direct kernel sampling mode (no kernel LUT). Up to this commit https://github.com/DTL2020/JincResizeMT/commit/63f8f81a5e3aef14e8c037cd445629eae46ab39e
Now it can work as 3 different jinc-based filters -
wt param of 0,1,2 (may be string named instead of numbers ?)
typedef enum _WEIGHTING_TYPE
{
SP_WT_NONE = 0, // no weighting, pure (jinc) kernel, like SincResize for 1D
SP_WT_JINC = 1, // Jinc first lobe to first zero weighting, initial for JincResize AVS plugin (aka EWA_Lanczos), like LanczosResize for 1D (weighting by first lobe of the base kernel function)
SP_WT_TRD2 = 2, // Trapecoidal weigthing with linear fade to zero at the second half of filter size, like in SincLin2Resize, expected a bit sharper of 1 and still not having edge issues of 0
} WEIGHTING_TYPE;
Where SP prefix and postfix in naming expected to mark Single Pass resize stuff (names).
Test script to check kernel footprints:
LoadPlugin("JincResizeMT.dll")
BlankClip(20000,1,1, pixel_type="YV24", color_yuv=$D07f7f)
AddBorders(21,21,21,21, color_yuv=$307f7f)
wt0=JincResizeMT(width*10, height*10, tap=8, wt=0).Subtitle("jinc single-2D wt=0")
wt1=JincResizeMT(width*10, height*10, tap=8, wt=1).Subtitle("jinc single-2D wt=1")
wt2=JincResizeMT(width*10, height*10, tap=8, wt=2).Subtitle("jinc single-2D wt=2")
wt0_dk=JincResizeMT(width*10, height*10, tap=8, wt=0, lutkernel=false).Subtitle("jinc single-2D wt=0 lk=f")
wt1_dk=JincResizeMT(width*10, height*10, tap=8, wt=1, lutkernel=false).Subtitle("jinc single-2D wt=1 lk=f")
wt2_dk=JincResizeMT(width*10, height*10, tap=8, wt=2, lutkernel=false).Subtitle("jinc single-2D wt=2 lk=f")
r1=StackHorizontal(wt0, wt1, wt2)
r2=StackHorizontal(wt0_dk, wt1_dk, wt2_dk)
StackVertical(r1, r2)
But the most important addition in next days. New filter for downsample UserDefined4ResizeMT_SP(k01, k02, k11, k21, s, others...). Where
k01, k02, k11, k21 - 4 user-defined 2D kernel coeffs (of 21 total setting up by symmethry internally)
s - support (as in 1D resize, adbout equal to 'radius' in JincResize)
Where k01 and k02 are close to b,c for UserDefined2Resize for H and V directed parts of kernel and k11 and k21 (slightly different) are for better control of angled/diagonal parts of 2D kernel.
The call to kernel function from resampling program generator changed to 2D-style with (dx,dy) https://github.com/DTL2020/JincResizeMT/blob/63f8f81a5e3aef14e8c037cd445629eae46ab39e/Src/JincResizeMT.cpp#L687 to use new kernel function of this filter.
DTL
15th September 2025, 22:59
Finally added 2D version of UserDefined resize with 2D kernel based on jinc base function. Tests shows it also working about good for typical sinc-based dual-1D upsampling resize at displays.
Test release - https://github.com/DTL2020/JincResizeMT/releases/tag/post1.1.0_t01
Test script with 2D kernel params adjusted to look close to UserDefined2Resize(b=80, c=-20):
LoadPlugin("JincResizeMT.dll")
BlankClip(100,200,200,pixel_type="YV24", color_yuv=$207f7f)
Subtitle("TEXT TEST O ",text_color=color_whitesmoke,halo_color=color_whitesmoke, size=10, align=5)
ud4=UserDefined4ResizeSPMT(width/2, height/2, k10=100, k20=0, k11=60, k21=-10, s=5).Subtitle("UD4SP")
#ud4=UserDefined4ResizeSPMT(width/2, height/2, k10=85, k20=-10, k11=40, k21=-20, s=5).Subtitle("UD4SP")
#ud4=UserDefined4ResizeSPMT(width/2, height/2, k10=105, k20=20, k11=60, k21=0, s=5).Subtitle("UD4SP")
ud2=UserDefined2Resize(width/2, height/2, b=80, c=-20).Subtitle("UD2")
lz4=LanczosResize(width/2, height/2, taps=4).Subtitle("LZ4")
ud4=SincLin2Resize(ud4, ud4.width*8, ud4.height*8)
ud2=SincLin2Resize(ud2, ud2.width*8, ud2.height*8)
lz4=SincLin2Resize(lz4, lz4.width*8, lz4.height*8)
Interleave(ud4, ud2, lz4)
First test shows the b,c params table from sinc-based UserDefined2Resize are not any good applicable and new 4-members table need to be created. Current script to view kernel footprint for params tuning for low ringing in 2D and for kernel look round enough in 2D space:
LoadPlugin("JincResizeMT.dll")
BlankClip(20000,1,1, pixel_type="YV24", color_yuv=$D07f7f)
AddBorders(7,7,7,7, color_yuv=$307f7f)
UserDefined4ResizeSPMT(width*20, height*20, k10=100, k20=0, k11=60, k21=-10, s=5).Subtitle("UD4SP")
Kernel members mapping in 2D space:
/* 2D kernel of sum of jincs of max size 5x5 with trimmed out corner samples (XX), so 21 jincs in sum total
kernel samples placement in 2D full numbering (x,y)
where k(+0,+0) = 1.0 - center sample of kernel
XX k(-1,+2) k(+0,+2) k(+1,+2) XX
k(-2,+1) k(-1,+1) k(+0,+1) k(+1,+1) k(+2,+1)
k(-2,+0) k(-1,+0) k(+0,+0) k(+1,+0) k(+2,+0)
k(-2,-1) k(-1,-1) k(+0,-1) k(+1,-1) k(+2,-1)
XX k(-1,-2) k(+0,-2) k(+1,-2) XX
copy of k10, k20, k11, k21 by symmethry:
XX k21 k20 k21 XX
k21 k11 k10 k11 k21
k20 k10 1.0 k10 k20
k21 k11 k10 k11 k21
XX k21 k20 k21 XX
*/
jpsdr
16th September 2025, 09:02
For not breaking (in a way) compatibility with original, it's better to put added parameters after the originals, so doing this :
env->AddFunction("JincResizeMT", "c[target_width]i[target_height]i[src_left]f[src_top]f[src_width]f[src_height]f[quant_x]i[quant_y]i[tap]i[blur]f" \
"[cplace]s[threads]i[opt]i[initial_capacity]i[initial_factor]f[wt]i[lutkernel]b" \
"[range]i[logicalCores]b[MaxPhysCore]b[SetAffinity]b[sleep]b[prefetch]i[ThreadLevel]i", Create_JincResize, 0);
Edit:
Same with UserDefined4ResizeSPMT, but adding in that case the parameters before the specific MT parameters, these are always at the end, so like this:
env->AddFunction("UserDefined4ResizeSPMT", "c[target_width]i[target_height]i[src_left]f[src_top]f[src_width]f[src_height]f[quant_x]i[quant_y]i" \
"[cplace]s[threads]i[wt]i[lutkernel]b[k10]f[k20]f[k11]f[k21]f[s]f" \
"[range]i[logicalCores]b[MaxPhysCore]b[SetAffinity]b[sleep]b[prefetch]i[ThreadLevel]i", Create_JincResizeTaps<100>, 0); // 100 - temporal hack for different kernel_type signalling
This is how i'll implement your changes in my repo.
jpsdr
16th September 2025, 12:06
Something begins to bother me... I don't mind having UserDefined4ResizeSPMT, but is it technicaly still an only Jinc resize ?
What i don't want is having an ApplePie.dll producing lemon pie... :D
You can use different kinds of Apples, you can put a little honey on it, but it's still apple pies.
So, are we still doing only apple pies, or are we begin to produce lemon and others kinds of pie ?
If it's the last one, i'll stop JincResizeMT and create a totaly new Resample2DMT project and repo, with JincResize being only one of the functions.
DTL
17th September 2025, 03:20
It is also
1. Jinc as base kernel
2. Single pass 2D resize engine (designed from resampling program generator and resampling processing engines for different CPU architectures).
Different wt-params really forms 3 named resizers as we have with sinc-based dual-1D (in AVS core and ResampleMT) -
SincResize ~ JincResize(wt=0)
LanczosReisze ~ JincResize(wt=1)
SincLin2Resize ~ JincResize(wt=2)
UserDefined2Resize ~ UserDefined4ResizeSP (may be named also JincResize(kernel_members, kernel_type=JINCSUM))
It is only different ways of naming kernel and resampling engine setup. At different resampling projects as I see users already go from single named resizer to something like setting structure of required resize params for resampling program parser:
{
base function = <sinc, jinc, spline, gauss,...> + params;
weighting = <none, lanczos-like, trapecoidal,...> + params;
processing_engine = <dual-1D, single pass 2D>
}
For JincResize project if we know it is jinc base function and single pass 2D resampling engine we only need some complementary set of resize functions for upsampling and downsampling. That is now JincResize for upsampling (interpolation, decompression) and UserDefined4ResizeSP for downsampling (initial content production, compression). In theory they may be renamed to JincUpsize() and JincDownsize().
But if we want to give users ability to test different kernels with single pass 2D resampling engine it may be created most of 1D resize kernels as somehow named resizers for single-pass 2D resampling engine like
BilinearResizeSP(2D?)
BicubicResizeSP(2D?)
GaussResizeSP(2D?) - really important as widely used blur-engine
SplineXResizeSP(2D?)
About arguments placing - adding to the end was the fastest way without complex counting and changing numbers in many other arguments reading places. I make a copy of JincResize256 as having lower arguments count (but wt and lutkernel are not used in UserDefined4Resize) . For future I think to add (make working 'blur' (or something like this - may be 'scale' ?) argument for UserDefined4Resize to make some fine-tuning (additional global scale) of base jinc function. Currently it is only fixed M_PI number scaled at
double jinc_pi(double arg)
{
const auto x = M_PI * arg;
#ifdef C17_ENABLE
return std::cyl_bessel_j(1, x) / x;
#else
return bessel_j1(x) / x;
#endif
}
But jinc function in 2D space is not equal to sinc in 1D space. Sinc is band-limited and orthogonal and periodical in 1D space and it looks no additional scale required for tuning. Jinc in 2D space is only band-limited but non-periodic and not-orthogonal so M_PI scaling is only first approximation to make it working with integer scaled 2D resamlpling grid (so its frequency cut-off is close to required Nyquist limit at least for H and V directions). But may be some additional fine tuning may be useful (in range like 0.25..2) so I think to pass 'blur' param as 'scale' in this function like
double jinc_pi(double arg, float s) // s = 'blur' argument, 1.0 default
{
const auto x = M_PI * arg * s;
#ifdef C17_ENABLE
return std::cyl_bessel_j(1, x) / x;
#else
return bessel_j1(x) / x;
#endif
}
Also I see the second required patch - this function is not safe for zero-arg (divide by zero and undefined result + some math exception). So it may be good to redesign to sinc-like limiting
https://github.com/jpsdr/ResampleMT/blob/0a2db0dd44f8f2ae8da262e69f477aecf58fbac5/ResampleMT/resample_functions.cpp#L100
if (value > 0.000001)
{
const auto x = M_PI * arg * s;
#ifdef C17_ENABLE
return std::cyl_bessel_j(1, x) / x;
#else
return bessel_j1(x) / x;
#endif
}
else
{
return 1.0;
}
I hope 0.000001 threshold is enough for both float32 and double float possible arguments and not too much affect precision near zero.
Yes - the Resample2DMT project may be better to hold many other possible single-pass 2D resize functions/filters. But it is more complex redesign. It may be useful for future addition of this second resize engine to AVS core.
jpsdr
17th September 2025, 09:01
Maybe near 0 instead of returning 1, we can return the... as i'm writing these line i don't have access to inline translation so i don't know how to proper translate from french" "développement limité", but replacing f(x) by f(x0+h) = f(x0) + coeff1*h + coeff2*h² .. +o(h^n).
So if we can get the approximate function of bessel_j1(0) near 0 order 3 wich is probably like x+a*x²+b*x^3, and replace the f(x)/x by 1+a*x+b*x² (so getting proper value with double precision of a and b) would be the best option, i think.
I'll try to see if i can find formula.
From what you said (if i understand properly), everything is still Jinc based, so naming the dll JincResize is fine. We are still doing apple pies but just with different kinds of apples... :D
Edit:
I'm stupid, i have the formula in the code...
Edit2:
Near 0, we can approximate (unless i've made a mistake) J1(x) = x/2 -(x^3)/16 +(x^5)/384
So, J1(x)/x = 0.5 -x²/16 +(x^4)/384.
DTL
17th September 2025, 10:58
Bessel J1(x) starts from 0 near x=0 https://en.wikipedia.org/wiki/Bessel_function
https://i.ibb.co/DHKvsSCv/bessel-j1.jpg (https://imgbb.com/) (image link https://ibb.co/hJ84htW4 )
J1(x) at the interval 0..3 is very close to sin(x) and also starts from 0.
So we have close to sin(x)/x (not clearly defined) limit of 0/0 and to make kernel shape smooth near 0 we need return 1 for jinc(0). But the best return values for very small argument deviation around 0 may be subject to research. Also it may depends on the precision of computing in the used bessel j1 function.
Also from 1D resize - it also based on some bessel-kind J0(x) function that is sin(x)/x - The zeroth spherical Bessel function j0(x) is also known as the (unnormalized) sinc function.
" everything is still Jinc based,"
Yes - UserDefined4ResizeSP also jinc-based. And may be form of JincResize with multi-jincs sum as kernel function. Where old/typical JincResize is single jinc somehow weighted (and size-scaled by blur-agrument). I think old designers and users of single-jinc kernel tried to use blur-spatial scaling to somehow fix ringing. But it not best method because frequency cut-off of single jinc function still too hard and to fix ringing we need more slow cut-off. This is designed in multi-jinc sum of spatially shifted jincs.
The next performance updates planned:
1. FP16 resampling program of full size
2. Attempt to do optional LUT-based resampling program of smaller size in RAM. So it will only have start offset and pointer to kernel patch in LUT to use for current output sample. And new conversion function need to be created to convert full sized resampling program to lower sized by skipping too close looking kernel patches and replacing to pointers in the LUT. It is sort of simple compression.
For some use cases like integer resize ratios it is expected to compress GBs sized resampling program to L1D cache sized.
To control this a new arguments for JincResize planned - the type of resampling program (full-precision/full-size or limited precision LUT-based) and the max deviation of kernel patch in resample program to be skipped and replaced by close copy in the LUT. Deviation may be computed as any dissimilarity metric like SAD (from mvtools and other plugins).
jpsdr
17th September 2025, 11:55
we need return 1 for jinc(0)
Except i'ts more 0.5 according the formula.
You put:
float k10 = (float)args[13].AsFloat(16.0f); // set to zero 8bit as default
...
// convert to 0..1 range
k10 = (k10-16.0f)/219.0f;
and the others.
It's tuned for Y range only.
Does it mean it needs to be tuned acording different ranges ?
16-240, 16-255, 0-255, more than 8 bits ?
Shouldn't the paremeter in that case directly input to 0..1 ?
Or is it still in design mode and not in the final stage, so it will evolve ?
DTL
17th September 2025, 15:43
"i'ts more 0.5 according the formula."
It mean approximate formula is not completely correct. At least for special case f(0).
"Does it mean it needs to be tuned acording different ranges ?
16-240, 16-255, 0-255, more than 8 bits ?"
I think it is not needed because kernel is auto-scaled to 1 total *energy* to keep levels unchanged at the resampling program generator.
https://github.com/jpsdr/JincResizeMT/blob/23bc065a478c92ce84942debc3328f1aaf3e67bb/Src/JincResizeMT.cpp#L776
and
https://github.com/jpsdr/JincResizeMT/blob/23bc065a478c92ce84942debc3328f1aaf3e67bb/Src/JincResizeMT.cpp#L793
And this is applied to any kernel function used.
"Shouldn't the paremeter in that case directly input to 0..1 ?"
The kernel coeffs values for UserDefinedNResize* are entered in limited 8bit range (though with float precision if user need it) for 2 reasons:
1. In memory of the great old civilization with 8bit digital video and limited range encoding with 0 mapped to code value 16 and 1 mapped to code value 235.
2. It is real data sampling (in 1D or 2D space) in 8bit integer domain and can be entered in the kernel image simulation software (AVS too) for weighted sum with base kernel function to get resulted kernel image for processing. Software simulator in java/web-script also work in this range encoding domain.
Though some real used kernel weighting values like -10 are not valid in 8bit integer 0..255 domain but I still keep this way.
If you like you can change to 0..1 float range (with re-calculation of already shown some working values sets to this range). But entering small values starting from 0. may be more complex for user.
UserDefined2Resize(MT) uses same kernel members input way and everything is working without visible issues for many years - https://github.com/jpsdr/ResampleMT/blob/0a2db0dd44f8f2ae8da262e69f477aecf58fbac5/ResampleMT/resample_functions.cpp#L349
Also it makes easier for user to understand that kernel members between UserDefined2Resize(MT) for H+V resize mode and UserDefined4ReizeSP(MT) are not compatible even in H and V directions in 2D space.
tormento
17th September 2025, 17:41
I am losing myself into your math disquisition.
Would you please explain me what's all this fuss about Jinc kernel and some real world examples? ;)
jpsdr
17th September 2025, 18:18
From what i've found, the "Taylor expansion around 0 up to order 5" or "Maclaurin expansion of J1(x) up to order 5" (i was provided the 2 translations) is : J1(x)=x/2 -(x^3)/16 + (x^5)/384 +o(x^5). => J1(x)/x = 0.5 - x²/16 +(x^4)/384 + o(x^4) => Lim J1(x)/x, x->0.0 = 0.5.
DTL
17th September 2025, 20:12
Well - what we see from https://mathworld.wolfram.com/JincFunction.html - the jinc(0) is really peaks to 0.5. So it may be reason for 2.0 multiplier in original JincResize plugin.
https://i.ibb.co/x8RXVCR5/2025-09-17-222216.png (https://ibb.co/5xfGqMfc)
But usage of simple jinc(x) is not break the UserDefined4ResizeSPMT because resulted kernel always normalized in the resampling program generator. It is only good to remember the property of jinc(x) and why it somewhere normalized to 1.0 with 2.0 multiplier.
We can test how your appoximation around zero is working by some test MPEG encodings or other quality metrics in comparison with simple return 0.5 if arg < EPS.
jpsdr
17th September 2025, 23:29
Result is more accurate for sure, but having a visible effect is less sure. At this point, and first factor being x² for the EPS value, i'm not even sure the effect will be visible compared to a simple direct "brutal" threshold. And the less the bitdepth, the less chance being visible.
DTL
18th September 2025, 07:23
Added second pull request with removed unused params from UserDefined4ResizeSPMT and default params set to some working values close to the medium sharp UserDefined2Resize(b=80, c=-20).
jpsdr
18th September 2025, 08:48
For putting in the readme, if i understand properly, UserDefined4ResizeSPMT is more tuned for downsampling, and JincResizeMT is more tuned for upsampling, that's it ?
Does UserDefined4ResizeSPMT needs clamping like UserDefined2Resize on the parameters ?
DTL
18th September 2025, 11:20
"UserDefined4ResizeSPMT is more tuned for downsampling, and JincResizeMT is more tuned for upsampling, that's it ?"
Yes.
"Does UserDefined4ResizeSPMT needs clamping like UserDefined2Resize on the parameters ?"
I think it is not significant. If user provide unbalanced set of kernel members it will very badly distort filter responce and clamping can not save from this. It is expected user understand how filter is controlled by params and additional internal clamping can not help.
jpsdr
18th September 2025, 12:17
What should i put in the readme to describe k10,k20,... of UserDefined4ResizeSPMT ?
DTL
18th September 2025, 16:51
It is weighting coefficients of the 5x5 2D kernel based on jinc function with skipped corners (marked XX). Coefficients placement in 2D space:
XX k21 k20 k21 XX
k21 k11 k10 k11 k21
k20 k10 1.0 k10 k20
k21 k11 k10 k11 k21
XX k21 k20 k21 XX
I started to do fp16 mode and finally understand how resampling program working - it is not linear array as in ResampleMT and AVS core for 1D resize but a mix of
factor_map fixed size structure like a (small) LUT and addition of a variable size for border cases and not fitted in 'quantization' samples. So it is not very easy to convert it into fp16 format via pair of loops for x and y. But first attempt made without usage of coeff_meta part.
Also it looks the precision/quality may significanty depend on 'quantization' params and it is subject of more research if we can either skip this LUT too if full precision is required or use for best possible performance.
// Quantize xpos and ypos
const int quantized_x_int = static_cast<int>(xpos * quantize_x);
const int quantized_y_int = static_cast<int>(ypos * quantize_y);
const int quantized_x_value = quantized_x_int % quantize_x;
const int quantized_y_value = quantized_y_int % quantize_y;
const float quantized_xpos = static_cast<float>(quantized_x_int) / quantize_x;
const float quantized_ypos = static_cast<float>(quantized_y_int) / quantize_y;
if (!is_border && out->factor_map[quantized_y_value * quantize_x + quantized_x_value] != 0)
{
// Not border pixel and already have coefficient calculated at this quantized position
meta->coeff_meta = out->factor_map[quantized_y_value * quantize_x + quantized_x_value] - 1;
}
jpsdr
18th September 2025, 17:56
And s...? What description ?
jpsdr
18th September 2025, 18:31
@tormento
About the kernel, it's more DTL stuff, but this post (https://forum.doom9.org/showthread.php?p=2022230#post2022230) illustrate perfectly (at least for me) the difference between a 2 pass 1D kernel and a 2D kernel.
Otherwise, the math stuff for Jinc(0) is juste something very small and specific, don't bother with it.
DTL
18th September 2025, 18:34
s is simply support of filter. In integer samples count. Total filter size is 2_x_support (and squared in 2D, but limited by radius to round form currently). It is close to tap in JincResize in size on image. User may use kernel footprint display script to see how support change the actually used part of computed kernel. I hope that simple script need to be added to Resize AVS filters documentation because it is applicable to all linear resize filters in AVS.
BlankClip(20000,1,1, pixel_type="YV24", color_yuv=$D07f7f)
AddBorders(7,7,7,7, color_yuv=$307f7f)
UserDefined4ResizeSPMT(width*20, height*20, k10=100, k20=0, k11=60, k21=-10, s=3) // or any other resize filter to check
For UserDefined4ResizeSPMT support of 3 recommended (and default value). For better precision or special use cases can be expanded to higher values. But performance of computing of 2D convolution is reverse proportional to squared support. So it is generally useful to make small sized kernel (quiclky faded to zero) by setting balanced set of coefficients and use small support value to cover only significant part of kernel.
For better performance support may be reduced to about 2 (in float precision). But it generally depends on the size of 'active non-zero' part of kernel. If support truncates significant part of kernel it may result in more ringing (at the upsampling/displaying). All same as with support param for UserDefined2Resize.
Addition: Better description for k-parameters:
k10, k20, k11, k21 - weighting coefficients. Float values. Range mapping 16..235 to 0.0..1.0 (as in limited 8bit) internally (same as in UserDefined2Resize). Valid range - unlimited. But remember the center member k(0,0) is 1.0 fixed internally (equal to 235 user input). Default values - 100,0,60,-10. Adjusted to make close result to medium sharp of UserDefined2Resize(b=80, c=-20). If set to (16,16,16,16) - the kernel is equal to JincResize(wt=0) and s-param defines the taps (kernel size) used.
Typical task for kernel coefficients adjustment - get round shape smallest sized dot surrounded with undershoot (if video look/makeup required) or smooth falloff (if film look/makeup required) with minimum ringing. At the kernel setup it may be recommended to set s (support) value to big enough like 5 or 10 to check possible ringing at far distance. After kernel tuned for required shape - s param may be reduced to minimal enough to get best performance without loss of quality.
Example of kernels footprints of UserDefined2Resize (typical medium sharp setting) and current default UserDefined4ResizeSPMT (20x upsampled):
https://i.ibb.co/4wwFjFcF/res-ud2-ud4-01.png (https://imgbb.com/) image link https://i.ibb.co/4wwFjFcF/res-ud2-ud4-01.png
Current params for UserDefined4ResizeSPMT looks more blurry - in the future may be found more sharp version with still good controlled ringing.
jpsdr
19th September 2025, 08:40
The doc said that UserDefined4ResizeSPMT is more tuned for downscaling. I will not put a contradictory example using it for upscaling...:confused:
tormento
19th September 2025, 09:09
Otherwise, the math stuff for Jinc(0) is juste something very small and specific, don't bother with it.
I meant the opposite [emoji28]
I just want the know the effect of using jinc on real stuff instead of other type of kernels. Is it better? When? How?
DTL
19th September 2025, 09:35
It is not production resize mode but kernel check and tuning-setup mode. About any resampler can be used for upsampling too in special cases.
What we need is 2D waveform plot software to check 2D kernels with any angled crossections. For 1D kernels simple H-line waveform monitor is enough. But for 2D we need to check all possible angles between 0 and 90 degrees. At least most wanted is 45 and 45/2 ~ about 22 degrees. I hope it can be somehow solved with Crop and Rotate plugin for AVS. For images I remember we have some like PixelPlot (?) software but it is slow to copy output from AVS and transfer via file to other software to create waveform at required path via 2D image. Better to have some realtime AVS solution (may be modification of Histogram filter ?).
Image link https://i.postimg.cc/GhWsg356/image-2025-09-19-114544712.png
"Is it better? When? How?"
What kernel plots and some few examples of internal sources (text) scaling shows - it better process non-H/V (angled) directioned levels transients. Most difference at +-45 degrees to H/V. Expected to produce more 'natural/film-look' results. But real difference may be visible only at fine details level.
2D resize methods can make draw (compress+decompress) all angles symmetrical round shape in lower size (relative to 2D rectangular sampling grid) in comparison with dual-1D resize methods. When we decrease size of initial round dot with dual-1D resizes it start to lost symmetry and turn into + sign shape and it is shape distortion.
Some natural example is star field. With dual-1D resizers small stars with some sharpening undershoots are "+"-shaped with 4 dark/black undershoots around center white dot. With 2D single pass resize stars may be close to round white dot surrounded with dark circle.
Using Rotate plugin made kernel coeffs adjustment script for different use cases with waveform for 0, 22 and 45 degrees cross-section of the kernel:
LoadPlugin("Rotate_x64.dll")
LoadPlugin("JincResizeMT.dll")
BlankClip(20000,1,1, pixel_type="YV24", color_yuv=$F07f7f)
Border=5
AddBorders(Border,Border,Border,Border, color_yuv=$307f7f)
ConvertBits(32)
ColorYUV(gain_y=250,off_y=-50)
rot0=UserDefined4resizeSPMT(width*20, height*20, k10=120, k20=20, k11=60, k21=17, s=5) # k10=120 - flat/film look/makeup
#rot0=UserDefined4resizeSPMT(width*20, height*20, k10=110, k20=5, k11=50, k21=0, s=5) # k10=110
#rot0=UserDefined4resizeSPMT(width*20, height*20, k10=100, k20=0, k11=40, k21=0, s=5) # k10=100
#rot0=UserDefined4resizeSPMT(width*20, height*20, k10=90, k20=-10, k11=20, k21=-5, s=5) # k10=90
#rot0=UserDefined4resizeSPMT(width*20, height*20, k10=80, k20=-10, k11=20, k21=-10, s=5) # k10=80 - sharp/video look/makeup
#rot0=UserDefined4resizeSPMT(width*20, height*20, k10=16, k20=16, k11=16, k21=16, s=5) # test pure jinc
rot0=ConvertBits(rot0, 8)
rot45=Rotate(rot0, 45)
rot22=Rotate(rot0, 22)
wf0=Histogram(Crop(rot0,(Border*20)+15,0, rot0.width-(Border*20*2)-15, rot0.height), mode="Classic")
wf0=Levels(wf0,0,1,30,0,255)
wf0=TurnLeft(wf0)
wf0=BilinearResize(wf0,wf0.width, wf0.height*2).Subtitle("wf0", text_color=$FFFFFF)
wf22=Histogram(Crop(rot22,(Border*20)+15,0, rot22.width-(Border*20*2)-15, rot22.height), mode="Classic")
wf22=Levels(wf22,0,1,30,0,255)
wf22=TurnLeft(wf22)
wf22=BilinearResize(wf22,wf22.width, wf22.height*2).Subtitle("wf22", text_color=$FFFFFF)
wf45=Histogram(Crop(rot45,(Border*20)+15,0, rot45.width-(Border*20*2)-15, rot45.height), mode="Classic")
wf45=Levels(wf45,0,1,30,0,255)
wf45=TurnLeft(wf45)
wf45=BilinearResize(wf45,wf45.width, wf45.height*2).Subtitle("wf45", text_color=$FFFFFF)
fr=StackHorizontal(wf0, wf22, wf45)
sr=StackHorizontal(rot0, rot22, rot45)
StackVertical(fr, sr)
Converttorgb24()
Current recommended k-values sets for different sharpness levels from flat/film to 'sharp video' for UserDefined4ResizeSPMT() (sharpness increase from high to low k10 argument value)
k10=120, k20=20, k11=60, k21=17) # - flat/film look/makeup
k10=110, k20=5, k11=50, k21=0)
k10=100, k20=0, k11=40, k21=0)
k10=90, k20=-10, k11=20, k21=-5)
k10=80, k20=-10, k11=20, k21=-10) # - sharp/video look/makeup
It is sort of function of k10 argument in range about 80..120. Current stepping is not very fine and in future sets for k10 values like 85, 95, 105 need to be added for finer adjustment.
As first approximation intermediate coeffs sets may be some linear interpolation between known:
Like for k10=95:
k10=95, k20=-5, k11=30, k21=-2)
tormento
20th September 2025, 12:22
Current recommended k-values sets for different sharpness levels from flat/film to 'sharp video'
Thank you.
Can you generate a table such as the one we have for standard UserDefined?
DTL
20th September 2025, 12:45
Values of mod10 are hand adjusted at simulator and mod5 are linearly interpolated. As I see from mod10 values typical function of each kernel member with decreasing first (k10) member is enough monotonic and can be good enough interpolated between few known points. In some next versions we can attempt to include internal interpolation and make single-param adjustment like k10 only and some new param like autok=true or some 'magic' control value for other control param like k20=1000.
https://i.postimg.cc/XpXMVzM1/image-2025-09-20-144035776.png (https://postimg.cc/XpXMVzM1)
https://i.postimg.cc/DyP7P9xh/image-2025-09-20-144035776.png
About quant_x, quant_y params: I see any lowering to range 1..64 cause more or less significant distortions of a kernel. It looks it may be good to relax upper limit to some higher values like 512..1024..2048 or may be higher to let user have higher precision (with HBD too). It may make RAM usage a bit higher bit I hope not significantly.
DTL
30th September 2025, 09:14
New test release of FP16 resampling program data format.
https://github.com/DTL2020/JincResizeMT/releases/tag/post1.1.0_t02
Also for AVX2 it uses dual-rows processing in resampling engine and it looks a bit faster in many processing modes (with FP16=false too) in comparison with first release. 32bit build also added for users of 32bit environments with very low sizes of contigous virtual addresses space and out of memory issues with too big output frame size or kernel size (limit of total resampling program size in 32bit process address space) - https://forum.doom9.org/showthread.php?t=186053 . Usage of FP16 format expect to relax limitation of frame size and kernel size a bit more.
Test script:
BlankClip(20000,2,2, pixel_type="Y8", color_yuv=$D0Af5f)
AddBorders(70,70,70,70, color_yuv=$307f7f)
JincResizeMT(width*20,height*20, tap=7, FP16=false)
At i5-9600K 6core CPU:
FP16=false
FPS (min | max | average): 13.67 | 14.68 | 14.30
Process memory usage (max): 1498 MiB
Thread count: 16
CPU usage (average): 68.5%
FP16=true
FPS (min | max | average): 15.06 | 21.25 | 20.70
Process memory usage (max): 805 MiB
Thread count: 16
CPU usage (average): 81.4%
For 8bit samples precision with FP16 resampling program data format - processing errors are very rare (<1% ?) +-1 LSB errors.
About possible Gauss kernel for single pass 2D resize engine - I read in the imagemagic resize description it is a special math kernel and produces equal results with dual-pass (orthogonal) resampling engine: https://usage.imagemagick.org/filter/#gaussian
The one filter which produces no difference in results between an orthogonal 'resize' and a cylindrical 'distort' forms, is the special 'Gaussian' filter...
This is actually one of the special proprieties of this filter (known as separability), and one of the reasons why many cylindrical resampling implementations use it as the default filter.
So it is not really useful with such resize engine and will be only slower in processing.
jpsdr
2nd October 2025, 08:55
@DTL
Hello.
What's the status of your FP16 ? Is it still in test or can it be implemented ?
Ah... No it's not...! FP16 is not implemented in AVX512, so if AVX512 is enabled, it will create FP16 coeff (as AVX512 enable => AVX2 enable) but the code will not...:(
I still can begin to implement the structure to use it even if not implemented.
I'll have to move the create coeff function in the avx2 specific file, as it uses AVX2 intrinsic, and it will break LLVM build if i left it in the core file.
Edit:
I've integrated the FP16, i'm also adding it to AVX512 code.
Edit2:
I have a warning with const __m128i coeff_fp16 = _mm256_cvtps_ph(coeff, _MM_FROUND_NO_EXC);saying that _MM_FROUND_NO_EXC (value 8) is out of boundary for the parameter (0..7).
DTL
2nd October 2025, 16:21
"What's the status of your FP16 ? Is it still in test or can it be implemented ?"
It is expected as ready for use up to AVX2 (really with AVX2 only in my commits). AVX512 frame processing not designed yet because I do now have AVX512 CPU for encoding and for development. AVX512 is possible but require same significant re-write of _avx512.cpp file (in case of dual-rows processing as in AVX2 now). Also dual-rows processing need testing for performance at real AVV512 CPU (not in simulator SDE) if it make some performance advantage.
In theory SSE version of FP16 also possible - instructions
__m128i _mm_cvtps_ph (__m128 a, int sae)
__m128 _mm_cvtph_ps (__m128i a)
exist. But I not sure if some CPUs exist with SSE only and FP16C support. It may be rare enough models and very old too.
"I have a warning with
Code:
const __m128i coeff_fp16 = _mm256_cvtps_ph(coeff, _MM_FROUND_NO_EXC);
saying that _MM_FROUND_NO_EXC (value 8) is out of boundary for the parameter (0..7)."
It is a note from intrinsics guide to save from possible exceptions - https://www.laruence.com/sse/#text=_mm256_cvtps_ph&expand=1775
__m128i _mm256_cvtps_ph (__m256 a, int sae)
#include <immintrin.h>
Instruction: vcvtps2ph xmm, ymm, imm8
CPUID Flags: FP16C
Description
Convert packed single-precision (32-bit) floating-point elements in a to packed half-precision (16-bit) floating-point elements, and store the results in dst.
Exceptions can be suppressed by passing _MM_FROUND_NO_EXC in the sae parameter.
I do not know why it throws warning. Google shows some discussion about this issue in MSVS support forum - https://developercommunity.visualstudio.com/t/-mm-cvtps-ph-doesnt-accept-mm-fround-no-exc/1343857
Intel instructions manual says only bits 0,1,2 of the imm8 are used.
https://i.ibb.co/KpTCbPd7/image-2025-10-02-183621514.png (https://ibb.co/zTqKQMpb)
So >7 values are ignored. May be it can be safely removed (set to 0 ?). May be it was AMD (or other CPUs ?) specification ?
"move the create coeff function in the avx2 specific file, as it uses AVX2 intrinsic, "
In theory some C-software (or linkable binary library file) may exist to make non-SIMD conversions into and from FP16 format. If found (with enough license permissions) it may be pure non-SIMD version. But it may be much slower. At least it may be useful for x86 users to save from out of memory too early errors. In the standard C-libraries it looks not exist (at least old before C++2x ?).
jpsdr
2nd October 2025, 18:29
I've pushed all the new stuff.
DTL
2nd October 2025, 22:39
For AVX512 it is worth to test dual-rows 4x planes processing too. AVX512 uses 32 registers (at least in x64 mode ?) and it is expected to be enough for 4planes 2 rows load (+ 2 rows of coeffs) and intermediate accumulators and other possible temporals without registers temporal offload to cache.
32 registers also only 'logical' CPU model for instructions. Real physical implementations of SIMD parts of CPUs may have much larger memory for register file simulation and work even faster in comparison with 'simple' logical model. Though compiler of C program into binary file for distribution and execution can not go out of 'virtual' registers number and addressing in the instructions defined in the standard for given SIMD instructions set and will produce register to memory load/store instructions if no more registers left to hold temporal data. These instructions may or may not skipped by CPU microcode but if any instruction send data to physical RAM address it may end to real very long and slow operation of downloading this data via all cache levels from CPU core into SDRAM even if it is completely temporal and can be discarded after some loop exit. And still no 'scratch pad memory' in general PC architecture exist to relax limitation on very low available real-temporal data storage like 'registers' objects.
The 'result*' accumulators https://github.com/jpsdr/JincResizeMT/blob/750bf15230147de1b9c1746043dbe6fccbe10b6e/Src/resize_plane_avx512.cpp#L923 are pure temporal and if compiler will run out of 'registers' resources and will try to swap them in memory at each loop spin it may degrade performance. With AVX512 it is less probable even for 4 planes 2 rows processing (8x 512bit register objects required at least). With handcrafted ASM it is easy to control and avoid but takes longer time to develop.
For AVX2 file compiled with AVX512 support it is a question - can it use 32x 256bit registers ?
With AVX512 and small sized filters (like JincResize(tap=3) in upsampling mode) we have an issue of too large coeffs_stride of 16 while filter size may be <8. This again make not dense packing of coeffs rows in RAM and 2x RAM size and bandwidth is wasted. So with current functions it may be recommended to use AVX256 for filter size <8 and AVX512 for filter size >=8. But it is not easy for end user to calculate filter size from (taps/support + upsize/downsize ratio). This can be added into readme - recommendation to test AVX2 or AVX512 for best performance with current filter settings.
Possible solution for correct usage of AVX512 register file with 32 registers is to make 2 copies of AVX512 processing functions for 512bit and 256bit data size. To support coeffs stride of mod8 and mod16 separately. So it can use coeffs_stride = 8 and 32 x 256bit registers to process up to 4 planes with 2 rows per V-loop spin. In C program text it may be possible to implement with one more template parameter like coeff_stride of <16 (mod8) or >=16 (and mod8 or mod16) for processing functions from _avx512.cpp file to make number of functions with separate program text smaller.
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.