View Full Version : Internaly multi-threaded resampling functions
jpsdr
12th August 2016, 13:53
I've made a plugin of the resampling functions, with internal multi-threading.
I can allready ear the "why" ?
The answer is "because"...:D
More seriously, i'll explain my point of view.
For multi-threading in image processing, if you have n cores, you have basicaly two ways :
Case 1 : You process n pictures in parallel.
Case 2 : You process n 1/nth part of the picture in parallel.
The internal core can only offer the case 1, because it has no idea how to handle junction/overlap of the splitted image, neither how to split it (even if the most common way is the vertical split), and even more if splitting is possible.
The case 2 is only possible internaly, because only the filter know how to split, join, handle overlap, etc...
Personnaly, i don't like the case 1, i think case 2 is a way better.
Differences may not be very obvious with few cores, but it will become with a lot of cores.
With case 2, the memory used is always the same, whatever the number of cores you have, when with case 1, it always increases. The more cores you have, the less memory each core is working on => the more chance you have to fit it in lower cache level => the more performance you can get. Forget it with case 1.
For example, if you have an high-end 10 cores with HT CPU, giving you 20 cores.
A UHD 4k YV12 picture has a size of around 12,5MB. The whole picture fit on the L3 cache, so the 20 cores will access their data within it. I'll let you guess the result with case 1...
A FHD 1080p YV12 picture has a size of around 3MB, fit in L3 but 1/20 gives 155KB, this fits on L2 cache, so each core will work with data from his own L2 cache. Case 1 doesn't even fit in L3 cache.
You may not have this point of view, but for those who share it, you can use my multi-threaded version.
This version works on all avs+ version, and on all 2.6.x versions.
Current version : 2.11.1
Sources are here (https://github.com/jpsdr/ResampleMT).
Binaries are here (https://github.com/jpsdr/ResampleMT/releases/download/2.11.1/ResampleMT_v2_11_1.7z).
Version history
2.11.1 : Pinterf's fix for AVX512 compute coeff.
2.11.0 : Implement new core code update.
2.10.0 : Implement core code update with AVX512.
2.9.8 : Minor changes (more code refactory).
2.9.0 : Revert and remove all ASM code, benchmark not good. Fix SSE crash & Bicubic.
2.8.0 : Add ASM code AVX/AVX2/AVX512.
2.7.1 : Update matrix class.
2.7.0 : Update to new SSE code.
2.6.0 : Update to new AVX2 code.
2.5.1 : Fix for optimized functions.
2.5.0 : Add chroma placement and new filters coeff calcul.
2.4.0 : Add force parameter, fix a crop issue and update Gaussresize.
2.3.9 : Update to new AVS+ headers.
2.3.8 : Add DTL pull-request (update on UserDefined2ResizeMT).
2.3.7 : Update to new AVS+ headers.
2.3.6 : Update on threadpool, no user limit (except memory).
2.3.5 : Fix on threadpool, using prefetch parameter created hang. Add negative prefetch for triming, read Multithreading.txt or Multithreading chapter here. Fix for RGB packed format.
2.3.4 : Fix on threadpool.
2.3.3 : Working fix for too small size vs size filter, update avs headers.
2.3.2 : Add UserDefined2ResampleMT function, fix for too small size vs size filter.
2.3.1 : Add SincLin2Resize function.
2.3.0 : Add SinPowResize function.
2.2.3 : Update of the threadpool, update to new avisynth headers.
2.2.2 : Minor code change after threadpool update, fix in the number of threads,
fix to perfectecly match avs+ output (V/H resize order was sometimes different).
2.2.0 : Update of the threadpool, add ThreadLevel parameter.
2.1.2 : Update of Matrix Class.
2.1.1 : Optimized CPU placement if SetAffinity=true for prefetch>1, SetAffinity back to default false.
2.1.0 : Merge new core resampler code, filter is MT_NICE.
2.0.3 : Fix a bug in the MTData, add check for inverting matrix in desample.
2.0.2 : Minor update on threadpool.
2.0.1 : Desample now handle properly croped and/or high value shifted original.
2.0.0 : Added the Desample functions.
1.5.8 : Fix possible deadlock in threadpool, and fix issue of filter "doing nothing".
1.5.7 : Fix in threadpool.
1.5.6 : Minor fix.
1.5.5 : Minor changes on threadpool.
1.5.4 : Minor update on threadpool.
1.5.3 : Update avs header, fix range mode issue.
1.5.2 : Some fixes, add range mode 4, set range mode default to 1, apply range only on last step.
1.5.1 : Change code to allow the merge of the plugins.
1.5.0 : Add range parameter.
1.4.0 : Add sleep and prefetch parameters.
1.3.0 : Update to the new resample core functions. Update to the new avs header and support of all supported video formats. Build with /MD instead of /MT.
1.2.6 : Use Mutex instead of CriticalSection on some places and some changes in the threadpool interface.
1.2.5 : Fix deadlock case in Threadpool interface. Remove CACHE_DONT_CACHE_ME and small changes in the threadpool interface.
1.2.4 : Add several parameters to allow more specific tuning of the threadpool if necessary.
1.2.3 : Minor change.
1.2.2 : Update of the threadpool and minor change.
1.2.1 : Update the threadpool interface, fix the deadloock on init and other small things.
1.2.0 : Update the threadpool interface.
1.1.0 : Use an external thread pool class but internaly to prevent the thread creation explosion.
1.0.2 : Test of finaly a bad idea...
1.0.1 : Fix Intel compiler warning.
1.0.0 : First release.
==================================================================
Desample
Desample functions added on v2.0.0
DeBilinearResizeMT
DeBicubicResizeMT
DeLanczosResizeMT
DeLanczos4ResizeMT
DeBlackmanResizeMT
DeSpline16ResizeMT
DeSpline36ResizeMT
DeSpline64ResizeMT
DeGaussResizeMT
DeSincResizeMT
DeSinPowResizeMT
DeSincLin2ResizeMT
DeUserDefined2ResampleMT
More information on Desample functions here (http://forum.doom9.org/showthread.php?p=1817097#post1817097).
==================================================================
The functions inside this plugin are :
PointResizeMT
BilinearResizeMT
BicubicResizeMT
LanczosResizeMT
Lanczos4ResizeMT
BlackmanResizeMT
Spline16ResizeMT
Spline36ResizeMT
Spline64ResizeMT
GaussResizeMT
SincResizeMT
SinPowResizeMT
SincLin2ResizeMT
UserDefined2ResampleMT
Parameters are exactly the same than the orignal resampling functions, and in the same order, so they are totaly backward compatible.
For the new kernel functions added, check the ReadMe file.
Several new parameters are added at the end of all the parameters :
threads -
Controls how many threads will be used for processing. If set to 0, threads will
be set equal to the number of detected logical or physical cores,according logicalCores parameter.
Default: 0 (int)
logicalCores -
If threads is set to 0, it will specify if the number of threads will be the number
of logical CPU (true) or the number of physical cores (false). If your processor doesn't
have hyper-threading or threads<>0, this parameter has no effect.
Default: true (bool)
MaxPhysCore -
If true, the threads repartition will use the maximum of physical cores possible. If your
processor doesn't have hyper-threading or the SetAffinity parameter is set to false,
this parameter has no effect.
Default: true (bool)
SetAffinity -
If this parameter is set to true, the pool of threads will set each thread to a specific core,
according MaxPhysCore parameter. If set to false, it's leaved to the OS.
Default: true (bool)
sleep -
If this parameter is set to true, once the filter has finished one frame, the threads of the
threadpool will be suspended (instead of still running but waiting an event), and resume when
the next frame will be processed. If set to false, the threads of the threadpool are always
running and waiting for a start event even between frames.
Default: false (bool)
prefetch -
This parameter will allow to create more than one threadpool, to avoid mutual resources acces
lock/wait if "prefetch" is used in the avs script.
0 : Will set automaticaly to the prefetch value use in the script. Well... that's what i wanted
to do, but for now it's not possible for me to get this information when i need it, so, for
now, 0 will result in 1. For now, if you're using "prefetch" in your script, put the same
value on this parameter.
range -
This parameter specify the range the output video data has to comply with.
Limited range is 16-235 for Y, 16-240 for U/V. Full range is 0-255 for all planes.
Alpha channel is not affected by this paramter, it's always full range.
Values are adjusted according bit depth of course. This parameter has no effect
for float datas.
0 : Automatic mode. If video is YUV mode is limited range, if video is RGB mode is
full range, if video is greyscale (Y/Y8) mode is Y limited range.
1 : Force full range whatever the video is.
2 : Force limited Y range for greyscale video (Y/Y8), limited range for YUV video,
no effect for RGB video.
3 : Force limited U/V range for greyscale video (Y/Y8), limited range for YUV video,
no effect for RGB video.
4 : Force special camera range (16-255) for greyscale video (Y/Y8) and YUV video,
no effect for RGB video.
Default: 1
ThreadLevel -
This parameter will set the priority level of the threads created for the processing (internal
multithreading). No effect if threads=1.
1 : Idle level.
2 : Lowest level.
3 : Below level.
4 : Normal level.
5 : Above level.
6 : Highest level.
7 : Time critical level (WARNING !!! use this level at your own risk)
Default : 6
The logicalCores, MaxPhysCore, SetAffinity and sleep are parameters to specify how the pool of thread will be created and handled, allowing if necessary each people to tune according his configuration.
So, syntax is :
ResampleFunction([original parameters],int threads, bool logicalCores, bool MaxPhysCore, bool SetAffinity, bool sleep, int prefetch,int range)
==================================================================
JincResizeMT
Current version : 1.1.0
Sources are here (https://github.com/jpsdr/JincResizeMT).
Binaries are here (https://github.com/jpsdr/JincResizeMT/releases/download/1.1.0/JincResizeMT_v1_1_0.7z).
JincResizeMT(clip, int target_width, int target_height, float src_left, float src_top, float src_width, float src_height, int quant_x, int quant_y, int tap, float blur, string cplace, int threads, int opt, int initial_capacity, float initial_factor, int range, bool logicalCores, bool MaxPhysCore, bool SetAffinity, bool sleep, int prefetch, int ThreadLevel)
)
Jinc36ResizeMT/Jinc64ResizeMT/Jinc144ResizeMT/Jinc256ResizeMT(clip, int target_width, int target_height, float src_left, float src_top, float src_width, float src_height, int quant_x, int quant_y, string cplace, int threads, int range, bool logicalCores, bool MaxPhysCore, bool SetAffinity, bool sleep, int prefetch, int ThreadLevel)
)
See the ReadMe file for more informations and descriptions of the functions and their parameters.
==================================================================
Multi-threading information
CPU example case : 4 cores with hyper-threading.
If you leave all the multi-threading parameters to their default value, it's set to be "optimal" when you're not using prefetch or if you are under standard avisynth, all the logical CPU will be used.
If you put SetAffinity to true it will allocate the threads on the CPU contiguously. Physical CPU 1 will have threads (0,1), ... physical CPU 4 will have threads (6,7), allowing optimal cache use. Make test to see what's best for you.
Now, if you are using prefetch on your script, things are different !
If you're using it with the max number of CPUs (8 in our exemple case), you still can make tests, but i would strongly advise to disable the internal multi-threading by using threads=1. In this case, there is no threadpool created, and all the other multi-threading related filter parameters have no effect, even prefetch.
If you're using prefetch on your script, with less than your CPU number, you may want to try to mix the external and internal mutli-threading, setting the internal multi-threading to a lower number of threads, and setting the prefetch parameter of the filter. This parameter will set the number of internal threadpool created, the best is to match the prefetch script value. If you don't set it (leave it to 1) or set a lower value than prefetch on your script, you'll have several instances (or GetFrame) created, but they'll not be running efficiently, because each instance (or GetFrame) will spend time waiting for a threadpool to be avaible, if not enough were created.
Unfortunately, as things are now, i have no way of knowing the prefetch value used in the avisynth script at the time i need the information, this is why you have to use the prefetch parameter in the filter.
In our CPU exemple case, you can have things like :
filter(...,threads=1)
prefetch(8)
or
filter(...,threads=2,prefetch=4)
prefetch(4)
or
filter(...,threads=4,prefetch=2)
prefetch(2)
or even
filter(...,threads=3,prefetch=4)
prefetch(4)
if you want to boost and go a little over your total CPU number.
Also, if your prefetch is not higher than your number of physical cores, you can try to put SetAffinity to true, but in that case, you have to set MaxPhysCore to false. The threads of each pool will be set on CPUs by steps.
For exemple, in our case :
filter(...,threads=2,prefetch=4,SetAffinity=true,MaxPhysCore=false)
prefetch(4)
Will create 4 pool of 2 threads, with the following :
pool[0] : threads(0 -> 1) on CPU 1.
pool[1] : threads(0 -> 1) on CPU 2.
pool[2] : threads(0 -> 1) on CPU 3.
pool[3] : threads(0 -> 1) on CPU 4.
filter(...,threads=4,prefetch=2,SetAffinity=true,MaxPhysCore=false)
prefetch(2)
Will create 2 pool of 4 threads, with the following :
pool[0] : threads(0 -> 1) on CPU 1.
pool[0] : threads(2 -> 3) on CPU 2.
pool[1] : threads(0 -> 1) on CPU 3.
pool[1] : threads(2 -> 3) on CPU 4.
Negative prefetch
The possibility to put negative prefecth to tune the prefetch parameter to optimal value has been added. The filter will throw an error if the number is not high enough to avoid waiting when requesting internal threadpool. For this to work properly, you have to put negative prefetch on ALL the filters of your script, and also ALL instances of the same filter.
Exemple :
filter(...,threads=2,prefetch=-2)
prefetch(2)
You'll see an error.
But with :
filter(...,threads=2,prefetch=-3)
prefetch(2)
You'll see no error, so the optimal is :
filter(...,threads=2,prefetch=3)
prefetch(2)
Once you've tune, put back a positive value.
shekh
12th August 2016, 14:38
This is interesting. Do you have any actual timings of slice vs frame performance?
I dont know much low level, but my impression is you are lucky if you are bound by L1 cache (you are already in the fast camp), and L1 caches are per-core anyway. If there is enough computation it can easily dominate over all memory bottlenecks.
jpsdr
12th August 2016, 15:07
L1... 32k max... you can fit only a few lines of pictures pictures, but small pictures more indeed, even better than L2 if lucky(which are also per core).
jpsdr
12th August 2016, 19:49
Need help from C++ expert, why Intel compiler is not happy ?
Of course, no issue with Visual Studio.
Code is :
class ResamplingFunction
/**
* Pure virtual base class for resampling functions
*/
{
public:
virtual double f(double x) = 0;
virtual double support() = 0;
virtual ResamplingProgram* GetResamplingProgram(int source_size, double crop_start, double crop_size, int target_size, IScriptEnvironment* env);
};
class PointFilter : public ResamplingFunction
/**
* Nearest neighbour (point sampler), used in PointResize
**/
{
public:
double f(double x);
double support() { return 0.0001; } // 0.0 crashes it.
};
static PClip CreateResize( PClip clip, int target_width, int target_height, int _threads, const AVSValue* args,
ResamplingFunction* f, IScriptEnvironment* env );
return CreateResize( args[0].AsClip(), args[1].AsInt(), args[2].AsInt(),args[7].AsInt(0), &args[3],
&PointFilter(), env );
Error message is :
1>resample.cpp(2458): error : expression must be an lvalue or a function designator
1> &PointFilter(), env );
1> ^
I also have :
1>resample.cpp(2472): warning #1563: taking the address of a temporary
1> &MitchellNetravaliFilter(args[3].AsDblDef(1./3.), args[4].AsDblDef(1./3.)), env );
1> ^
Expert help needed... :thanks:
feisty2
12th August 2016, 20:03
I'm no c++ expert but what's "&PointFilter()"?
I assume that PointFilter is a class name here, so PointFilter() is the constructor?
You can't take the memory address of constructors...
And you didn't overload (), so not a functor either
jpsdr
12th August 2016, 20:07
Ok, for now just VS builds, no Intel, so you can test, torture, whatever you want.
@feisty2 : Well, VS don't complain, so maybe it's something "acceptable". It's not the 1rst time i encounter some kind of issue where Intel compiler is less tolerant than (or too strict ?) than VS.
But, this is out of my skills, need realy a c++ expert.
feisty2
12th August 2016, 20:15
Ahhh, got it
&PointFilter::PointFilter is taking the address of the constructor which is invalid
&PointFilter() is taking the address of a TEMPORARY object, the object has the type of PointFilter &&, which is an xvalue, and you can't take the address of that either
feisty2
12th August 2016, 20:25
ResamplingFunction *f -> ResamplingFunction &&f
&PointFilter() -> PointFilter()
will probably work...
Edit: you might be using an obsolete msvc which does not feature rvalue reference, a modern c++ feature, so it didn't bitch about nothing
jpsdr
12th August 2016, 20:43
Euh... maybe a little for VS2010, but VS2015 update 3 compile without even a warning... Too much permissive maybe.
feisty2
12th August 2016, 20:54
Well you should probably not write super confusing stuff like "&PointFilter()" in the future, I mean, who the hell even remembers if & has the higher priority or () does...
Modern c++ is nice, so kiss c++98 goodbye
shekh
12th August 2016, 20:57
&PointFilter() is taking address of a temporary object, which has a const qualifier
I would put this on separate line
PointFilter filter;
return CreateResize( args[0].AsClip(), args[1].AsInt(), args[2].AsInt(),args[7].AsInt(0), &args[3], &filter, env );
but I hope CreateResize does not store that pointer somewhere
jpsdr
12th August 2016, 21:11
I've figure out. Some throw error, some throw warning. Those who throw warning are those where a constructor is defined, those with error are those where there is no constructor defined. Default constructor is apparently not enough for the Intel compiler, just adding an empty constructor transform the error in a warning. I'll try also the shekh suggestion, it seems safer to me...
Edit : With skeh suggestion, there not even the warning anymore. I'll update to this, even if maybe it's not realy necessary, i don't like warning if i can avoir them... ;)
feisty2
12th August 2016, 21:13
&PointFilter() is taking address of a temporary object, which has a const qualifier
I would put this on separate line
PointFilter filter;
return CreateResize( args[0].AsClip(), args[1].AsInt(), args[2].AsInt(),args[7].AsInt(0), &args[3], &filter, env );
but I hope CreateResize does not store that pointer somewhere
Nah, you're passing out an address on stack, &filter is a dangling pointer!
The object is meant to be on the stack of CreateResize, not the stack of the function that calls CreateResize!
jpsdr
12th August 2016, 22:08
Anyway, it's working... Update the 1rst post with a 1.0.1 version, you can torture, test, etc...
feisty2
12th August 2016, 22:38
Fine, I was wrong and delusional and half asleep, "return" gave me a false illusion as if it jumped out of the function that called CreateResize and continued at CreateResize and therefore filter was freed, but it indeed jumped to CreateResize, just never out of the outer function, it will go back to that outer function when CreateResize is done so filter was never freed
Hopefully next time I won't reply when I'm taking a nap..
ultim
13th August 2016, 15:22
Bonus points for using a thread pool and not starting new threads each frame. Now when IScriptEnv2 finalizes, this plugin only needs to use the internal pool of Avs+ to make it better :D
I am also not convinced by the caching arguments, but I can see other ways this kind of threading is helpful. Most obvious example, for large frames (4K or 8K) where memory needs for frame-based threading become prohibitive for normal users, the lower memory needs of slice-based threading may come to the rescue, which will certainly be faster than swapping memory to disk.
jpsdr
13th August 2016, 17:55
this plugin only needs to use the internal pool of Avs+ to make it better :D
I'm not closed to it, if it's possible (i'm not against a link to something using it providing an exemple), and if it doesn't break the compatibility with running "the same way" under any avs+ and any avs 2.6.x. (which may probably need to keep the actual code, and create another code specific to the use of this thread pool, increasing complexity, but, again, why not... It's something i have to see):D
After, for the thread pool, i've just used and adapted what Tritical has done under nnedi3.
Personnaly, i'll never allocated/create/etc... on each frame !
You do this once for all on constructor. Well, it's also of course my personnal point of view.
The other thing i'll try to see if it improve speed, is to test the "trick" i've used on nnedi3 on RGB24. It helped on it, i have to check to see if it can also help on this case.
ultim
13th August 2016, 21:26
well it's in an unstable interface which is why there are no real examples yet for its usage. it's also why you should wait a little more. once it's deemed ready, I'll announce it and provide descriptions+examples of the most useful features.
TheFluff
13th August 2016, 23:16
Stephen R. Savage posted this earlier but since he loves deleting his own posts I'll repeat what I remember of it:
There's no evidence that slice-based threading is any faster than frame-based threading for a convolution filter like a resizer. Frame-based threading of the Vapoursynth internal resizers scaled very close to linearly up to 24 cores in Stephen's tests (24 cores, 23.8x speedup compared to one core). He had some argument that there is no cache advantage to the slice-based threading because there's no shared data between lines, or something? I don't remember. But anyway internally multithreading like this is likely pointless, at least for resizers. Then again I'm pretty sure avs-mt's frame-based multithreading design is bad but I don't really have the evidence to back that up.
You may not have this point of view, but for those who share it, you can use my multi-threaded version.
I realize that "optimizing" things based on guesswork, hearsay and fundamental misunderstandings of the underlying technology is a very doom9 thing to do (remember that guy who wrote 3000 lines of asm to try to optimize memcpy even though optimizing memcpy does absolutely nothing in the real world?), but holy shit, seriously. Dude. If you optimize something, you'd better benchmark it to prove that is faster than the thing you wanted to improve on. One algorithm being faster than another isn't an opinion or a point of view. Don't try to rice shit without benchmarks.
Chikuzen
13th August 2016, 23:25
benchmark?
I did. http://pastebin.com/ZCNnN5RW.
Groucho2004
13th August 2016, 23:55
Another one (i5 2500K @ 4GHz, XP32, AVS+ r2085):
Internal resizers:
[Runtime info]
Frames processed: 1000 (0 - 999)
FPS (min | max | average): 1.743 | 255682 | 17.60
Memory usage (phys | virt): 293 | 295 MiB
Thread count: 13
CPU usage (average): 99%
Time (elapsed): 00:00:56.812
[Script]
colorbars(width = 1920 * 2, height = 1080 * 2, pixel_type = "yv12").killaudio().assumefps(25, 1).trim(0, 999)
BilinearResize(width() - 64, height() - 64)
BicubicResize(width() - 64, height() - 64)
LanczosResize(width() - 64, height() - 64)
Lanczos4Resize(width() - 64, height() - 64)
BlackmanResize(width() - 64, height() - 64)
Spline16Resize(width() - 64, height() - 64)
Spline36Resize(width() - 64, height() - 64)
Spline64Resize(width() - 64, height() - 64)
GaussResize(width() - 64, height() - 64)
SincResize(width() - 64, height() - 64)
Prefetch(4)
Plugin resizers:
[Runtime info]
Frames processed: 1000 (0 - 999)
FPS (min | max | average): 6.480 | 16.84 | 16.66
Memory usage (phys | virt): 76 | 79 MiB
Thread count: 89
CPU usage (average): 99%
Time (elapsed): 00:01:00.031
[Script]
colorbars(width = 1920 * 2, height = 1080 * 2, pixel_type = "yv12").killaudio().assumefps(25, 1).trim(0, 999)
BilinearResizeMT(width() - 64, height() - 64, threads = 4)
BicubicResizeMT(width() - 64, height() - 64, threads = 4)
LanczosResizeMT(width() - 64, height() - 64, threads = 4)
Lanczos4ResizeMT(width() - 64, height() - 64, threads = 4)
BlackmanResizeMT(width() - 64, height() - 64, threads = 4)
Spline16ResizeMT(width() - 64, height() - 64, threads = 4)
Spline36ResizeMT(width() - 64, height() - 64, threads = 4)
Spline64ResizeMT(width() - 64, height() - 64, threads = 4)
GaussResizeMT(width() - 64, height() - 64, threads = 4)
SincResizeMT(width() - 64, height() - 64, threads = 4)
TheFluff
14th August 2016, 01:13
benchmark?
I did. http://pastebin.com/ZCNnN5RW.
38.607 sec [77.706fps] #threads=1, prefetch=1
36.943 sec [81.206fps] #threads=2, prefetch=1
Is this an error in the benchmark or does it basically not scale at all with two threads?
jpsdr
14th August 2016, 10:01
As i'm often doing this at work during lunch break, and i have only at it a 2 cores CPU without HT, when i've checked on it between 1 or 2 threads, there was a significant difference, and yes, looking also at the result, it scales with 2 threads.
I'm very busy today, but tomorrow i'll try on my PC some benchmark also...
jpsdr
15th August 2016, 10:21
Some tests on my PC :
[OS/Hardware info]
OS version: Windows 7 (x64) Service Pack 1 (Build 7601)
CPU (brand string): Intel(R) Core(TM) i7-6950X CPU @ 3.00GHz
CPU (code name): Unknown Core 2
CPU clock (measured): 3624 MHz
CPU cores / Logical cores: 10 / 20
[Avisynth info]
Avisynth VersionString: AviSynth+ 0.1 (r2151, MT, x86_64)
Avisynth VersionNumber: 2.60
File version: 0.1.0.0
Avisynth Interface Version: 6
Multi-threading support: Yes
Linker/compiler version: 14.0
[Clip info]
Number of frames: 10000
Length (hh:mm:ss.ms): 00:06:40.000
Frame width: 3776
Frame height: 2096
Framerate: 25.000 (25/1)
Colorspace: YV12
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 118.3 | 149685 | 492.7
Memory usage (phys | virt): 1196 | 1191 MiB
Thread count: 41
CPU usage (average): 99%
Time (elapsed): 00:00:20.295
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
Spline36Resize(width()-64,height()-64)
Prefetch(20)
[OS/Hardware info]
OS version: Windows 7 (x64) Service Pack 1 (Build 7601)
CPU (brand string): Intel(R) Core(TM) i7-6950X CPU @ 3.00GHz
CPU (code name): Unknown Core 2
CPU clock (measured): 3624 MHz
CPU cores / Logical cores: 10 / 20
[Avisynth info]
Avisynth VersionString: AviSynth+ 0.1 (r2151, MT, x86_64)
Avisynth VersionNumber: 2.60
File version: 0.1.0.0
Avisynth Interface Version: 6
Multi-threading support: Yes
Linker/compiler version: 14.0
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 459.2 | 505.2 | 502.5
Memory usage (phys | virt): 63 | 58 MiB
Thread count: 61
CPU usage (average): 92%
Time (elapsed): 00:00:19.900
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
Spline36ResizeMT(width()-64,height()-64)
#Prefetch(20)
[OS/Hardware info]
OS version: Windows 7 (x86) Service Pack 1 (Build 7601)
CPU (brand string): Intel(R) Core(TM) i7-6950X CPU @ 3.00GHz
CPU (code name): Unknown Core 2
CPU clock (measured): 3623 MHz
CPU cores / Logical cores: 10 / 20
[Avisynth info]
Avisynth VersionString: AviSynth 2.60, build:Mar 31 2015 [16:38:54]
Avisynth VersionNumber: 2.60
File version: 2.6.0.6
Avisynth Interface Version: 6
Multi-threading support: No
Linker/compiler version: 6.0
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 21.46 | 43.44 | 43.26
Memory usage (phys | virt): 46 | 44 MiB
Thread count: 1
CPU usage (average): 5%
Time (elapsed): 00:03:51.170
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
Spline36Resize(width()-64,height()-64)
[OS/Hardware info]
OS version: Windows 7 (x86) Service Pack 1 (Build 7601)
CPU (brand string): Intel(R) Core(TM) i7-6950X CPU @ 3.00GHz
CPU (code name): Unknown Core 2
CPU clock (measured): 3623 MHz
CPU cores / Logical cores: 10 / 20
[Avisynth info]
Avisynth VersionString: AviSynth 2.60, build:Mar 31 2015 [16:38:54]
Avisynth VersionNumber: 2.60
File version: 2.6.0.6
Avisynth Interface Version: 6
Multi-threading support: No
Linker/compiler version: 6.0
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 404.0 | 484.2 | 481.5
Memory usage (phys | virt): 47 | 45 MiB
Thread count: 41
CPU usage (average): 90%
Time (elapsed): 00:00:20.769
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
Spline36ResizeMT(width()-64,height()-64)
Groucho2004
15th August 2016, 10:59
Intel(R) Core(TM) i7-6950X
Nice CPU, bloody expensive.
jpsdr
15th August 2016, 11:04
Nice CPU, bloody expensive.
It helps when brother works on Intel...:D
real.finder
18th August 2016, 02:38
intel SSE4.2 Release gave me 0x7e error in load plugin for both x64 and x86
the cpu is core i7 X 980 in os windows server 2008 R2, same intel SSE4.2 Release for nnedi3 always work
jpsdr
18th August 2016, 08:45
I'll check when back at home if i see anything strange in the build profile. My standard PC is no more than SSE4.2 (i7@850), and didn't have issue with it. Do you have the last Intel Redistribuable version ? (Don't know if it can be related...).
EDIT : Can you check if you have the same issue with either the AutoYUY2 filter and/or my VDub filters ?
real.finder
18th August 2016, 15:29
I'll check when back at home if i see anything strange in the build profile. My standard PC is no more than SSE4.2 (i7@850), and didn't have issue with it. Do you have the last Intel Redistribuable version ? (Don't know if it can be related...).
EDIT : Can you check if you have the same issue with either the AutoYUY2 filter and/or my VDub filters ?
AutoYUY2 SSE4.2 gave same error
Intel Redistribuable
I don't see it before, don't you build it as static? maybe you build nnedi3 as static but the others not?
jpsdr
18th August 2016, 16:13
I think all are build as static (i'll chek later). Get and install the Intel Redistribuable (https://software.intel.com/sites/default/files/managed/46/54/ww_icl_redist_msi_2016.3.207.zip), and check if it's this.
real.finder
18th August 2016, 16:33
I think all are build as static (i'll chek later). Get and install the Intel Redistribuable (https://software.intel.com/sites/default/files/managed/46/54/ww_icl_redist_msi_2016.3.207.zip), and check if it's this.
same thing :(
Groucho2004
18th August 2016, 17:00
same thing :(
You could try troubleshooting with Dependency Walker and AVSMeter (with the "-avsinfo" switch).
jpsdr
18th August 2016, 18:25
I've checked the build profile, and found for now nothing unusual, and i must confess that i have no idea what error 0x7e is... :(
After, you still have the standard no Intel releases, they are here as safeguard for such kind of cases. And in the best case, speed difference would probably be only a few percent.
Do you have the same issue with my VDub filters ?
burfadel
19th August 2016, 07:28
benchmark?
I did. http://pastebin.com/ZCNnN5RW.
What if you did thread 4, prefetch 4 (for example) instead of threads/prefetch as 8/1 or 1/8?
MysteryX
19th August 2016, 08:14
Why are you guys spending so much time on this if it gives lower performance than the regular filters with Prefetch?
jpsdr
19th August 2016, 09:12
Why are you guys spending so much time on this if it gives lower performance than the regular filters with Prefetch?
Mine #24 (http://forum.doom9.org/showpost.php?p=1777109&postcount=24) is a little higher on avs+, and a looot on standard avisynth. So basicaly, it depends on people. I never said this was for avs+ only, and that was also the purpose of it. After, everyone is free to use what they want. Personnaly, i rather keep the memory usage low, even at the cost of more threads created, but others are totaly free to have a different point of view, of course.
I've take a look at fk3db to see if i can do the same thing, but it's a lot more complex to follow, and the C++ is a little to much ++ for me... :(
Groucho2004
19th August 2016, 10:00
Mine #24 (http://forum.doom9.org/showpost.php?p=1777109&postcount=24) is a little higher on avs+, and a looot on standard avisynth. So basicaly, it depends on people. I never said this was for avs+ only, and that was also the purpose of it. After, everyone is free to use what they want. Personnaly, i rather keep the memory usage low, even at the cost of more threads created, but others are totaly free to have a different point of view, of course.
I too think it's a very good alternative for "classic" Avisynth users. It scales well, uses very little memory and it's easy to substitute the standard resizers. Nicely done. ;)
FranceBB
19th August 2016, 11:54
Tested using an i7 4 core, 8 threads, 3.60 GHz, 8 MB cache l3, AVX 2, using avisynth trying to upscale a progressive material via Spline64Resize and Spline64ResizeMT to 4K and encode it with x265.
It indeed increases the speed as the "pitch" reached by the standard Spline64 is 3.05, and is stable at 2.88 fps, while Spline64MT reaches a "pitch" of 3.84 and is stable at 3.24 fps, which is a very good improvement for me!
I tested it using an old processor as well, which is a monocore dual thread, which supports up to SSE3 trying to downscale from 1080p to 720p via Spline64 and I can tell ya that the Mt Version takes longer to start and it doesn't have any noticeable benefits. So... it really depends on your CPU. I'm gonna try it on an AMD 6 core as soon as I can, as AMD handles multithreading differently compared to Intel (in a worse way, most of the time) and maybe AMD CPUs will be the ones who will benefit most from it.
Oh, by the way, you said that you tried to do it with f3kdb, right? If you have time, how about doing it with the old good LSFmod as well? *_*
Groucho2004
19th August 2016, 13:09
Tested using an i7 4 core, 8 threads, 3.60 GHz, 8 MB cache l3, AVX 2, using avisynth trying to upscale a progressive material via Spline64Resize and Spline64ResizeMT to 4K and encode it with x265.
It seems to me that you mainly measured the encoding speed. :rolleyes:
jpsdr
19th August 2016, 20:29
I've made a new version with optimized threads repartition according physical cores.
Some tests with it :
[OS/Hardware info]
OS version: Windows 7 (x64) Service Pack 1 (Build 7601)
CPU (brand string): Intel(R) Core(TM) i7-6950X CPU @ 3.00GHz
CPU (code name): Unknown Core 2
CPU clock (measured): 3624 MHz
CPU cores / Logical cores: 10 / 20
[Avisynth info]
Avisynth VersionString: AviSynth+ 0.1 (r2172, MT, x86_64)
Avisynth VersionNumber: 2.60
File version: 0.1.0.0
Avisynth Interface Version: 6
Multi-threading support: Yes
Linker/compiler version: 14.0
[Clip info]
Number of frames: 10000
Length (hh:mm:ss.ms): 00:06:40.000
Frame width: 3776
Frame height: 2096
Framerate: 25.000 (25/1)
Colorspace: YV12
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 471.4 | 509.3 | 506.4
Memory usage (phys | virt): 63 | 58 MiB
Thread count: 41
CPU usage (average): 60%
Time (elapsed): 00:00:19.749
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
Spline36ResizeMT(width()-64,height()-64,threads=10)
#Spline36Resize(width()-64,height()-64)
#Prefetch(20)
[Clip info]
Number of frames: 10000
Length (hh:mm:ss.ms): 00:06:40.000
Frame width: 3776
Frame height: 2096
Framerate: 25.000 (25/1)
Colorspace: YV12
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 460.0 | 502.9 | 501.0
Memory usage (phys | virt): 64 | 59 MiB
Thread count: 61
CPU usage (average): 90%
Time (elapsed): 00:00:19.962
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
Spline36ResizeMT(width()-64,height()-64,threads=0)
#Spline36Resize(width()-64,height()-64)
#Prefetch(20)
[Clip info]
Number of frames: 10000
Length (hh:mm:ss.ms): 00:06:40.000
Frame width: 3776
Frame height: 2096
Framerate: 25.000 (25/1)
Colorspace: YV12
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 118.2 | 217181 | 450.0
Memory usage (phys | virt): 624 | 619 MiB
Thread count: 31
CPU usage (average): 63%
Time (elapsed): 00:00:22.222
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
#Spline36ResizeMT(width()-64,height()-64,threads=0)
Spline36Resize(width()-64,height()-64)
Prefetch(10)
[Clip info]
Number of frames: 10000
Length (hh:mm:ss.ms): 00:06:40.000
Frame width: 3776
Frame height: 2096
Framerate: 25.000 (25/1)
Colorspace: YV12
[Runtime info]
Frames processed: 10000 (0 - 9999)
FPS (min | max | average): 125.1 | 191873 | 493.3
Memory usage (phys | virt): 1196 | 1191 MiB
Thread count: 41
CPU usage (average): 99%
Time (elapsed): 00:00:20.273
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,9999)
#Spline36ResizeMT(width()-64,height()-64,threads=0)
Spline36Resize(width()-64,height()-64)
Prefetch(20)
And winners awards are :
The fastest : MT version with 10 threads (not what i hoped...:sly: )
The more memory used : Core version with 20 threads.
The more threads created : MT version with 20 threads.
Well, it depends of CPU and maybe a little tunning also.
Well, finaly, it's up to you to choose whatever version you want to use... :D
jpsdr
20th August 2016, 15:19
Pleeeaseee !!!! Help wanted.... :(
I've tried to create an external ThreadPool in a DLL for plugin to use.
I've searched and found how to create a simple DLL.
I've been ... almost... successfull.
I am for now doing tests on my "standard" PC, so it's Windows 7 x86 with Avisynth 2.6.0.
You can get the source of the ThreadPool (https://github.com/jpsdr/ThreadPoolDLL) DLL and the filter (https://github.com/jpsdr/ResampleMT).
I have one minor and one critical issue.
The minor : Even if i put the DLL in the same directory of the plugin, it's not working, i have to put it in the system32 directory. Strange, because i hadn't this issue when i've first made some quick very small little test, of an exe using 2 dll, both using a third dll... When everyone was in the same directory, everyone was happy... If anyone can tell me if there is something to do during the build process, on compiler option to add for this to be solved.
The critical : Well... Indeed, everything works fine, i can open the avs script in VDub, etc... Until.... I want to close it :eek:
It seems that in the FreeData called by the destructor, the program is stuck on the line
WaitForSingleObject(thds[i],INFINITE);
I've made a lot of trick, tests, it seems that whatever i do, the StaticThreadpool function don't want to end/exit.
The strange things is that i'm doing exactly the same working thing i'm doing everywhere else.
But, as it's the first time i'm trying to do a DLL, maybe the way i'm doing it is totaly wrong, it works by chance, and the fact that it doesn't end properly is caused by that.
Maybe it's something else, compiler option, just a little trick, no idea.
So, if there is someone with enough courage and powerwill to take a look at the sources code i've provided...
In any case :thanks:
jpsdr
20th August 2016, 18:48
I've used a workaround for the major issue (but still not sure if my way of doing things is good).
But with avs+, strange result.
jpsdr
20th August 2016, 19:17
After some test, finaly, speed is slower. Another bad idea and almost 2 days wasted... :(
Well, it's when trying that you see. But... Instead of an external DLL, the same thing internaly... May keep up with speed, and reduce the number of threads... Another thing to try.
jackoneill
20th August 2016, 20:02
Is avstp.dll not suitable for your needs?
Groucho2004
20th August 2016, 21:33
Is avstp.dll not suitable for your needs?I did some tests with avstp and plugins that use it. Unfortunately, it's not very efficient and does not scale well.
Groucho2004
20th August 2016, 21:36
After some test, finaly, speed is slower. Another bad idea and almost 2 days wasted... :(
Not really wasted, I'm sure you learned something. At least that's how I like to look at such experiences.
jpsdr
21st August 2016, 09:13
I did not tested it with MT disabled.
My internaly MT plugins are compatible with MT enabled, but they are only MT_MULTI_INSTANCE, and not MT_FRIENDLY.
I personnaly don't use MT mode, so...
jpsdr
21st August 2016, 09:16
Try to use avstp.dll
My experience (what i've just tried to do) with a thread pool in an external DLL is that's it's slower than the internal thread pool, so, i'll stay with an internal thread pool.
Groucho2004
21st August 2016, 09:51
@jpsdr
In comparisons like this one (http://forum.doom9.org/showthread.php?p=1777634#post1777634), you can make use of this AVSMeter ini setting:
"DisplayEfficiencyIndex" (0 or 1):
If set to "1", the result of (FPS / CPU usage) is printed to the console. This ratio indicates the efficiency of a script (higher = better) and is useful for comparing different versions of a script or scripts that are supposed to do the same thing.
jpsdr
21st August 2016, 11:07
Ok, new version.
- Update to new avisynth header (don't know if it changes anything, but don't hurt).
- Use an external thread pool class, but internaly within the DLL, not on an external DLL.
Previous 1.0.1 version :
[OS/Hardware info]
OS version: Windows 7 (x64) Service Pack 1 (Build 7601)
CPU (brand string): Intel(R) Core(TM) i7-6950X CPU @ 3.00GHz
CPU (code name): Unknown Core 2
CPU clock (measured): 3624 MHz
CPU cores / Logical cores: 10 / 20
[Avisynth info]
Avisynth VersionString: AviSynth+ 0.1 (r2172, MT, x86_64)
Avisynth VersionNumber: 2.60
File version: 0.1.0.0
Avisynth Interface Version: 6
Multi-threading support: Yes
Linker/compiler version: 14.0
[Clip info]
Number of frames: 1000
Length (hh:mm:ss.ms): 00:00:40.000
Frame width: 3200
Frame height: 1520
Framerate: 25.000 (25/1)
Colorspace: YV12
[Runtime info]
Frames processed: 1000 (0 - 999)
FPS (min | max | average): 39.57 | 47.14 | 46.84
Memory usage (phys | virt): 91 | 87 MiB
Thread count: 221
CPU usage (average): 47%
Time (elapsed): 00:00:21.350
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,999)
BilinearResizeMT(width()-64,height()-64,threads=10)
BicubicResizeMT(width()-64,height()-64,threads=10)
LanczosResizeMT(width()-64,height()-64,threads=10)
Lanczos4ResizeMT(width()-64,height()-64,threads=10)
BlackmanResizeMT(width()-64,height()-64,threads=10)
Spline16ResizeMT(width()-64,height()-64,threads=10)
Spline36ResizeMT(width()-64,height()-64,threads=10)
Spline64ResizeMT(width()-64,height()-64,threads=10)
GaussResizeMT(width()-64,height()-64,threads=10)
SincResizeMT(width()-64,height()-64,threads=10)
Actual 1.1.0 version :
[Clip info]
Number of frames: 1000
Length (hh:mm:ss.ms): 00:00:40.000
Frame width: 3200
Frame height: 1520
Framerate: 25.000 (25/1)
Colorspace: YV12
[Runtime info]
Frames processed: 1000 (0 - 999)
FPS (min | max | average): 27.91 | 47.02 | 46.56
Memory usage (phys | virt): 83 | 78 MiB
Thread count: 31
CPU usage (average): 47%
Time (elapsed): 00:00:21.477
[Script]
Colorbars(width=1920*2,height=1080*2,pixel_type="yv12").killaudio().assumefps(25,1).trim(0,999)
BilinearResizeMT(width()-64,height()-64,threads=10)
BicubicResizeMT(width()-64,height()-64,threads=10)
LanczosResizeMT(width()-64,height()-64,threads=10)
Lanczos4ResizeMT(width()-64,height()-64,threads=10)
BlackmanResizeMT(width()-64,height()-64,threads=10)
Spline16ResizeMT(width()-64,height()-64,threads=10)
Spline36ResizeMT(width()-64,height()-64,threads=10)
Spline64ResizeMT(width()-64,height()-64,threads=10)
GaussResizeMT(width()-64,height()-64,threads=10)
SincResizeMT(width()-64,height()-64,threads=10)
The speed loss is 0.6%... Nothing to cry about compared to the thread explosion prevention. And bonus, even a little less memory used.
My next step : Doing the same thing on other plugins i'm working on (AutoYUY2 and NNEDI3).
My next next step, creating a plugin package with my 3 filters, but the filters in standalone version will continue and will not be dropped.
The point : None if you're using only one of them, reduce the number of threads created if you're using more than one of them, because they'll all share the same thread pool.
If using an external DLL thread pool didn't produce a noticeable speed lost, it would have been the best, but it's, still from my point of view (of course you may not share it) the best compromise.
For those who are still wondering the point of the whole subjet ? I personnaly don't use the MT mode because i don't want the memory explosion usage when you have a lot of cores.
Groucho2004
23rd August 2016, 15:58
jpsdr, I sent you a PM.
jpsdr
23rd August 2016, 18:51
I've seen, let me time to get back home... :p
ultim
23rd August 2016, 19:39
My next next step, creating a plugin package with my 3 filters, but the filters in standalone version will continue and will not be dropped.
If you are so eager to add internal multithreading to all your plugins, you might really try IScriptEnv2 now. Yes if it breaks you'll have to recompile, but if you plan to switch to it later you'd have to recompile anyway+rewrite everything, you you might be better off to do it now. Users will have to upgrade both core and your plugins if that time comes. EDIT: I am planning to break the threadpool API at most once in the future, if at all, at the time it stabilizes. So you won't see repeated problems.
The basic usage is:
1) You call IScriptEnv2->NewCompletion() in your plugin constructor. The capacity arg is the number of jobs you'll enqueue at once in parallel.
2) In your GetFrame() you call IJobCompletion->Reset(), then call IScriptEnv2->ParallelJob() for each task you want to execute in the threadpool. The 1st arg is your function to execute, the 2nd is data that'll be passed to your function, and the 3rd is the compleiton object you created in step 1.
3) After all tasks are queued for a frame, you call IJobCompletion->Get to receive the result of the i'th queued job.
4) In your plugin destructor, you call IJobCompletion->destroy(). Do not call delete on this object yourself.
This will of course also reduce coding burden on you since proper thread-safe queueing, thread management, and in general everything associated with the thread pool will be managed and maintained by the core, and you won't have to maintain duplicate code for it in your plugin.
ultim
23rd August 2016, 19:54
One more thing: if you use the core's threadpool API, then ofc you can keep your plugins in separate packages, and they will still share the same threadpool. So that is an upside too.
jpsdr
23rd August 2016, 21:23
I'll try, out of curiosity to see if i see the performance drop i've noticed when i try to put the thread pool in an external DLL.
But it will complexify the code (a lot... not so much...?) to have in the same time my and your threadpool, because there is one thing not to forget : I want my plugins working multi-threaded an all avs+ and 2.6.x standard version, not only on the avs+ MT version.
But, i'll give a try, i'm curious...
ultim
23rd August 2016, 22:14
Well, if you want to stay compatible with avs 2.6, than I can understand if you don't want to use avs+-specific APIs. May I ask the reasons for wanting to support 2.6? Is it a technical reason, like an area where avs+ doesn't work as great as 2.6 does, or is it simply because avs+ is not that commonly spread as 2.6 yet? I think the latter situation will improve once a new stable version of avs+ is released, which I'm planning on in the foreseeable future.
jpsdr
24th August 2016, 08:43
It's just my way... to want to be compatible with the most possible (reasonable) versions, if it's not something if think too hard. In fact, i often hate the opposite : being obliged to stick with a specific version, when i think it could have been otherwise just with a little work. (By reasonable i mean i've dropped 2.5.x ... :p ). No realy technical reasons, just a less restriction philosophy... ;)
And for those who still use 2.6.x for any reasons, they'll have something upgraded.
After, as i said, it's just my way, for now. What future will be...?
But, i'm still curious to give a try to your api, so, maybe not right now, but very soon, i'll made a specific version using it to make some tests.
jpsdr
24th August 2016, 18:24
New version, see first post.
jpsdr
24th August 2016, 20:48
Ooops... :eek:
Wait next release tomorrow, there is a tricky deadlock. It doens't show if you open a script with VDub and seek within it, it doesn't show if you run a script with avsmeter, but, it shoes if you're creating several jobs in batch in VDub and want to run them.
Fix is done, but it's late, so new builds an push on github tomorrow.
jpsdr
25th August 2016, 20:03
New version fixed, see first post.
jpsdr
28th August 2016, 09:52
New version, see 1rst post. I'll not say final, because there may be update in the functions of the core in the future i'll try to follow, but for now, i think i'm done with the threadpool part.
jpsdr
30th August 2016, 20:41
Never said "At last, final version"... :p
New version, see 1rst post.
bilditup1
31st August 2016, 04:42
With latest avs+ mt r2172 & your nnedi3 r26, using this yields a 1.5fps increase using this script (http://forum.doom9.org/showpost.php?p=1779369&postcount=2385) on a (for now) stock 4770K. Sometimes the encode appears to halt entirely but I'm not sure if this is an avs+ issue or a ResampleMT/nnedi3 issue.
jpsdr
2nd September 2016, 17:22
New version, see first post.
I've added several parameter to allow to tune the created threadpool. I've made several testbench, and set the default settings correspond to the best result i have on my PC. But, it doesn't mean it will be also what produce the best result on others PC, so, the parameters added will allow to eventualy each one tune according his hardware configuration.
Check the ReadMe file for precise information.
real.finder
10th October 2016, 17:38
I did edit to ResizeX to make it work with this plugin
edit : add edi_rpow2_v1.0.zip and maa2 (http://forum.doom9.org/showpost.php?p=1725500&postcount=69)
new edit ResizeX (https://pastebin.com/Jkhvw9Yr) and edi_rpow2 (http://pastebin.com/jpYhcqee) will work in new avs+ colour format
edit: another ResizeX edit here https://forum.doom9.org/showpost.php?p=1823545&postcount=5 and use it with this https://pastebin.com/8kcg2MtJ edi_rpow2
jpsdr
12th October 2016, 19:14
New version (see 1rst post).
Can be used also with prefetch without issue, and can also be used with the last nnedi3 version and with prefetch also.
jpsdr
16th October 2016, 14:44
New version, see first post.
real.finder
7th December 2016, 20:10
I wonder how this filter work, it's depends on avs Resizers or have an edit of them?
I ask because I didn't see any mention on what colour format that this filter supports
jpsdr
8th December 2016, 11:28
It uses the code of the internal avs+ resamplers. About supported format, it supports all the formats supported by avs+ at the time the filter has been build.
I'm working on the new version to support the new formats, but for now it crashes in both avisynth and avs+ and i don't had time to investigate futher. But i'll probably be able to resume work on it soon.
real.finder
8th December 2016, 15:50
It uses the code of the internal avs+ resamplers. About supported format, it supports all the formats supported by avs+ at the time the filter has been build.
I'm working on the new version to support the new formats, but for now it crashes in both avisynth and avs+ and i don't had time to investigate futher. But i'll probably be able to resume work on it soon.
after I update to last ResampleMT and NNEDI3 from ResampleMT 1.0.2 and NNEDI3 0.9.4.24 I see speed lost, from 1.6 fps to 1.3 fps, is this because threadpool?
why not try using avstp?
and can z.lib (http://forum.doom9.org/showthread.php?t=173986) make it faster?
jpsdr
9th December 2016, 10:45
after I update to last ResampleMT and NNEDI3 from ResampleMT 1.0.2 and NNEDI3 0.9.4.24 I see speed lost, from 1.6 fps to 1.3 fps, is this because threadpool?
Honestly, i don't know, but it's not impossible, even more possible if you're also multi-threading again using prefetch. Is it the case ?
As there is only one threadpool shared instead of several (before, one was created for each instance filter) => result in slowdown when shared resources required.
For optimal, i suggest if you're also prefetching to reduce the number of threads and make tests. The values i suggest for testing are first threads=1, and second threads=CPU/2. And if you're using ResampleMT within NNEDI3, don't forget also to put the value on the threads_rs parameter.
why not try using avstp?
Not interested, and using an external dll for the threadpool make it even slower (i've tested with my threadpool).
and can z.lib (http://forum.doom9.org/showthread.php?t=173986) make it faster?
No idea. But if it uses directly a square kernell instead of doing two passes, multi-threading by splitting the image may be more difficult.
jpsdr
13th December 2016, 20:07
New version, check first post.
jpsdr
30th December 2016, 11:28
New version, check first post.
jpsdr
17th January 2017, 22:21
New version, check first post.
real.finder
18th January 2017, 01:46
range -
This parameter specify the range the output video data has to comply with.
Limited range is 16-235 for Y, 16-240 for U/V. Full range is 0-255 for all planes.
Alpha channel is not affected by this paramter, it's always full range.
Values are adjusted according bit depth of course. This parameter has no effect
for float datas.
0 : Automatic mode. If video is YUV mode is limited range, if video is RGB mode is
full range, if video is greyscale (Y/Y8) mode is Y limited range.
1 : Force full range whatever the video is.
2 : Force limited Y range for greyscale video (Y/Y8), limited range for YUV video,
no effect for RGB video.
3 : Force limited U/V range for greyscale video (Y/Y8), limited range for YUV video,
no effect for RGB video.
Default: 0
do we really need this? we can do it in avs Limiter if we have to, but having range in resize or nnedi make no sense and if someone work in full range will have to set it to 1 every time, and it's kinda annoying
jpsdr
18th January 2017, 09:52
I don't agree, standard YUV is not full range, so it results that nnedi and resize can produce incorrect value output. On the oposite, someone who want to work with standard value has to put a limiter...?
No, that's not a proper behavior for me, it's the contrary, you have to specify "something" when you want to work outside standard values.
real.finder
18th January 2017, 18:09
I don't agree, standard YUV is not full range, so it results that nnedi and resize can produce incorrect value output. On the oposite, someone who want to work with standard value has to put a limiter...?
No, that's not a proper behavior for me, it's the contrary, you have to specify "something" when you want to work outside standard values.
and what about scripts that do everything with Y8? at least you should make the default is 3 for Y/Y8 so that chroma will be correct ((if there is RGB support (by Y8 is scripts) then it will fall, but I don't care for RGB, if you care then Y/Y8 should have range default 1)), for both nnedi and this
edit: and scripts that did TV to PC range internally for some specific purpose like SMDegrain will fall too, so default should be 1 everywhere, that reminds me of keep_tv_range bool in flash3kyuu_deband, it's false by default
jpsdr
18th January 2017, 19:25
at least you should make the default is 3 for Y/Y8 so that chroma will be correct
But luma will not be, so...
edit: and scripts that did TV to PC range internally
A script/plugin can do whatever it wants internaly, it have to put back correct levels on its output, otherwise, it screws you things without telling you.
keep_tv_range bool in flash3kyuu_deband, it's false by default
Yes, it should be the oposite, when i use it on YUV data, i always have to put it to true.
real.finder
18th January 2017, 19:33
A script/plugin can do whatever it wants internaly, it have to put back correct levels on its output, otherwise, it screws you things without telling you.
it do tv to pc for motions search for example, your change will make that fall or useless
anyway, I will stick with the older versions, thank you for the old change efforts :) it was cool
jpsdr
18th January 2017, 19:51
it do tv to pc for motions search for example, your change will make that fall or useless
You just have now to specify that range is not anymore the standard range for the actual format, and it will not break anything.
Of course, if a metadata tag existed, it would have been perfect...
real.finder
18th January 2017, 20:12
You just have now to specify that range is not anymore the standard range for the actual format, and it will not break anything.
Of course, if a metadata tag existed, it would have been perfect...
for nnedi3, I don't have time for edit like 10 scripts just for that, and some of them still has 2.5 support (with 2.6 and plus) and that will make edit things way annoying, so I will pass
but if you kindly make range = 1 then I will taking the range into account for some scripts in future
pinterf
23rd January 2017, 08:55
I don't think either that defaulting to 16-240 is a good idea, neither on YUV, especially nor for greyscale. There are many scripts out there that rely on the old behaviour and use clip converted at the beginning to PC range. Or use Y as an extracted channel from an RGB clip, these scripts (or plugins that invoke nnedi3_rpow2) may break now.
Many camcorders and video capable cameras now are using 16-255 that contain extra highlight information (superwhites) to be retreived later on processing. You can even display this range on a capable device (there are such projectors).
Now this extra highlight information is lost by the new defaults.
I think it is not the resizers task to police over the range. People just blindly download and use latest version and see that something went wrong.
jpsdr
23rd January 2017, 10:17
I think it is not the resizers task to police over the range.
Personnaly, i think, not specificaly resizers, that any filter has to produce "in specs" output levels.
YUV "alone", without more indications or informations is by default/standard TV limited range, this is the proper behavior.
So, any filter processing YUV has to produce a proper "in spec" output.
You can have of course full range YUV, but it's not a standard behavior, so you have to specify that you're oustide the default behavior. Not the oposite, it's not when you're within the default/normal behavior that you have to specify it.
As there is not such information as the range in the avs "clip" (otherwise it could have been used), the standard prevail, and the standard for YUV for example, is that YUV is limited range.
Thanks for the 16-255 range, i didn't know about it, i'll add it in a future version.
pinterf
23rd January 2017, 10:39
Just imagine what would happen if we had to provide every plugin and avisynth filter an extra parameter not to screw up the processing, removegrains, mvtools functions, any. I maintain that until the range cannot be extracted with 99.99% confidence from a clip or frame property, this auto-clamp feature is dangerous. Unless nnedi3 is a brand new filter that is not used widespreadly and behaves as such from the beginning.
jpsdr
23rd January 2017, 12:48
I understand this point of view, i've thought about it before doing this.
But the only fact that with the actual behavior, something like this :
AVISource("My SD YV12 file",false,"YV12")
Spline36Resize(1280,720)
can produce out of spec/range output...
No... i don't like it.
And having a parameter to have to specify that your file is in the standard spec, and you want your output stay in standard spec...
Not again, i don't like it either.
I personnaly think that the actual behavior is wrong (and so my filters were wrong), i even didn't realise it until now, i'm a little pissed over myself of not noticing sooner something finaly so obvious, and i personnaly don't want to continue/keep this way.
I want that with my filters, a simple script like described before work properly when everything is within the standard specs, without having to tell when everything is "normal".
This is the behavior i'll stick with now.
shekh
23rd January 2017, 13:27
Another possible convention is that all intermediate results are allowed to be out of range, this may simplify things (values must be clamped on output and before colorspace change, but not anywhere else).
jpsdr
23rd January 2017, 14:48
Do you mean avisynth will automaticaly clamp values before outputing datas according the format of the video ? (Full range for RGB, limited for Y/YUV) ?
shekh
23rd January 2017, 15:14
No, I don`t know current set of rules, just speculating.
raffriff42
23rd January 2017, 15:44
Resampling/resizing is not affected by "range" at all, except in the case of overshoot. It should not affect a resizer AT ALL.
Handling overshoot without clipping is the whole point of so-called "limited range."
("so-called" because it actually stores a WIDER range of colors, unless it is misguidedly restricted to 16-235)
pbristow
23rd January 2017, 16:02
Another possible convention is that all intermediate results are allowed to be out of range, this may simplify things (values must be clamped on output and before colorspace change, but not anywhere else).
Yes, this, please! It is crazy to place the burden of range-checking on every single filter in the chain, and crazy to expect novices have to figure out for each and every filter what option they should be choosing if they suspect the default might not be right. You're just adding needless complexity to the debugging chain.
Levels/ranges should only ever be policed explicitly, and at a place in the chain chosen by the user to suit their needs. They shouldn't have to work their way through the whole chain of filters trying to work out which one is messing up the range, and whether its because they've chosen the wrong option or because the filter itself has a bug!
What I'd much rather see is for someone to create a nice, user friendly "levels advisor" plugin. Something that looks at the incoming clip, checks the ranges of values, and says:
"This clip is using Y/U/V values above/below the standard YUV minimum/maximum. If you wish to store this data in a standard YUV format, consider using the Levels filter to either (a) re-scale the data to fit within range; (b) clip or "hard limit" the range. If you do not think the data should be exceeding the standard range at this point in your script, trying using LevelsAdvisor earlier in your script to trace where things are going wrong."
It should of course also display the actual maximum and minimum values found in each channel, and what the standard limits are. If the actual values are only ever one LSB out of range, that suggests trivial rounding errors are at fault, and hard limiting the range is the appropriate option. If the overshoot is bigger, that suggests re-scaling (or filter chain debugging) is needed.
Wilbert
23rd January 2017, 19:21
Personnaly, i think, not specificaly resizers, that any filter has to produce "in specs" output levels.
YUV "alone", without more indications or informations is by default/standard TV limited range, this is the proper behavior.
So, any filter processing YUV has to produce a proper "in spec" output.
You can have of course full range YUV, but it's not a standard behavior, so you have to specify that you're oustide the default behavior. Not the oposite, it's not when you're within the default/normal behavior that you have to specify it.
As there is not such information as the range in the avs "clip" (otherwise it could have been used), the standard prevail, and the standard for YUV for example, is that YUV is limited range.
Thanks for the 16-255 range, i didn't know about it, i'll add it in a future version.
Like other people say it's a bad idea. Not only because you break backwards-compatibility, but also because there is no proper "in spec" output. Both are allowed in the h.264/h.265 specs for example and that's even within the same standard. Although one is more common than the other obviously.
jpsdr
23rd January 2017, 19:42
For x264, the parameter is :
--input-range <string> Specify input color range ["auto"]
- auto, tv, pc
Unable to find what "auto" is doing. Maybe it changes the range "on the fly" during the encode process if it founds a value outside the TV range...?
All docs on the net are obsolete, they talk of a "fullrange" parameter with "off" by default ;), but this parameter doesn't exist anymore.
I never said that full range is not possible, i said that standard/default range of YUV, without more information than just only saying "it's YUV", is limited range.
Edit : More likely, the "auto" probably choose tv/pc according the input data format, and/or the encoded data format, and probably behave according the standard (YUV -> TV, RGB->PC).
Well, exactly what i'm doing.
real.finder
23rd January 2017, 20:03
For x264, the parameter is :
--input-range <string> Specify input color range ["auto"]
- auto, tv, pc
Unable to find what "auto" is doing. Maybe it changes the range "on the fly" during the encode process if it founds a value outside the TV range...?
All docs on the net are obsolete, they talk of a "fullrange" parameter with "off" by default ;), but this parameter doesn't exist anymore.
I never said that full range is not possible, i said that standard/default range of YUV, without more information than just only saying "it's YUV", is limited range.
Edit : More likely, the "auto" probably choose tv/pc according the input data format, and/or the encoded data format, and probably behave according the standard (YUV -> TV, RGB->PC).
Well, exactly what i'm doing.
it's see the max and the min, with auto I encode full range anime EP (Trickster) from Japanese TV and it was fine with no clamp in auto
jpsdr
23rd January 2017, 21:34
Well... Even if i realy don't like it, i'll set back the default range on 1 on next releases, as everyone seems to want to have improper behavior, i'll put the range in my scripts...
pbristow
23rd January 2017, 21:50
If the actual values are only ever one LSB out of range, that suggests trivial rounding errors are at fault, and hard limiting the range is the appropriate option. If the overshoot is bigger, that suggests re-scaling (or filter chain debugging) is needed.
... *OR* that in fact neither is needed, because *at this particular point in the script or filter chain*, out-of-standard-range values are entirely to be expected, and nothing to worry about!
pbristow
23rd January 2017, 21:55
Just seen this, after posting my addendum:
Well... Even if i realy don't like it, i'll set back the default range on 1 on next releases, as everyone seems to want to have improper behavior, i'll put the range in my scripts...
Thankyou.
Side-note: It's not "improper behaviour" we want (who in the world wants that?). It's "behaviour that we think is proper, and you don't". =;o}
[SALUTES YOU FOR TACKLING THE MULTI-THREADING OPPORTUNITIES IN THE BEST, MOST LOGICAL AND EFFECTIVE PLACE]
jpsdr
23rd January 2017, 22:33
"Improper behavior" means thinking default YUV is full range when it's limited. You want Out of range value ? No problem, but you have to tell that you don't want normal output, that's all.
Why when you want "not out or range value", you have to specify it ? Proper behavior should be the oposite !
Well, as i said, it will be like this back again in next releases...
jpsdr
24th January 2017, 20:58
New version, see first post.
jpsdr
23rd March 2017, 09:27
New version, see first post.
FranceBB
21st April 2017, 02:36
@jpsdr
ResampleMT 1.5.3 x86 XP SSE4.2 doesn't work on Windows XP x86, although the normal XP executable works fine (the one without assembly optimizations).
My CPU is an Intel i7 6700HQ, which supports SSE4.2, in fact your NNEDI3 XP SSE4.2 build works fine.
Oh, and by the way, thank you for your work and for supporting XP ;)
jpsdr
21st April 2017, 08:42
ResampleMT 1.5.3 x86 XP SSE4.2 doesn't work on Windows XP x86, although the normal XP executable works fine (the one without assembly optimizations)
All have the same optimisations, it's just that the SSE4.2 (and all others) are compiled with SSE4.2 (or others) settings in the Intel compiler. I have unfortunately no idea why it's not working, maybe the compiler generate "more" than SSE4.2...:confused:
Just switch to "standard", the only difference is that it's compiled with Visual Studio, without specific CPU.
Maybe test also the "all in one" plugin, with resample and nnedi in the same dll.
FranceBB
22nd April 2017, 01:12
I see. I compiled it with Visual Studio with no problem in SSE and SSE2. The only problem is that Visual Studio doesn't have the option to optimize to something greater than SSE2, and compilation fails with GCC and OpenWatcom.
I'm downloading Intel Parallel Studio XE 2016 right now :)
Also, I modified afxres.h
//#include "afxres.h"
#include "WinResrc.h"
#define IDC_STATIC -1
in order to be able to compile it with Visual Studio Express :)
jpsdr
20th May 2017, 15:43
New version, see first post (minor update).
FranceBB
1st June 2017, 16:54
Tested in Windows XP SP3 x86. I'm still getting an error with XP_SSE4.2, but it's not a big deal,
'cause normal XP Release works flawlessly. Thanks! ^_^
jpsdr
3rd June 2017, 19:37
New version, see first post (minor update).
jpsdr
18th June 2017, 13:21
New version, minor change, see first post.
jpsdr
9th August 2017, 21:03
New version, see first post.
Atak_Snajpera
12th August 2017, 16:39
Do we really need so many builds for specific extension. One for SSE4.2 another for AVX,AVX2,AVX-512.
On my SandyBridge (Xeon E5-2690) binary from Release_W7/XP folder is the fastest.
Script
LoadPlugin("C:\Users\Dave\Documents\Delphi_Projects\RipBot264\_Compiled\Tools\AviSynth plugins\RawSource\RawSource.dll")
video1=RawSource("E:\_Video_Samples\y4m\crowd_run_1080p50.y4m")
video2=RawSource("E:\_Video_Samples\y4m\park_joy_1080p50.y4m")
video3=RawSource("E:\_Video_Samples\y4m\ducks_take_off_1080p50.y4m")
video4=RawSource("E:\_Video_Samples\y4m\in_to_tree_1080p50.y4m")
video5=RawSource("E:\_Video_Samples\y4m\old_town_cross_1080p50.y4m")
return video1+video2+video3+video4+video5
Resizer used:
Spline36ResizeMT(1280,720)
SSE 4.2
AVSMeter 2.2.6 (x86)
AviSynth 2.60, build:Mar 31 2015 [16:38:54] (2.6.0.6)
Loading script...
Number of frames: 2500
Length (hh:mm:ss.ms): 00:00:50.000
Frame width: 1280
Frame height: 720
Framerate: 50.000 (50/1)
Colorspace: YV12
Frames processed: 2500 (0 - 2499)
FPS (min | max | average): 127.8 | 172.1 | 153.0
Memory usage (phys | virt): 527 | 523 MiB
Thread count: 17
CPU usage (average): 14%
Time (elapsed): 00:00:16.341
AVX
AVSMeter 2.2.6 (x86)
AviSynth 2.60, build:Mar 31 2015 [16:38:54] (2.6.0.6)
Loading script...
Number of frames: 2500
Length (hh:mm:ss.ms): 00:00:50.000
Frame width: 1280
Frame height: 720
Framerate: 50.000 (50/1)
Colorspace: YV12
Frames processed: 2500 (0 - 2499)
FPS (min | max | average): 130.4 | 173.3 | 154.0
Memory usage (phys | virt): 527 | 523 MiB
Thread count: 17
CPU usage (average): 14%
Time (elapsed): 00:00:16.232
Release_W7/XP
AVSMeter 2.2.6 (x86)
AviSynth 2.60, build:Mar 31 2015 [16:38:54] (2.6.0.6)
Loading script...
Number of frames: 2500
Length (hh:mm:ss.ms): 00:00:50.000
Frame width: 1280
Frame height: 720
Framerate: 50.000 (50/1)
Colorspace: YV12
Frames processed: 2500 (0 - 2499)
FPS (min | max | average): 133.5 | 180.2 | 159.7
Memory usage (phys | virt): 527 | 522 MiB
Thread count: 17
CPU usage (average): 13%
Time (elapsed): 00:00:15.657
Is above compiled with default microsoft's compiler? What extensions did you use?
TheFluff
12th August 2017, 18:02
I'm getting removegrain flashbacks, here. Please don't trigger my PTSD.
Jokes aside, I'm pretty sure it's been repeatedly pointed out in several different places by several different people that compiling NNEDI with AVX2 (or SSE4.2 for that matter) does pretty much nothing to improve performance because there's nothing performance critical the compiler can potentially autovectorize, but that doesn't stop d9 posters from cargo cult optimizing by flipping all the compiler switches. Benchmarking is for nerds.
e: jpsdr was specifically told about this over a year ago (https://forum.doom9.org/showthread.php?p=1779552#post1779552) but, well. The internet is all FAKE NEWS anyway, can't trust anyone on here.
jpsdr
13th August 2017, 09:41
Do we really need so many builds for specific extension.
Maybe not, but i rather continue to make them, just in case. In the worst case, it's just my time i'm waisting making all of these builds.
For exemple, it may just be a tiny optimisation, but who knows if on functions like memcpy, the compiler it's not using different code according the instructions setting...?
...
Release_W7/XP
...
Is above compiled with default microsoft's compiler? What extensions did you use?
Yes, as said in the ReadMe, and it's archsee2.
TheFluff
13th August 2017, 23:29
For exemple, it may just be a tiny optimisation, but who knows if on functions like memcpy, the compiler it's not using different code according the instructions setting...?
Optimizing memcpy does effectively nothing for Avisynth. This has been shown empirically in the past. ultim had a bitblt (effectively memcpy for video frames) that was over 20% faster in benchmarks of only that routine, but it produced no measurable speedup in actual filter chains. Your imagination is not a functional optimization tool, so please stop trying to use it for that. You might as well try optimization by means of voodoo dolls.
All you're doing is confusing your users, who are expecting you (who is, after all, the authoritative expert on your own software) to know what you're doing.
hello_hello
14th August 2017, 01:02
I don't see the problem. If RemoveGrain peaked high on the confusion scale, the current needi3 releases would barley register. Each new release comes as a single 7z file containing an organised assortment of nnedi3.dlls. One for every occasion. Pick the one that takes your fancy based on CPU and OS and you're good to go.... links to the appropriate runtime files included..... and no pleading for an XP version required.....
AzraelNewtype
14th August 2017, 07:22
Did you miss the benchmarks above showing that if a user were to naively choose the version that actually matches their CPU, they would be lowering their performance compared to the generic one?
jpsdr
14th August 2017, 09:37
Personnaly, when i'm doing benchmark, i'm doing it from Colorbars video. At least, i'm sure i'm only benchmarking the filter, and not the codec, hdd access or anything else.
Doesn't mean it will necessary change the results, of course, but at least, you're sure of what you've benchmarked.
hello_hello
14th August 2017, 11:53
Did you miss the benchmarks above showing that if a user were to naively choose the version that actually matches their CPU, they would be lowering their performance compared to the generic one?
No, because I reasoned if there was no generic one, they'd be forced to choose the one that matches their CPU. ;)
Atak_Snajpera
14th August 2017, 12:40
To be honest generic XP version is all we need. I'm really surprised that Microsoft's compilers generates faster code than Intel's.
PS. Without resizing I get 200 fps. With single threaded Spline36Resize(1280,720) fps drops to 100 fps.
Groucho2004
14th August 2017, 15:16
I'm really surprised that Microsoft's compilers generates faster code than Intel's.
You can't generalize this. Intel's optimization algorithms are quite different from Microsoft's, it all depends on the code. Not to mention the huge number of optimization options that Intel's compiler has, it takes quite some time to find the right combination and it's not always the one you would expect, I have a couple of examples where "O1" produces faster code than "O2" or "O3".
stax76
15th August 2017, 18:37
@jpsdr
Please have a look with a freeze I get in staxrip, environment is avs+ x64, script is:
LoadPlugin("D:\Projekte\VS\VB\StaxRip\bin\Apps\Plugins\both\ffms2\ffms2.dll")
LoadPlugin("D:\Projekte\VS\VB\StaxRip\bin\Apps\Plugins\avs\JPSDR\Plugins_JPSDR.dll")
FFVideoSource("D:\Temp\StaxRip\atmos.mkv", colorspace = "YV12", \
cachefile = "D:\Temp\StaxRip\atmos_temp\atmos.ffindex")
BicubicResizeMT(1920, 1080)
It opens fine in VirtualDub x64, and if you close your editor or encoder you might not even notice, but if you close the file via VFW (which staxrip does) it freezes, you can reproduce it easily in VirtualDub, in the main menu go to: File > Close video file, you'll get a freeze.
jpsdr
16th August 2017, 09:33
Where can i get ffms2.dll ?
Edit :
Is it this one (https://github.com/FFMS/ffms2/releases) ?
jpsdr
16th August 2017, 10:21
I don't have an mkv for, so i tested with an avi file, with standard avisynth under W7 x86, can open and close the file without issue.
I can't for now, but i'll try soon later with avs+ under W7 x64 with avs+.
My test script :
FFVideoSource("SP_IVTC.avi", colorspace = "YV12", \
cachefile = "D:\Temp\SP_IVTC.ffindex")
BicubicResizeMT(1920, 1080)
Edit :
So i've tested this script under W7 x64 with avs+ and Virtualdub x64, opened it, browse a little with the slide, and close it with "File -> Close video file" without issue.
Maybe you should provide me your mkv file...? Is your ffms2 dll the same that i've used ? (i've used the one i get from the link in post above).
stax76
16th August 2017, 14:07
I've a win7 VM client for staxrip development and there it works! This could be win10 specific or worse specific to my system, can other win 10 users confirm it? I tried 3 different source filters and it don't seem to be related to the source or source filter.
jpsdr
17th August 2017, 08:26
I've just thought about it, out of curiosity, does it also happen if you add threads=1 in the filter parameters ?
Does it also happen with the 2 others filters of the plugin ? (nnedi3 and AutoYUY2).
This is to try to figure out if it's related to the MT or not, and if it's specific or global.
stax76
17th August 2017, 11:56
nnedi3, AutoYUY2 and threads = 1 work fine.
jpsdr
17th August 2017, 15:05
Euh... Do you mean that nnedi3 and AutoYUY2 works fine whatever threads is, and MTResize works fine with threads=1 ?
Or that nnedi3 and AutoYUY2 works fine also only with threads=1 ?
stax76
17th August 2017, 15:45
Sorry but I didn't think that nnedi3 has a threads parameter too, I've tried it now with threads = 4 and that's fine, problem is only with the re-sample functions.
jpsdr
17th August 2017, 17:48
Ah... Interesting...
Can you try a last thing : Disable/remove the plugin dll, and use the dll of the resample filter only. It should behave the same, but i want to be sure, just in case.
stax76
17th August 2017, 18:53
It freezes too.
jpsdr
17th August 2017, 19:18
Ok. I'll check if i see something when i have time. On the other hand, i'm also asking, if someone else with Windows 10 can make the same test.
jpsdr
20th August 2017, 11:17
@stax76
Sorry to bother you, if you can, i would like you made some few others tests.
Is just the following nnedi3_rpow2(rfactor=2,cshift="Spline36Resize") (with the source filter vfw of course) trig the issue or not ?
With resample, can you also give me the result of the following tests :
- Resample changing only horizontal size.
- Resample changing only vertical size.
stax76
20th August 2017, 14:46
In my experience there is no way around:
1. trying to reproducing a bug
2. test the other big OS regularly, with big OS I mean Win 7 and Win 10
I use wmware and it's the only reason we found out so quickly and easily that this bug is Win 10 specific. I have to test every GUI change to ensure the High DPI scaling is pixel perfect, I do it with wmware. For 2 years there were limitations for Windows 7 users due to bugs in Windows 7 not supporting Unicode in batch files, I said it's the fault of Windows 7 as excuse and it's true but I never gave up looking for a solution and finally found one and the solution was better then the previous solution anyway. Since then I made a promise to Win 7 users that I will work very hard to make them stay first class citizens for as long as possible.
I coded a lot on my new libmpv based C# player called mpv.net and need some rest, I'll make your test as soon as possible, if anybody can help out, that would be great.
jpsdr
21st August 2017, 08:30
As i don't have and will not have Windows 10, i'm first trying to figure out the "configuration" wich trig the issue, to after try to see if i can figure out it by analysing it.
Also, is this issue present since the begining, or is it after a specific release ?
stax76
21st August 2017, 12:36
At the end you might regret that you didn't try to reproduce it from the very start, been there, done that and learned it the hard way, trust me.
Is just the following nnedi3_rpow2(rfactor=2,cshift="Spline36Resize") (with the source filter vfw of course) trig the issue or not ?
I tried it in staxrip and it works, source filter as previous tests have shown don't seem to play a role.
With resample, can you also give me the result of the following tests :
- Resample changing only horizontal size.
- Resample changing only vertical size.
It freezes even if both width and height parameters are identical to the source:
BicubicResizeMT(1280, 720, prefetch = 4)
jpsdr
21st August 2017, 13:23
At the end you might regret that you didn't try to reproduce it from the very start
I agree that being able to reproduce would be the best, but as i said, i don't have and will not have Windows 10, so...
But the fact that even with identical sizes (so, doing "nothing") it freezes, it's odd... But interesting in a way. This may be a clue.
EDIT :
Interesting indeed, because this case (doing "nothing"), i can reproduce. But i have a rough idea for this case, will solve this case also solve the others, maybe, but not sure.
jpsdr
22nd August 2017, 09:37
This will be a tough one, because i'm totaly...:confused:
I see what's happening, but i don't understand why it's happenig.
When i resume my thread, with Resumethread it should be restarted, the value returned is 1, meaning the thread was sleeping but was restarted, except that... It wasn't restarted (i know because i don't "stop" at the breakpoint i've put within it), creating a lock when just after i'm waiting for the thread to exit.
So, i must said that for now, i'm a little stuck... :(
jpsdr
22nd August 2017, 12:28
For now, it seems that everything thread related i'm doing when i'm in the destructor (ResumeThread or WaitForSingleObject) is not working, or is doing nothing, which is very... annoying ! I've not been able to found informations on the net. :(
When i'm within a destructor, the ResetEvent/SetEvent works, but the WaitForSingleObject never exit, even when if the thread exited...
EDIT :
More specific, these destructors are destructors called when DLL is unloaded, this seems to be a special condition, different than a call on a destructor when the DLL is "used", in that case, it seems there is no issue... Hard...
burfadel
22nd August 2017, 12:50
Is it just the MTResize parameters that are causing the issue? I'm on Windows 10 and use the varius MT resizers like sincresizeMT and bicubicresizeMT (included in a modded Resize8 script for my own use), so multiple calls of MTResize works fine for me. Or is it just when used in nnedi3_rpow2?
jpsdr
22nd August 2017, 13:24
One issue for sure : calling resample when "doing nothing" create an issue on Windows 7 and Windows 10.
Maybe also multiple calls in a way mask the issue on Windows 10, issue maybe trigged only with a single call.
It's not specific/related to use in nnedi3_rpow2.
Atak_Snajpera
22nd August 2017, 14:12
calling resample when "doing nothing" create an issue on Windows 7 and Windows 10.
Question: What is "doing nothing" ?
jpsdr
22nd August 2017, 14:32
Calling the resampler with both width and height parameters are identical to the source => Asking him to do "nothing".
burfadel
22nd August 2017, 15:00
Something interesting. For me at least, on an interlaced source if I run NNEDI3 before my mclean script, it freezes, if I run it after the script it is fine. If I use fielddeinterlace() or any other deinterlacer before mclean it is also works fine, so not sure what's going on there? I tried adjust all of nnedi3's parameters involving threads, set fapprox to 0, opt=1 etc, no luck. However, if in the script I follow nnedi3 directly with prefetch(4), followed by mclean it works fine as well. Very improper, but it works! I believe by doing that you are separating the running of nnedi3 from the rest of the script? In any case, it's weird behaviour. Nothing in mclean is related to nnedi3, so they shouldn't be clashing. Could it be an issue with the resource sharing of nnedi3's prefetch parameter or clashing threadpool?
So for me, resizeMT works fine, but nnedi3 doesn't :). Even if I just have the source filter, nnedi3, and mclean, nnedi3() simply does work before mclean unless prefetch is stated in between. Source filter doesn't change anything either.
Does not work:
source filter here
nnedi3()
mclean()
prefetch(4)
(doesn't work whether prefetch(4) is stated or not).
Does work:
source filter here
nnedi3()
prefetch(4)
mclean()
jpsdr
22nd August 2017, 15:19
So, if i understand the whole, this only :
nnedi3()
mclean()
is not working, but just nnedi3() is working ?
Can you provide the whole scirpt with all the parameters, and the script of mclean ?
burfadel
22nd August 2017, 15:58
That's right, nnedi3() by itself works, and it works after mClean, just not before.
mClean here:
https://forum.doom9.org/showthread.php?t=174804
It's a work in progress script, but as you can see there is nothing in there really that should be causing issues. I'll try and find the exact clash in the script.
Found it! It's mDegrain2 causing it to freeze.
Try this, with just the source filter in addition, interlaced source (MPEG2 DVD for example).
nnedi3()
super = MSuper (hpad=16, vpad=16)
bvec2 = MAnalyse (super, isb = true, delta = 2)
bvec1 = MAnalyse (super, isb = true, delta = 1)
fvec1 = MAnalyse (super, isb = false, delta = 1)
fvec2 = MAnalyse (super, isb = false, delta = 2)
MDegrain2 (super, bvec1, fvec1, bvec2, fvec2)
jpsdr
22nd August 2017, 16:19
I'll try this in latter time, there is a lot of filters i need to get first...
But i must said it's odd. I don't see any reason at first glance. Another tricky issue... :(
And i'd rather continue this on nnedi3 thread.
stax76
22nd August 2017, 20:31
@jpsdr
The built you mailed me fixes the issue but an avsmeter benchmark showed poor performance, with following I'm getting 50 fps:
LoadPlugin("D:\Temp\Plugins_JPSDR\Plugins_JPSDR.dll")
LoadPlugin("D:\Projekte\VS\VB\StaxRip\bin\Apps\Plugins\avs\L-SMASH-Works\LSMASHSource.dll")
LWLibavVideoSource("D:\Temp\StaxRip\Truck.ts", format = "YUV420P8")
Spline36ResizeMT(1920, 1080, prefetch = 4)
Prefetch(4)
This gives 550 fps:
LoadPlugin("D:\Temp\Plugins_JPSDR\Plugins_JPSDR.dll")
LoadPlugin("D:\Projekte\VS\VB\StaxRip\bin\Apps\Plugins\avs\L-SMASH-Works\LSMASHSource.dll")
LWLibavVideoSource("D:\Temp\StaxRip\Truck.ts", format = "YUV420P8")
Spline36Resize(1920, 1080, prefetch = 4)
CPU usage was similar, about 80%.
jpsdr
23rd August 2017, 07:44
I'm not realy happy with what i've done, so i'll send you later another first dll, will probably fix the issue when "doing nothing", but not sure for the rest.
And then, after result of this first dll, a second where there should be no deadlock (intend to implement another thing in threadpool). But, before sending the second, i would like to know about the first.
About performance, my MT version are more for people like me who are not using external MT or are using standard 2.6 avisynth.
If you're using external MT, either your value prefecth is lower than your CPU number, in that case, in the MT version you can adjust the parameters to have (threads*prefecth = 1 to 1.5 * CPU), but if you already prefetch with your number of CPU, it would be best to have threads=1 in my versions. Meaning, in case of resampling, there is no realy interest to use it in that case.
jpsdr
27th August 2017, 12:20
New version, see first post.
Atak_Snajpera
27th August 2017, 13:50
Seems to be working fine now. So how did you fix that? Did you use some dirty workaround or applied better method.
jpsdr
28th August 2017, 08:35
The freeze for "doing nothing" has a proper fix, the freeze because of MDegrain2 messes things resulting of destructor not called anymore is more like a workaround trying to minimize damages, because honestly, i don't know what i can do in this case...
jpsdr
1st September 2017, 18:58
New version, big update, see first post.
jpsdr
7th September 2017, 19:00
New version, Desample update, see first post.
jpsdr
24th November 2017, 12:48
New version, see first post.
jpsdr
2nd December 2017, 18:29
New version, see first post.
edcrfv94
10th December 2017, 06:38
7920x 2.0.3 work fine, 2.0.2 & 1.5.8 no response, But my 3770k 2.0.3, 2.0.2, 1.5.8 all working well it.
2.0.3 "Fix a bug in the MTData" solve freeze?
jpsdr
10th December 2017, 10:33
No, solved a crash "all or nothing" case. But your results are odd... Having no response on a CPU, and working fine on another with the exact same script on the exact same video, it's odd indeed.
edcrfv94
15th December 2017, 09:37
No, solved a crash "all or nothing" case. But your results are odd... Having no response on a CPU, and working fine on another with the exact same script on the exact same video, it's odd indeed.
I find out which part cause the problem.
kf_Padding(2, 2, 2, 2) #freeze
kf_Padding(4, 4, 4, 4) #working well
function kf_Padding_test(clip c, int "left", int "top", int "right", int "bottom", bool "pspuv")
{
w = c.width()
h = c.height()
return c.pointresizeMT(w+left+right, h+top+bottom, -left, -top, w+left+right, h+top+bottom)
}
jpsdr
15th December 2017, 11:29
I've just tested with the actual version, both work for me.
I must said that i will not spend time searching why a previous version wasn't working if the actual works...;)
jpsdr
31st March 2018, 10:16
New version, see first post, and i've also added on it a part about the multi-threading.
jpsdr
3rd April 2018, 12:10
There is issue with the Intel versions.
I'll update the release files on github, removing the Intel versions, and keeping only VS version, and adding an VS AVX2 version. Wait at least 24h to check/re-download the files.
jpsdr
3rd April 2018, 20:31
Trashed Intel version, file updated, redownload it.
jpsdr
7th April 2018, 12:46
New version, see first post, updated also the Multi-treading text part.
jpsdr
1st June 2018, 10:03
New version, see first post (minor update).
jpsdr
1st June 2019, 12:06
New version, see first post.
jpsdr
7th June 2019, 11:54
New version, see first post.
About the fix to perfectly match the avs+ output. It didn't produce bad or incorrect output before, just different.
The resize works by resizing horizontal and vertical. You can choose one of your own, but there is no specific rules that says if you should do H before V or the opposite. If you resize H before V, it will produce a different result than resizing V before H (except in float mode). Difference will be very small, and both results are proper results. Sometimes my filter wasn't doing the resize in the same order, so the result was different, but still proper. Now the results perfectly match.
jpsdr
8th June 2019, 17:13
I've messed-up my files management between my VS versions, commited but not pushed, result the build was made with only some parts of the fixes (but still the output was not bad or incorrect).
If you see this post and have already downloaded the 2.2.2 version, re-download it, and sorry for the inconvenience.
jpsdr
28th April 2020, 23:38
New version, see first post.
jpsdr
22nd July 2020, 06:07
New version, see first post.
jpsdr
2nd August 2020, 08:46
New version, see first post.
jpsdr
14th February 2021, 11:25
Even if i can just "quickly" merge (without building) DTL's PR, there still the small pinterf's fix i want also include before making a new build. But as the organisation of internal resample and my filter is slighty different, i have to spend a little time to see how to put it in mine, and for now, i'm focussing all of my few spare time on something else, so there will be no new build for a while.
DTL
14th February 2021, 13:05
As suggested made temporal build of pre-2.3.2 version if anyone interested in using/testing - see later post. Still only x64 binary (with minimum SSE2 CPU I think) because win32 produces some linker error about SAFESEH with my VS2015.
jpsdr
14th February 2021, 13:56
Ok, i try to guess english from my VS french version :
Project -> Property -> Linker -> Advanced -> "Image with exception...something"/the last setting -> SAFESEH:NO
And, as history shows, a new kernel function with the small pinterf fix will just be a 2.3.2... ;)
DTL
14th February 2021, 14:43
Ok, i try to guess english from my VS french version :
Project -> Property -> Linker -> Advanced -> "Image with exception...something"/the last setting -> SAFESEH:NO
Yes - this helps.
Renamed to 2.3.2 and upload new release to fork: https://github.com/DTL2020/ResampleMT/releases/tag/2.3.2
jpsdr
21st February 2021, 17:55
New version, see first post.
real.finder
1st April 2021, 14:22
can UserDefined2ResampleMT simulate Dither_resize16 additional kernels?
also it will be nice if there are something like "MT_resize" that has all Dither_resize16 parameters so we can say goodbye to dither tools and it's lsb hacks :)
jpsdr
1st April 2021, 18:00
I don't know, DTL may answer, but the UserDefined is using bicubic if remember as core root, so it's not a totaly open function.
the UserDefined is using bicubic if remember as core root,
No - it is pure sinc-based. Its kernel is just a sum of weighted sincs:
return c*sinc(x+2) + b*sinc(x+1) + a*sinc(x) + b*sinc(x-1) + c*sinc(x-2);
Where a=1 and b and c are user-defined weights. It can be expanded to any more number of members for better precision but for typical users it is hard to work even with 2 control params.
For experienced perfectionists or some scientific work may be added expanded version for example with 4 control members (UserDefined4ResizeMT(b,c,d,e) like
return e*sinc(x+4) + d*sinc(x+3) + c*sinc(x+2) + b*sinc(x+1) + a*sinc(x) + b*sinc(x-1) + c*sinc(x-2) + d*sinc(x-3) + e*sinc(x-4);
With expand filter support and other adjustments. It may look as Bicubic just because way of passing params from script to kernel-generation function was taken from BicubicResize and names b and c looks like not very bad, though names may be changed to other simple numbering like param1, param2 etc.
As I see from wiki-description: Dither_resize16 can also accept user-defined samples for kernel generation:
"impulse" Offers the possibility to create your own kernel (useful for convolutions). Add your coefficents in the string after “impulse”, separated with spaces (ex: "impulse 1 2 1"). The number of coefficients must be odd. The curve is linearly interpolated between the provided points. You can oversample the impulse by setting kovrspl to a value > 1.
But it uses linear interpolation between provided samples. So it may be separate request to jpsdr to add this as new resampler's kernel with some new function name.
As I think Dither-tools uses separate non-Avisynth resampler with many other params of kernel adjustments/tweaking. So if you use user-provided kernel in Dither-tools with other options it may be not very easy to port its resizer to ResampleMT.
"also it will be nice if there are something like "MT_resize" that has all Dither_resize16 parameters"
I think it mostly probably will require to port full Dither-tools resampling engine to use all these tweaking and adjustments params. Though may be possible to make some class to prepare kernel (from all that params) usable with ResampleMT/Avisynth resampling engine. Anyway it may require long work.
real.finder
1st April 2021, 22:43
"also it will be nice if there are something like "MT_resize" that has all Dither_resize16 parameters"
I think it mostly probably will require to port full Dither-tools resampling engine to use all these tweaking and adjustments params. Though may be possible to make some class to prepare kernel (from all that params) usable with ResampleMT/Avisynth resampling engine. Anyway it may require long work.
yes it seems
there are vs port https://github.com/EleonoreMizo/fmtconv maybe it can help
jpsdr
30th April 2021, 15:34
New version, see first post.
jpsdr
20th July 2021, 18:07
New version, see first post.
DTL
3rd September 2021, 11:16
When trying to make a table of b,c coefficients of impulse kernel for UserDefined2ResizeMT I found something interesting:
Later I think there are many possible b and c combinations available for each power of LPF filtering and over/under shoot amplitude. Keeping 'far' ringing as small as possible. But for real practic it is good to have a table to start with. So I start to make table of b param in rows and 3 different columns for c - for no_overshoot, low, medium, and high.
But at the process of filling the table it shows no many valid combinations available for 'far' ringing as low as possible for each fixed 'b' or 'c' member.
Current table for b,c (and also a bit more precise b,c,d,e for possible UserDefined4Resize())
https://i2.imageban.ru/out/2021/09/03/2942e09c366b65e4c5e123385e62e340.png
It can be used currently with UserDefined2ResizeMT(). I send e-mail to jpsdr with suggestion to add b,c version of this table to plugin documentation but not sure it e-mail received.
As table for b,c,d,e shows the practical not-far ringing self-windowing kernel fades quick enough so even e-member typically small differs from zero. Close to +-1LSB. Ofcourse it is for 8bit presicion integer. With very high precision float required number of members may be >10. But for many practical already distorted moving pictures it may be invisible.
So is the main idea of this findings:
The practically useful combinations of b,c (b,c,d,e) is not function of many arguments but looks like function of 1 argument (like b for example). So it is possible to hard-code the b,c,d,e = f(b) table with step like 5 and linear interpolation inbetween values and make separate named resize function of only 1 user-defined argument. As well as SinPowResize(), but that kernel uses attempt of analytic definition of required 'self-windowed low ringing' kernel. And now we have partially table-defined function with sinc internal interpolation as required for resampler.
Will try to make it.
Additional note: The finding above looks like only (mostly) valid for high downsampling ratios like 10:1 and more and (may be) for sources with good conditioning against ringing. For small downsampling ratios the lowest-ringing b,c (d,e) combinations be differs from the current designed table. But it require more investigations. For example if a source with already having some ringing is feed to the resampler. So to compensate the source ringing the different weights for sincs are required. So it not mean the current UserDefined2ResampleMT() will be complete obsolete after new named resizer is designed with only 1 argument and build-in table inside.
It may be also treated as 'self-weighted' kernel. Because it is quickly fades to zero and the real resampler's windowing with 'support' natural 'box window' do not cause additional kernel cutting and no additional Gibbs-effects may be of higher order. So may be possible name for new named resize function is SelfWeightedResize or SWResize to type less in scripting. Also because it very quickly fades to zero it should be fast enough in processing because it do not require large 'support' size for resampler engine. Speed will be close to current UserDefined2ResizeMT with fixed support=2. So new resizer may have speed/quality switch like using bc or bcde table and support =2 or 4.
hello_hello
11th October 2021, 06:03
I've been trying to debug a resizing function for so long I'm pretty sure my mind has gone, so I'm not certain this is real. Could somebody please tell me if this is real?
ResampleMT 2.3.4 (and 2.3.3) XP version.
Version.GaussResize(420, 520, src_left=0.0, src_top=0.0, src_width=-300.0, src_height=0.0)
https://i.postimg.cc/DmyXq4RL/GR-300.jpg (https://postimg.cc/DmyXq4RL)
Version.GaussResizeMT(420, 520, src_left=0.0, src_top=0.0, src_width=-300.0, src_height=0.0)
https://i.postimg.cc/qhQ6Pp6N/GRMT-300.jpg (https://postimg.cc/qhQ6Pp6N)
Version.GaussResize(420, 520, src_left=0.0, src_top=0.0, src_width=300.0, src_height=0.0)
https://i.postimg.cc/dZ1ZvgG4/GR-300.jpg (https://postimg.cc/dZ1ZvgG4)
Version.GaussResizeMT(420, 520, src_left=0.0, src_top=0.0, src_width=300.0, src_height=0.0)
https://i.postimg.cc/MXPjtK84/GRMT-300.jpg (https://postimg.cc/MXPjtK84)
Edit: Trying to discover why the ResampleMT resizers appear to be behaving themselves when used with one function but not another.
Version.ExtractR().GaussResizeMT(420, 520, src_left=1.0, src_top=1.0, src_width=-300.0, src_height=0.0)
https://i.postimg.cc/5HHZkWnb/R-Plane.jpg (https://postimg.cc/5HHZkWnb)
This is also satisfactory, so maybe it's just an RGB thing?
Version.ConvertToYV12().GaussResizeMT(420, 520, src_left=1.0, src_top=1.0, src_width=-300.0, src_height=0.0)
jpsdr
22nd October 2021, 19:56
I'll try to take a look during my hollydays in november, i should have time.
You said XP version, it's specific to the XP version ?
hello_hello
23rd October 2021, 02:32
I'm mainly using an XP machine, but I checked the vanilla Win7 version (ResampleMT 2.3.4) on a laptop and it's doing the same thing.
The laptop had ResampleMT 2.2.2 on it (I don't use it for video conversion much) and it was also the same, so I guess it's an old problem that's managed to go unnoticed.
Thanks!
poisondeathray
23rd October 2021, 05:34
This is also satisfactory, so maybe it's just an RGB thing?
Version.ConvertToYV12().GaussResizeMT(420, 520, src_left=1.0, src_top=1.0, src_width=-300.0, src_height=0.0)
ConvertToPlanarRGB() also works. There was a recent thread about similar weirdness for packed vs planar RGB, I'll try to find it
hello_hello
23rd October 2021, 06:32
poisondeathray,
Yeah, it's a different thread so you probably haven't seen my post, but I mentioned in the JPSDR Avisynth's plugins pack thread (https://forum.doom9.org/showthread.php?p=1955476#post1955476) that the problem seems to be confined to RGB24/32.
VoodooFX
25th October 2021, 11:36
I gave it a go, but its use case eludes me...
Script example:
LWLibavVideoSource("D:\test.mkv")
ExtractY
SincResizeMT(2360, 244, taps=2, threads=4) # Upsize *2
Benchmarks:
AVSMeter 3.0.7.0 (x86), (c) Groucho2004, 2012-2020
AviSynth+ 3.6.2 (r3341, master, i386) (3.6.2.0)
Frames processed: 4006
SincResize(2360, 244, taps=2)
FPS: 483.0
CPU: 77.0%
SincResizeMT(2360, 244, taps=2)
FPS: 369.3
CPU: 55.1%
SincResizeMT(2360, 244, taps=2, threads=4)
FPS: 330.0
CPU: 50.3%
SincResizeMT(2360, 244, taps=2).Prefetch(4)
FPS: 41.24
CPU: 78.7%
hello_hello
25th October 2021, 16:37
Odd. I gave it a spin (on an old quadcore).
AviSynth+ 3.7.0
ResampleMT via the JPSDR Avisynth's plugins pack
Source 704x528
SincResize(1408,1056, taps=2)
FPS 238
CPU 38%
SincResizeMT(1408,1056, taps=2)
FPS 384
CPU 72%
ExtractY
SincResize(1408,1056, taps=2)
FPS 345
CPU 43%
ExtractY
SincResizeMT(1408,1056, taps=2)
FPS 522
CPU 77%
SincResize(1408,264, taps=2)
FPS 301
CPU 43%
SincResizeMT(1408,264, taps=2)
FPS 443
CPU 74%
ExtractY
SincResize(1408,264, taps=2)
FPS 415
CPU 50%
ExtractY
SincResizeMT(1408,264, taps=2)
FPS 584
CPU 81%
Edit: The help file suggests that if you're using Prefetch in a script, the ResampleMT Prefetch argument should have the same value. I didn't test with Prefetch in the script.
jpsdr
25th October 2021, 17:17
@VoodooFx read the MultiThreading.txt file provided with the plugins for using prefecth. After, sometimes things which work better for someone aren't for someone else... :(
jpsdr
25th October 2021, 21:28
I wonder if issue is not here...
result=env->Invoke(turnLeftFunction,clip).AsClip();
result=CreateResizeV(result, subrange_left, subrange_width, target_width,threads_number,_sleep,(step2)?1:range_mode,desample,accuracy,0,0,avsp, f, env);
result=env->Invoke(turnRightFunction,result).AsClip();
After turnLeftFunction, are some parameters not good...
VoodooFX
25th October 2021, 23:06
Odd. I gave it a spin (on an old quadcore).
Odd... Btw, I tried x86 "Release_W7_AVX" version.
@VoodooFx read the MultiThreading.txt file provided with the plugins for using prefecth.
I've found something :) :
ExtractY.SincResizeMT(2360, 244, taps=2, threads=2).Prefetch(2)
FPS: 401.0
CPU: 83.5%
ExtractY.SincResize(2360, 244, taps=2).Prefetch(2)
FPS: 72.56
CPU: 81.6%
jpsdr
26th October 2021, 17:31
@VoodooFx
How core do you have ?
VoodooFX
26th October 2021, 17:47
@VoodooFx
How core do you have ?
Tested on 2 real cores (4 virtual),
jpsdr
27th October 2021, 21:48
Out of curiosity, try :ExtractY.SincResizeMT(2360, 244, taps=2, threads=2, prefetch=2).Prefetch(2) and ExtractY.SincResizeMT(2360, 244, taps=2, SetAffinity=true)
VoodooFX
27th October 2021, 21:55
Out of curiosity, try :ExtractY.SincResizeMT(2360, 244, taps=2, threads=2, prefetch=2).Prefetch(2)
SincResizeMT hangs at start, no error.
ExtractY.SincResizeMT(2360, 244, taps=2, SetAffinity=true)
FPS: 397.5
CPU: 63.6%
jpsdr
28th October 2021, 17:33
Ah... hangs... That's unexpected... Another things to investigate... one day...
MysteryX
29th October 2021, 16:23
L1-L2 cache limitations, interesting.
I was having issues in VapourSynth with 5K video clips. Performance starts at 0.55fps and then drops to 0.35fps after 10 frames. Bicubic resize being at the top of the list of performance-heavy filters??
Could it be the exact CPU cache issue you're talking about here?
To test the theory, I just ran the full script with 5K vs 720p videos, and here's the output of vspipe --filter-time (top of the list of performance-heavy filters)
720p
KNLMeansCL parreq 98.82 10.09
BM3D parallel 34.93 3.56
Analyse parallel 9.82 1.00
Analyse parallel 9.39 0.96
Analyse parallel 8.87 0.91
Analyse parallel 8.87 0.91
Analyse parallel 8.86 0.90
Bicubic parallel 8.69 0.89
Bicubic parallel 5.02 0.51
Analyse parallel 4.75 0.48
Bicubic parallel 4.33 0.44
Expr parallel 3.67 0.37
Expr parallel 3.57 0.36
Expr parallel 3.52 0.36
Degrain3 parallel 3.48 0.35
Expr parallel 3.45 0.35
Bicubic parallel 3.36 0.34
bitdepth parallel 3.13 0.32
Spline36 parallel 3.02 0.31
5K
Bicubic parallel 71.76 50.94
BM3D parallel 60.19 42.73
Bicubic parallel 56.42 40.05
KNLMeansCL parreq 55.52 39.42
bitdepth parallel 46.31 32.88
Degrain3 parallel 42.70 30.32
Analyse parallel 38.09 27.04
Analyse parallel 37.40 26.55
Analyse parallel 37.18 26.39
Analyse parallel 35.70 25.34
Analyse parallel 35.10 24.92
Analyse parallel 34.07 24.18
Bicubic parallel 30.62 21.74
Super parallel 21.67 15.38
Spline36 parallel 13.47 9.56
Bicubic parallel 12.79 9.08
Recalculate parallel 11.23 7.97
Recalculate parallel 11.04 7.83
Recalculate parallel 10.37 7.36
Recalculate parallel 10.28 7.30
Recalculate parallel 10.18 7.22
Spline36 parallel 10.00 7.10
Recalculate parallel 9.69 6.88
Convolution parallel 9.41 6.68
Currently I'm in the process of porting the script to Avisynth. Is there any VapourSynth version of your solution to test it out?
DTL
29th October 2021, 17:02
I sometime get hanging at start of avsmeter tool. But it is not repeatable usually. So it is good to try start >1 attempt.
jpsdr
5th November 2021, 16:16
Hang is not because of avsmeter (unfortunately for me).
Sorry for disapointment, but investigate of issues will not be done before unknow date. I'm not in the mood for now to spend time on this, i have others stuff i want to spend my time on.
jpsdr
4th February 2022, 21:38
Hangs using prefetch fixed, will be on the next build. In fact it was a generic issue with MT_NICE filters and my threadpool when using prefetch. Also affect aWarpsharp and HDRTools and... that's all. The other filters are not MT_NICE. Fix will be included when i'll do a next build, probably not before stable release of llvm 13.0.1.
jpsdr
21st February 2022, 22:47
Packed RGB issue fixed, everything is pushed on git, expect a build maybe this WE.
FranceBB
22nd February 2022, 00:34
expect a build maybe this WE.
Thank you, as always. ;)
Looking forward to it. :)
jpsdr
23rd February 2022, 18:38
Finaly sooner than expected, new version, see first post.
tormento
1st September 2022, 20:05
Finaly sooner than expected, new version, see first post.
Please have a look at descale (https://github.com/Irrational-Encoding-Wizardry/descale/releases/tag/r8).
Is the b & c assignment of any help when downsizing to native resolution?
If positive, would you please add them?
Edit: I tried descale and it’s nice to play with them to have the wanted result.
jpsdr
2nd September 2022, 18:42
I just took a quick look, but... I think... it basicaly does what my Desampling functions are doing. Just he computes a different matrix a different way, maybe producing smaller matrix and so maybe faster.
I'll will not work on this, as i've allready done Desampling functions, which are, from my point of view (after, i may be wrong...), the same thing.
Edit
Ok, looking at the other thread, it's doing the same thing, but with a different method.
But, still not planning to add this to my projects.
jpsdr
20th November 2022, 15:10
New version, see first post.
jpsdr
26th February 2023, 12:33
New version, see first post.
DTL
27th February 2023, 00:03
Same idea as in the https://github.com/AviSynth/AviSynthPlus/issues/337#issuecomment-1443754965 - add also user control for 'support' of UserDefined2ResizeMT as it was found make significant changes to processing result. At least in the range from current 2.0 to 3. So it recommended to add one more control param 'support' or 's' and use it as 'support()' kernel class return value to resampler's program (line https://github.com/jpsdr/ResampleMT/blob/b55cb99a681ad69795c9dbdf678d9a7c601ee697/ResampleMT/resample_functions.h#L311 ). Range from todays 2.0 (or even 1.5 if user like to truncate used in resampler kernel even more) to may be some really 'big' value like 20.
Also the 'UserDefined' will be more flexible controlled by user's params input (no internally fixed 'support' param as today).
Also as AVS looks like allow to put vector of variable length as filter argument - may be make more 'universal' version of UserDefinedNResizeMT() with variable number of kernel members ? Let it be default 2 arguments as today but if user need more precise control and want to provide more kernel members - read from provided text string (count number of provided numbers and set as b,c,d,e,... members of kernel). Or may be separated named resize with kernel members list instead of b and c fixed 2 members ?
DTL
16th March 2023, 21:55
I install MFC to my home VisualStudio2019 as it looks it required now (need include file afxres.h) but still got lots of linker errors like
Error LNK2001 unresolved external symbol "void __cdecl resizer_h_avx2_generic_float<-1,7>(unsigned char *,unsigned char const *,int,int,struct ResamplingProgram *,int,int,int,unsigned char,bool)" (??$resizer_h_avx2_generic_float@$0?0$06@@YAXPEAEPEBEHHPEAUResamplingProgram@@HHHE_N@Z) ResampleMT \ResampleMT-master\ResampleMT\resample.obj 1
What may be wrong ? At work it builds with another install of VisualStudio2019 but may be with different set of components installed. May be some component of VS still missing ?
jpsdr
17th March 2023, 09:38
Ah... Sorry, no idea about the install needed part.
But... just be sure that the avx2 .cpp specific files are effectively build in the project. My .sln on github is a VS2010, so avx2 files are included in the project, but disabled in the build.
DTL
17th March 2023, 11:34
Oh - it works now. Thank you. It looks I forgot it from previous years. I sadly quickly forgot many things.
Now I found I make changes of addition s-param to UserDefined2Resize and make pull-requests to your github and to pinterf's AVS+ but forgot to make pre-build for users to test. It is made and uploaded now - https://github.com/DTL2020/ResampleMT/releases/tag/pre-2.3.8
I understand the accepting new changes may also take many months or many years but it is good to start process of testing by users some day.
The default s-param value is set to 2.2f to better differentiate it from SinPowResize (only 2.0 support by design and unlikely can be significantly changed). The UserDefined2Resize kernel as really infinitely linear can use any support value without additional distortions. Also for best 'linear' processing quality the 'large' support of much more 3 is recommended (though resampler's performance will be lower). But playing with 'low' support values around 1.8..2.5 may give additional benefits in not-true-highend workflows of limited frame sizes giving smaller (thinner) halo around transients.
Currently max s-value is limited to 15 to not allow user to set too slow processing with visually equal output. I hope it is enough with even float precision computing and currently used only 2 kernel members.
pinterf
17th March 2023, 12:55
When the changes seem to be final, I'm going to accept the pull request (along with a short and a long hint which can be put into the documentation), it is a good thing to end users to keep the original and jpsdr's MT variants in sync. To Avisynth+ core UserDefined2 is new and hasn't got history so it is easier for me.
DTL
17th March 2023, 16:49
"When the changes seem to be final"
I hope changes are final. The kernel equation become more simple (and hope more stronger in design). No more condition check with internal 'magic number'. Also the last processing param of 'support' is also user-defined now. The a-member of the kernel is =1 by design and should not be changed. Nothing more to control. It is much more programmer- and user-friendly in compare with 100+ user-controlled params of mvtools degrain now. MDegrainN may soon reach 50+ params count. Heh.
Also to make total resampling_functions.cpp program text shorter - the sinc(x) function may be global for all sinc-based kernels. UserDefined2 do not require any special sinc(). It may be equal for common mathematics of current civilization sin(x)/x (with some protection from division by zero error in practical programming). It may reuse sinc() from 'classic' SincResize() and others.
If sinc() exist in standard C-library it can be directly used.
jpsdr
26th March 2023, 13:55
New version, see first post.
jpsdr
20th November 2023, 21:54
New version, see first post, but nothing big...
tormento
30th September 2024, 14:25
I've made a plugin of the resampling functions, with internal multi-threading.
I need some clarifications about how they compare with AVS+ internal filters.
I am using SinPowerResize(720,540) to downsample Y plane in a 2:1 anime downscale:
Y = ConvertToY(matrix="Rec709").SinPowerResize(720,540)
U = UToY()
V = VToY()
YToUV(U, V, Y)
What is the difference between SinPowerResize and your SinPowResizeMT? How many taps do they apply by default? Higher is better?
And, what is the inverse function of it? DeSincResizeMT, DeSinSqrResizeMT or DeSincLin2ResizeMT?
jpsdr
30th September 2024, 19:31
What is the difference between SinPowerResize and your SinPowResizeMT?
Normaly, none. I mean, unless of a bug, the functions are identical in ouput.
But... I'm not sure, as it was a while ago, but if i remember properly, it's DLT who asked me to add these functions, so it's possible that they were first implemented in my plugin, and then afterward implemented in avs.
If that's the case, it wasn't possible for me to compare my functions with the ones in avs core (to be sure of identical output) as they didn't exist, so, in that case, maybe output is not identical...
The description is in the Readme, default p=2.5.
And, what is the inverse function of it?
The reverse of a function is the same name with "De", so reverse of SinPowResizeMT is DeSinPowResizeMT.
tormento
30th September 2024, 20:12
If that's the case, it wasn't possible for me to compare my functions with the ones in avs core
They are not, at least SinPow. AVS+ is sharper.
The reverse of a function is the same name with "De"
Why you created the "De" variant? Aren't them identical, once you use the same downscaled resolution? What does change?
jpsdr
30th September 2024, 20:43
They are not, at least SinPow. AVS+ is sharper.
In that case, it was probably like i vaguely remember. DLT asked me these functions, with some default settings.
Then, later, asked for them to be implemented in avs+ core. Either with different default settings, either with a different parameter in the function.
Code should be compared.
Or... Default settings in avs+ changed since i made the code, but i doubt... More likely the first explanation.
Why you created the "De" variant? Aren't them identical, once you use the same downscaled resolution? What does change?
IF you have an original size of X0,Y0.
If you do
xxxxResize(X1,Y1)
xxxxResize(X0,Y0)
It will not revert exactly to your orignal X0,Y0 picture.
But this:
xxxxResize(X1,Y1)
(or xxxxResizeMT(X1,Y1))
DexxxxResizeMT(X0,Y0)
will revert exactly to your orignal X0,Y0 picture.
hello_hello
30th September 2024, 21:04
They are not, at least SinPow. AVS+ is sharper.
Are you sure?
https://imgur.com/rMAST0R.png
tormento
30th September 2024, 22:15
Are you sure?
I am talking about downsizing not upsizing.
hello_hello
1st October 2024, 00:01
I am talking about downsizing not upsizing.
I did try it both ways.
https://imgur.com/itrILx0.png
tormento
1st October 2024, 16:20
I did try it both ways.
I was actually wrong. I forgot a 'c' in the function name.
They are eventually equal in image output but jpsdr's SinPowResizeMT is faster, with threads=1 in a Prefetch (2,6) script.
I have tried DeSinPowResizeMT to downsize an image but the results is awful.
FranceBB
1st October 2024, 17:39
DeSinPowResizeMT to downsize an image but the results is awful.
Each "De" version of resampling kernels is used to invert such a kernel. If the content was upscaled with Bilinear, then DeBilinear inverts it, if the content was upscaled with Bicubic, then DeBicubic inverts it and so on.
In other words, only if an image was upscaled with SinPowerResize, then you can reverse it using the "De" variant.
A few catches, though:
1) SinPower is only really used to downscale, so it's very unlikely to ever find a source that has been upscaled with such a kernel
2) Inverting a kernel isn't a normal downscale, you have to not only be sure that a content was upscaled with that resizing kernel, but also that the resolution you're bringing it back to was the one you started with.
For your use-case which is downscaling, you can go with SinPowerResize and call it a day.
tormento
6th October 2024, 10:44
@jpsdr
Can your resampling functions deal with fractional resolutions? If positive, have I to converto to floating before your resizing filter or the output is floating already?
jpsdr
7th October 2024, 18:04
They are exactly the same as the core functions. Parameters target are interger, src are float.
I don't realy understand the question about the ouput. The data depth is the same as the input, if picture data input is float, picture data output is float.
FranceBB
7th October 2024, 20:46
Can your resampling functions deal with fractional resolutions?
You can't convert to fractional resolutions, they have to be integers. If you work in yv12 then they can only be even and if you work in yv16, yv24 or rgb24 then they can also be odd.
For instance:
ColorBars(848, 480, pixel_type="yv12")
Spline64Resize(848, 478)
because you can only resize to even resolutions, but if you go to yv16, yv24 or rgb24 then you can resize to odd resolutions, so this works:
ColorBars(848, 480, pixel_type="yv16")
Spline64Resize(848, 479)
same goes for this:
ColorBars(848, 480, pixel_type="yv24")
Spline64Resize(848, 479)
and of course this:
ColorBars(848, 480, pixel_type="RGB24")
Spline64Resize(848, 479)
Note the "479" here being odd, yet working.
Anyway, encoding files with an odd resolution is a very bad behavior and I know a person or two who would try to find you and scold you if they were to get in touch with a similarly encoded file hahahahahahha
If positive, have I to converto to floating before your resizing filter or the output is floating already?
Nope, ConvertBits(32) won't save you, you can't resize with non integer values, so sticking a "ConvertBits(32)" before Spline64ResizeMT() won't automagically allow you to do something like Spline64Resize(848, 479.5).
TL;DR just resize to even pixels and encode it. ;)
DTL
8th October 2024, 18:05
@jpsdr
Can your resampling functions deal with fractional resolutions? If positive, have I to converto to floating before your resizing filter or the output is floating already?
First you need to understand the difference between:
1. Frame/canvas size
2. Image size
3. Resolution
1. Frame or digital canvas size always integer (simply number of samples)
2. Image size can be any real number in size (also the position too) and in the beast case Image size < Frame size. For example at ADC of Analog TV line (ITU Rec.601 and later) it takes 702 samples of 720 frame size. Outer samples help to encode transients from edge of line to the 'known level' and it is black level. With your fractional V image size it is better to add some padding to digital frame size to keep these transients (also you need to shape/prepare/condition these transients in a good way so any upscale resizer at display will create nice image borders without Gibbs ringing).
The 'pure digital' standards like DV and many more others with 'all-samples active image') are not completely defined at the image edges and require some hacking at the image processing to handle image edges of 'special case' (typically as some extension of last sample to half kernel size or other ways to fix edge issues).
3. Resolution is property of image data and can be from zero to max (soft of Nyquist and Gibbs limited).
Here is some simple drawing how Image can be positioned inside Frame
https://ibb.co/d23F04C
https://i.ibb.co/ncV9sMd/2024-10-08-201305.png (https://ibb.co/d23F04C)
After you prepare an Image inside Frame (Digital) you can perform a non-integer scale (and shift) to fine tune your image in digital form on digital canvas. If resizer filters do not support real number direct scale - you can use upsize to N and downsize to M integers so output will have N/M real numbered size. Any avisynth resizer should support integer-sized resize (also see chroma subsampling limitations if used).
So with AVS resizers you operate with Frame/canvas integer sized objects all the time but your Image inside this Frame can be real numbered in size and position (relative to sampling grid). Similar to any real object in the image. You can treat Image as largest possible object in the digital Frame.
If you set Image size = Frame size you lost about half samples to proper encode Image size and position in real numbers space. And force display scaling to work in harder way to make some more or less nice scaling at the edges if image. For N.x Image size you anyway need to use Frame size at least of N+1 or more integer. For less scaling aftifacts it is recommended to pad source frame enough before scaling and make scaling to output Frame size and try to Crop as many Frame samples around Image as you decide (also test with your display scalers how they will handle such prepared Image edges - simple scalers can make more halo or even ringing at poorly conditioned Image edges). In a 'perfect digital world' we need to have some 'safe area' around Image in the Frame and auto-overscan in the display device to cut-off this transient area to show only Image area limited by hardware frame of display device. Like emulating physical overscan at CRT display device. If user set zero overscan at display device it will cause some issues like black border at white areas near frame edge (or any other level of Frame edge defined). If display device also black frame over Image area it may be minor issue.
tormento
8th October 2024, 19:10
Thank you all! :)
tormento
20th October 2024, 19:50
I am going thru a very strange bug with SinPowResizeMT and lineart.
It produces really jagged lines with halo "sparks" (please, zoom the image).
Here (https://www.mediafire.com/file/cmrixa6u6rmlsno/Shangri-La+01+11151-11228.mkv/file) you can find a sample of the video.
SinPowResizeMT script:
SetMemoryMax()
SetCacheMode(0)
SetFilterMTMode("DEFAULT_MT_MODE", 2)
LoadPlugin("D:\Eseguibili\Media\DGDecNV\DGDecodeNV.dll")
DGSource("M:\Out\Shangri-La 01 11151-11228.dgi")
SinPowResizeMT(1656,932,threads=1)
Prefetch(2,6)
Frame 33:
https://i.ibb.co/JF8Q57w/Shangri-La-01-11151-11228-Sin-Pow-Resize-MT-33.png (https://ibb.co/sJcHywd)
Spline64ResizeMT script:
SetMemoryMax()
SetCacheMode(0)
SetFilterMTMode("DEFAULT_MT_MODE", 2)
LoadPlugin("D:\Eseguibili\Media\DGDecNV\DGDecodeNV.dll")
DGSource("M:\Out\Shangri-La 01 11151-11228.dgi")
Spline64ResizeMT(1656,932,threads=1)
Prefetch(2,6)
Frame 33:
https://i.ibb.co/YTMf6Dd/Shangri-La-01-11151-11228-Spline64-Resize-MT-33.png (https://ibb.co/vvGLbsd)
Any idea?
DTL
20th October 2024, 22:37
Some downsizers are not 'universal' with default control params. Try to set a higher p-param value if your source is too sharp. Like p=2.6 or even 2.7 or higher. With p > 3.0..3.2 it will be close to gauss and may be too soft. Default p may be about 2.5 and it may be too 'sharp' for very sharp sources (and low downscale ratio).
Also SinPow resize is not completely perfect and only more simple to control by the average user with simply 1 control param. If it does not work nice with your source it is recommended to try more 'linear' and somehow more complex to control UserDefined2Resize(MT) . The b and c initial params you can see at table https://forum.doom9.org/showthread.php?p=1951250#post1951250 and s-param may be about 2.0..2.2 for better 'sharp/crisp' result but if your source already too sharp you may set s to 3.0 or more to allow filter to be more 'linear'.
Downsizing is a complex task and requires user control to get better results. Also required 'look/makeup' about balance between flat/sharp/ringing or other distortions.
tormento
21st October 2024, 11:07
Some downsizers are not 'universal' with default control params.
I hoped that SinPower could be a general purpose filter and I expected haloing but not line distortion, that's why I reported.
Is it normal that a resize filter deforms lines so much?
Please notice that your link to the table is broken.
If you have some values to start with this kind of scenario, you are welcome.
DTL
21st October 2024, 13:07
The link is to post #179 in this thread. Will try to fix it.
The image hosting looks still working -
https://i2.imageban.ru/out/2021/09/03/2942e09c366b65e4c5e123385e62e340.png
It may be enough good for 'natural' sources like broadcast cameras and film scans. But some sources may cause issues. If it do not work for you with your sources and default params - you can either tweak params or try different resizer.
I still not able to download your source - will try later from different provider and at different machine. My home machine is currently bugged with some issues with browser updates and I do not have time to fix it.
tormento
21st October 2024, 14:30
The link is to post #179 in this thread. Will try to fix it.
Nice!
Are the c,d,e values the result of a formula or determined by some kind of trials and errors?
It would be nice to have a .avsi capable, given b, to ask for how much sharpness/softness we want and give the correct parameters to the other values.
DTL
21st October 2024, 15:37
They are adjusted manually via sinc-summing web-scripting application. They are recommended values but only expected to be mostly valid with infinity downscaling ratio (or at least > 10:1). With a low downscaling ratio best b and c (or c for some given b value) may be somehow different.
Possible adjusting workflow: set b value for some expected sharpness/peaking and adjust c for lowest possible ringing. Currently only the b and c 2 members version is implemented (so name is UserDefined2). If 2 members are not enough for some precise adjustment - the d and e (and any more) may be easily added. In general form it may be UserDefinedN if someone will program any number of input arguments (like string with , separated or any other separation of float numbers). But easy enough was to program 2 members as separated b and c params.
"It would be nice to have a .avsi capable, given b, to ask for how much sharpness/softness we want and give the correct parameters to the other values."
I had the same idea. But for any given source the best c (d,e,..) combination with any given b-member may be different (depending on both source and downscale ratio). Some way to adjust c (and possible other members) - feed output to waveform monitor and find some sharp enough transient and adjust c (and other members if present) to get lowest possible ringing (or some balance between sharpness and ringing). Where first b-member mostly control overall shape of transient (soft/flat/peaked).
Addition: I got your source and made tests - the Spline64Resize works about very good with this footage. About close to this is UserDefined2Resize(1656,932,b=80, c=-20, s=2.0). Differences with SincLin2Resize(width*2, height*2) testing:
1. Spline64Resize on most lines sharper (thinner lines), very few places with some ringing.
2. UserDefined2Resize - on most lines softer (thicker lines and some peaking), looks about no ringing, still some jagginess on lines present. More softer version with less residual distortions is UserDefined2Resize(1656,932,b=90, c=-12, s=3.0) (larger support and longer peaking/halo around transients).
More simple and hacky short support SinPowResize is completely looser at this specially peaked anime source - with default p=2.5 too many distortions and with p=3.2 it going into too soft.
I think users like to use more simple and sharper Spline64Resize and may be very few can see some ringing (at some places at some displays with long sinc kernel scaler). Though the real viewing experience may significantly depends also on display size and viewing distance - too thin 'peaking/halo' may going out of viewer's resolution and image with some more thicker peaking/halo may look visibly sharper (typical 'video' look/makeup). So you can test different production scalers with your own displays (and its sharpen settings) and your typical viewing distance.
DTL
21st October 2024, 21:24
For your use-case which is downscaling, you can go with SinPowerResize and call it a day.
As you see at some real use cases the more simple and short kernel of SinPowerResize may be not any good. So for broadcast purposes it may be recommended to switch from SinPowerResize to UserDefined2Resize in FFASTrans application. The UserDefined2Resize may be more 'fail safe' in such possible use cases as this anime sample. The best defaults values for b,c,s params are subject of big testing with many real footages (including such anime cases) may be with some weighting to different footages classes (real broadcast/ENG/EFP cameras, film scans, such anime if possible and may be others). As some small enough testing shows typical values may be around b=80 c=-20 s=3.0 (may be tested range of 2.0..3.0 with initial close to 2.5..3.0).
FranceBB
21st October 2024, 23:34
I think that line art as those in anime are a very edge case. Before putting SinPowerResize() in production via FFAStrans I've been testing it on a lot of footages and I manually checked several different real life footage without ever finding any issues, from penguins in ice to otters chilling on a stone on the side of a river to urban areas with buildings to trees and grass in a park to people and their faces etc. I was looking for ringing and aliasing and it never created anything as weird as the images posted above. I think that with natural footage we're fine and that's the predominant use case, but yeah, now that I know that for things like anime it doesn't work, I'll go back to using LanczosResize() or BlackmanResize() when I'll come across that kind of content. After all, they're windowed Sinc so they should be fine. I'm not gonna go with things like GaussResize() unless it's a last resort as it's far too soft. At the same time, I'll never gonna use Spline based resizers to downscale as they're known to be introducing ringing and I've seen it with my eyes back in 2013 in my first job at Crunchyroll. Anyway, I've been working for Sky for almost 9 years now, so I haven't really been encoding anime for a really long time. It's almost exclusively natural footage. As for FFAStrans, you have now given me a bit of an headache, but I guess I'm gonna have to revert to either Lanczos or Blackman by default 'cause if something like that comes up without anyone checking it and it's performing the downscale automatically in production bad things are gonna happen and there won't be anyone QCing it given that most stuff nowadays are all handled automatically. :(
DTL
22nd October 2024, 04:58
they're windowed Sinc so they should be fine.
For downscaling single windowed sinc is not enough (too narrow). So UserDefined2 is a weighted sum of several (standard sampling interval 1 sample shifted) sincs. And it is enough self-windowed with that set of weighting coefficients from table.
The kernels of Gauss, SinPow and UserDefined2 are of special 'family' of 'wide kernels' with some low-pass filtering effect required for supress Gibbs ringing. Also with adjustable width of the main lobe. Many others of single windowed sincs (Lanczos or Blackman or many many other window-named) are not so only good for upscale.
About Spline I not sure but if the graph of http://avisynth.nl/index.php/File:Spline.jpg is correct - it also have first zero at x=1 (+-1) as standard sinc so too narrow for downsize.
"it never created anything as weird as the images posted above."
The distortions may be increased in case of 1. Too sharp/peaked source + 2. Too small downscale ratio (below 2:1) + 3. Non-integer scale ratio (about 1.159:1). If you test something like UHD to FHD downscale or lower it may perform with less distortions.
You may add some more auto-logic into FFASTrans like special case of downsizing with very small ratio range like from 1.001:1 to about 1.4:1 (subject to test) and recommend some other downsize engine instead of SinPow with default p-param (also depends on input source sharpness). Or at least note (warning) to user to check output in this case.
tormento
22nd October 2024, 12:10
I can't understand the sensitivity of p parameter in SinPow: p=1-2 are the sharpest but with lot of artefacts.
In the 1-100 range of p parameter, even 4 makes the picture softer than Spline.
tormento
22nd October 2024, 13:53
Every day I understand how much I ignore.
Such as in 420 chroma is half the resolution of luma, isn't better to downscale like
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(resX,res,threads=1)
U = UToY().Spline64ResizeMT(resX*2, resY*2 ,threads=1)
V = VToY().Spline64ResizeMT(resX*2, resY*2 ,threads=1)
YToUV(U, V, Y)
And if positive, how much should I move chroma in the ConvertY line?
Another option could be
AiUpscale(Factor=1, CResample="KrigBilateral", Mode="LineArt", OutDepth=16)
but I'd like to keep things as simple and fast as possible.
DTL
22nd October 2024, 15:07
I can't understand the sensitivity of p parameter in SinPow: p=1-2 are the sharpest but with lot of artefacts.
In the 1-100 range of p parameter, even 4 makes the picture softer than Spline.
It is not aligned to 0..100 range and only usable range is very narrow float - from about 2.5 to 3.0. Where 2.5 is about max sharp and about 3.2 and more is close to Gauss.
FranceBB
22nd October 2024, 15:46
If you test something like UHD to FHD downscale or lower it may perform with less distortions.
Yeah that's probably the case. Most of the times the use cases were either UHD SDR to FULL HD SDR or FULL HD SDR to SD SDR as I have to create all the versions of the files starting from the same main asset.
You may add some more auto-logic into FFASTrans like special case of downsizing with very small ratio range like from 1.001:1 to about 1.4:1 (subject to test)
Yeah, I've been thinking about this as I was in bed, trying to sleep, and I came up with a similar approach. I actually like this and I feel like this is gonna be the way to go. :)
Or at least note (warning) to user to check output in this case.
Yep, I will definitely write something under the "?" button and in the documentation, that's for sure.
tormento
23rd October 2024, 08:04
It is not aligned to 0..100 range and only usable range is very narrow float - from about 2.5 to 3.0. Where 2.5 is about max sharp and about 3.2 and more is close to Gauss.
Ok, perhaps it's better to clarify this in the Wiki.
What about my idea of different scaling for luma/chroma?
DTL
23rd October 2024, 16:45
I do not understand your variables (also no values) and no input-output sizes. Better post example with all numbers filled and also input clip size.
I see no one is updating Wiki after new releases today. So I need to register and attempt to make edits to Resize page about 3 new resize filters added. I made registration on Wiki but still not got confirmation e-mail so can not edit Resize page. Will try to wait several days.
tormento
23rd October 2024, 18:32
I do not understand your variables (also no values) and no input-output sizes. Better post example with all numbers filled and also input clip size.
I know the syntax is not exact, it is just a fuzzy idea in my mind.
Resizing from 1920*1080 to 540p, I can go full chroma with:
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(960,540,threads=1)
U = UToY()
V = VToY()
YToUV(U, V, Y)
Let's say that I have a 1920*1080 4:2:0 source and I want to resize it to 1280*720.
Would be correct to do like this?
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(1280,720,threads=1)
U = UToY().Spline64ResizeMT(1280, 720 ,threads=1)
V = VToY().Spline64ResizeMT(1280, 720 ,threads=1)
YToUV(U, V, Y)
I'd like to avoid chroma being resized from 540p to 360p, such as in a a simple resize filter. Why to waste all the bits in the 540p chroma that I already have?
And, if correct, would I have a 444 video? Do I need to shift chroma too?
Selur
23rd October 2024, 19:25
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(720,540,threads=1)
U = UToY()
V = VToY()
YToUV(U, V, Y)
Does this work?!? This seems wrong to me.
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(720,540,threads=1) should return a 720x540 plane, this looks fine, but
U = UToY() should return 960x540, same for
V = VToY()
with 4:2:0 U and V should be half the clips width and height.
So:
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(960,540,threads=1)
U = UToY()
V = VToY()
YToUV(U, V, Y)
should work, but using 720 instead of 960 seems wrong to me.
Cu Selur
tormento
23rd October 2024, 19:42
I wrote for 1440x1080, sorry. Fixed.
DTL
23rd October 2024, 22:54
"Let's say that I have a 1920*1080 4:2:0 source and I want to resize it to 1280*720.
Would be correct to do like this?
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(1280,720,threads=1)
U = UToY().Spline64ResizeMT(1280, 720 ,threads=1)
V = VToY().Spline64ResizeMT(1280, 720 ,threads=1)
YToUV(U, V, Y)"
"would I have a 444 video?"
I think you need to set current clip to 444 format first. So the YtoUV filter can collect planes to required 4:4:4 format. Expected better:
Y = ConvertToY(matrix="Rec709").Spline64ResizeMT(1280,720,threads=1)
U = UToY().Spline64ResizeMT(1280, 720 ,threads=1)
V = VToY().Spline64ResizeMT(1280, 720 ,threads=1)
ConvertToYUV444()
YToUV(U, V, Y)
And you will get 4:4:4 sampling format but still not full possible bandwidth UV because it is only 960x540 at 4:2:0 FullHD. But it is still some better in comparison with 4:2:0 downscaling.
"Do I need to shift chroma too?"
It may depend on your input chroma assumed position. So you can check 4:4:4 result for possible chroma offset and correct it with chroma sub-shifting if required.
hello_hello
24th October 2024, 01:14
Do the ConvertToY() and ExtractY() functions give you identical output when you want the untouched Y plane from a YUV source?
ConvertToY(matrix="Rec709")
To the best of my knowledge the matrix argument does nothing unless you're converting RGB to YUV, or converting YUV to RGB.
I don't think there'd be any chroma shift if you're only resizing the Y plane, but I did find myself wondering how to correct the chroma shift when resizing from 1920x1080 to 1280x720, or to any resolution that isn't half the original width and height, and you want to resize the chroma for YUV444. Would it make more sense to convert the source to YUV444 before resizing, or would it be better to resize the luma down from 1920x1080 to 1280x720 and the chroma up from 960x540 to 1280x720?
Clip = last
Luma = Clip.Resize8(1280, 720, kernel="Spline36", kernel_c="Bicubic", chroma=false)
Chroma = Clip.Resize8(2560, 1440, kernel="Spline36", kernel_c="Bicubic", y=false)
CombinePlanes(Luma, Chroma, planes="YUV", source_planes="YUV", pixel_type="YUV444P8")
Resize8 is chroma placement aware and shouldn't cause a shift when resizing "normally", but I can't quite get my head around whether the shift would be any different when up-scaling the chroma for YUV444 using the method above.
tormento
24th October 2024, 11:56
I can't quite get my head around whether the shift would be
And that's secondo reason for my question.
The first one is: the result is different enough (i.e. resize chroma separately) to justify the few extra cpu cycles or a normal resize is more than enough? If positive, perhaps chroma "adaptation" should be hardcoded in the resize filters.
DTL
24th October 2024, 17:08
"but I did find myself wondering how to correct the chroma shift when resizing from 1920x1080 to 1280x720, or to any resolution that isn't half the original width and height, and you want to resize the chroma for YUV444. "
I think typical plane shift in integer number of samples is Crop+AddBorders and sub-sample shift is applying some resize kernel (Sinc/SincLin2 may be recommended or any Lanczos-like with enough taps) with no-integer resize and only float src_left/src_top arguments.
Though AddBorders is not very nice (in most cases where edge sample of the frame are not conditioned to some fixed code value so it can be safely repeated) and better to use some edge sample-repeating filter.
hello_hello
24th October 2024, 19:45
My understanding of how much the chroma is shifted by Avisynth's resizers for YUV420 is as follows, assuming the chroma placement is "left"....
For a 2x upscale it's a shift of one quarter of a luma pixel to the right.
For a 2x downscale it's half a luma pixel to the left.
As YUV420 chroma has half the luma resolution, shifting the chroma to the left by half a chroma pixel would shift it a full pixel left relative to the luma. That's how Resize8 calculates it. For a 2x upscale it shifts the chroma half of one quarter of a luma pixel to the left (an 8th of a chroma pixel) to compensate for the resizer shifting it to the right.
https://i.imgur.com/maVfpXK.png
Which gets me to my question....
To match what ConvertToYUV444 does, I have to shift the chroma by twice the amount I mentioned above. I can't seem to get my head around the reason though, given it's just being resized. Is ConvertToYUV444 shifting it too much?
separate=true for Resize8 bypasses the normal chroma shift correction and lets you specify the same shift for all planes, even when the chroma is sub-sampled.
What I assumed would be correct.
Clip = last # 1280x720
Luma = Clip.Resize8(1280,720, kernel="Spline36", kernel_c="bicubic", chroma=false)
Chroma = Clip.Resize8(2560,1440, 0.125,0,1280,720, kernel="Spline36", kernel_c="bicubic", y=false, separate=true)
A = CombinePlanes(Luma, Chroma, planes="YUV", source_planes="YUV", pixel_type="YUV444P8")
B = Clip.ConvertToYUV444(ChromaInPlacement="left", chromaresample="bicubic")
Compare(A.ExtractU(), B.ExtractU())
https://imgur.com/74iQphM.png
But to match ConvertToYUV444.
Clip = last # 1280x720
Luma = Clip.Resize8(1280,720, kernel="Spline36", kernel_c="bicubic", chroma=false)
Chroma = Clip.Resize8(2560,1440, 0.25,0,1280,720, kernel="Spline36", kernel_c="bicubic", y=false, separate=true)
A = CombinePlanes(Luma, Chroma, planes="YUV", source_planes="YUV", pixel_type="YUV444P8")
B = Clip.ConvertToYUV444(ChromaInPlacement="left", chromaresample="bicubic")
Compare(A.ExtractU(), B.ExtractU())
https://i.imgur.com/sgUdAi7.png
hello_hello
25th October 2024, 19:28
tormento,
for funzies I added an argument fullc (full chroma) to Resize8 if you want to play with it.
When fullc=true the output is always YUV444.
The chroma shift correction is applied accordingly, and I'm pretty sure it's the right amount, although I still don't know why ConvertToYUV444 appears to shift it more.
Link deleted. See post #253
tormento
25th October 2024, 23:11
tormento
Gonna test it ASAP.
hello_hello
26th October 2024, 15:31
After some more testing and comparing upscaling the chroma with Resize8 and AVSResize, I realized the Resize8 chroma shift correction for the test version I linked to is wrong. Only when upscaling the chroma though (when fullc=true and the source has subsampled chroma). Chroma shift correction for the standard resizing is fine. I'll upload a fixed version tonight or tomorrow.
tormento
28th October 2024, 11:46
I'll upload a fixed version tonight or tomorrow.
Just curious to know your findings. :p
Jamaika
6th November 2024, 06:56
I've made a plugin of the resampling functions, with internal multi-threading.
I can allready ear the "why" ?
The answer is "because"...:D
More seriously, i'll explain my point of view.
For multi-threading in image processing, if you have n cores, you have basicaly two ways :
Case 1 : You process n pictures in parallel.
Case 2 : You process n 1/nth part of the picture in parallel.
For me it's simple for gnu. Since the plugin has common ThreadPool::~ThreadPool() functions with avisynth then everything goes to the trash. I won't think about how to separate the thread functions since no definition has been added.
The same applies to HDRTools
Implemented new ideas about more flexible GaussResize : https://github.com/DTL2020/ResampleMT/releases/tag/pre-2.3.10
Added b, s params to GaussResizeMT
b - base value. Default 2.0, may be set to e-number 2.7 (2.71828...) for better precision of natural gaussian. Clamping range 1.5 to 3.5.
s - filter support. Default 4.0, if set to 0 - auto-support is enabled (it saves kernel down to 1% of its max value for support up to 150).
p-param limits relaxed from 0.1..100 to 0.01..100 to make larger blur if required.
Now it is possible to create large gauss blurs with auto-adjusting of support for resampler to keep all kernel working. Default support of 4.0 in old GaussResize limits working p-params to about 3..4. Now working is down to 0.01 (may require enough frame size for resampler to start with support about 100 and up to 150).
Pre-release for testing. Pull-request created.
Test script:
LoadPlugin("ResampleMT.dll")
BlankClip(100, 10, 10, color=$FFFFFF, pixel_type="YV12")
AddBorders(100,100,100,100,color=$000000)
GaussResizeMT(width, height, p=0.02, b=2.7, s=0, src_left=0.00001, src_top=0.00001)
LanczosResize(width*2, height*2, taps=10)
ConvertToRGB24(matrix="PC.601")
Better to make Animate with p from 0.02 to 20 to see the difference between auto-support (s=0) and default s=4 in old versions.
Example: old GaussResizeMT p=0.2, base 2.0, fixed support to 4.0 (zoom 400% in VirtualDub)
https://i.postimg.cc/tgxbFMj4/2025-03-09-232030.png
New GaussResizeMT p=0.2, base 2.0, auto-support (s=0)
https://i.postimg.cc/NG4c8XWm/2025-03-09-232215.png
jpsdr
25th March 2025, 14:41
The soon new posted version will not have the new resample engine.
I don't even know if i'll implement the new resample engine, because of alignement issues.
I've asked, but i'm almost sure AVS+ guarantee a higher minimum alignment than standard AVS. I don't see why pinterf in his new core code would bother checking alignment cases not hapening. Meaning that probably (i didn't check) this new code is not compatible for both standard AVS and AVS+, as it probably don't handle anymore alignment cases possible in standard AVS but not in AVS+. And as i want my plugin stay compatible with standard AVS and AVS+, it's unfortuntately highly possible that i'll don't upgrade and keep like it.
tormento
25th March 2025, 16:19
I don't even know if i'll implement the new resample engine, because of alignement issues.
That alignment issue is persecuting my thoughts since a long time.
What could be a general explanation / solution when you don't have to deal with half/double the original resolution?
@pinterf ? @anyone ?
jpsdr
25th March 2025, 18:11
The possible alignment issue has nothing to do with the resolutions...
Otherwise, new version, see first post.
StvG
26th March 2025, 00:46
The soon new posted version will not have the new resample engine.
I don't even know if i'll implement the new resample engine, because of alignement issues.
I've asked, but i'm almost sure AVS+ guarantee a higher minimum alignment than standard AVS. I don't see why pinterf in his new core code would bother checking alignment cases not hapening. Meaning that probably (i didn't check) this new code is not compatible for both standard AVS and AVS+, as it probably don't handle anymore alignment cases possible in standard AVS but not in AVS+. And as i want my plugin stay compatible with standard AVS and AVS+, it's unfortuntately highly possible that i'll don't upgrade and keep like it.
Can you write the specific reason why you want to keep the AVS compatibility? Can you write specific case/situation that AVS is doing better than AVS+? I cannot find situation where AVS+ fails but AVS is ok.
jpsdr
26th March 2025, 19:05
No specific, i just want to keep AVS compatibility for people who are still using it and using my plugins.
You're talking to someone who's still under Windows 7 (but with Openshell with Windows XP interface) because i hate the new interface, and even with Openshell Windows 10 interface is not back to my liking.
But unfortunately, i'll have no choice for the new gig i'm building... :(
jpsdr
2nd April 2025, 08:44
I will not implement the new optimized SSE/AVX2 code, but the rest: new coeff calcul and chroma, i will (or at least, i'll try).
I've pushed (but no build made) the first step of it. The new coeff calcul is implemented, but that's all, for now, the chroma is still the same as previous.
I've made only a few tests. If some people are also interested in testing and building, go ahead ! :D
Jamaika
3rd April 2025, 19:14
I've pushed (but no build made) the first step of it. The new coeff calcul is implemented, but that's all, for now, the chroma is still the same as previous.
I've made only a few tests. If some people are also interested in testing and building, go ahead ! :D
resample_functions.h:121:32: error: 'memset' was not declared in this scope
121 | if (bits_per_pixel<32) memset(pixel_coefficient,0,sizeof(short)*target_size*filter_size);
| ^~~~~~
I'm curious. Will all aligned functions be cut out in the end like in avisynth?
switch(data->f_process)
{
//case 1 : ptrClass->ResamplerLumaAlignedMT(MT_DataGF);
//break;
case 2 : ptrClass->ResamplerLumaUnalignedMT(MT_DataGF);
break;
//case 3 : ptrClass->ResamplerUChromaAlignedMT(MT_DataGF);
//break;
case 4 : ptrClass->ResamplerUChromaUnalignedMT(MT_DataGF);
break;
//case 5 : ptrClass->ResamplerVChromaAlignedMT(MT_DataGF);
//break;
case 6 : ptrClass->ResamplerVChromaUnalignedMT(MT_DataGF);
break;
//case 7 : ptrClass->ResamplerLumaAlignedMT2(MT_DataGF);
//break;
case 8 : ptrClass->ResamplerLumaUnalignedMT2(MT_DataGF);
break;
//case 9 : ptrClass->ResamplerLumaAlignedMT3(MT_DataGF);
//break;
case 10 : ptrClass->ResamplerLumaUnalignedMT3(MT_DataGF);
break;
//case 11 : ptrClass->ResamplerLumaAlignedMT4(MT_DataGF);
//break;
case 12 : ptrClass->ResamplerLumaUnalignedMT4(MT_DataGF);
break;
default : ;
}
I am interested in the output parameter color range tv {range=2}. When is it active?
jpsdr
4th April 2025, 18:10
I have no issue building with either Visual Studio 2010 or 2019, the memset issue is odd...
As i said, there will be no change in my code of the core resample function, my code is:
void FilteredResizeV::StaticThreadpoolV(void *ptr)
{
Public_MT_Data_Thread *data=(Public_MT_Data_Thread *)ptr;
FilteredResizeV *ptrClass=(FilteredResizeV *)data->pClass;
MT_Data_Info_ResampleMT *MT_DataGF=((MT_Data_Info_ResampleMT *)data->pData)+data->thread_Id;
switch(data->f_process)
{
case 1 : ptrClass->ResamplerLumaAlignedMT(MT_DataGF);
break;
case 2 : ptrClass->ResamplerLumaUnalignedMT(MT_DataGF);
break;
case 3 : ptrClass->ResamplerUChromaAlignedMT(MT_DataGF);
break;
case 4 : ptrClass->ResamplerUChromaUnalignedMT(MT_DataGF);
break;
case 5 : ptrClass->ResamplerVChromaAlignedMT(MT_DataGF);
break;
case 6 : ptrClass->ResamplerVChromaUnalignedMT(MT_DataGF);
break;
case 7 : ptrClass->ResamplerLumaAlignedMT2(MT_DataGF);
break;
case 8 : ptrClass->ResamplerLumaUnalignedMT2(MT_DataGF);
break;
case 9 : ptrClass->ResamplerLumaAlignedMT3(MT_DataGF);
break;
case 10 : ptrClass->ResamplerLumaUnalignedMT3(MT_DataGF);
break;
case 11 : ptrClass->ResamplerLumaAlignedMT4(MT_DataGF);
break;
case 12 : ptrClass->ResamplerLumaUnalignedMT4(MT_DataGF);
break;
default : ;
}
}
and will not change.
If no range is specified (default mode auto), tv is active for all YUV formats.
jpsdr
4th April 2025, 21:16
Still no build for now, but i've pushed the final step of resampler update, added keep_center and placement parameters.
Very first quick test, seems to work.
jpsdr
6th April 2025, 11:35
... It seems that i have an issue with AVX2 & AVS+, but not with AVX2 and AVS...
Argh... ! :(
As my standard PC with Visual Studio hasn't AVX2, and my default is with AVS, didn't see it in very quick test...
Up to Visual Studio 2017 we have good (full) integration of SDE from intel. So if you use VS2015 (?) or VS2017 (and may be some old ?) you can download and install intel SDE and have full AVX2 (and AVX512) simulation at about any intel (?) CPU. Simply select SDE debugger in the IDE.
jpsdr
7th April 2025, 21:59
Pushed a new version, i made more tests, i think everything is good now.
Builds comming soon.
jpsdr
9th April 2025, 18:09
New version, see first post.
DTL
10th April 2025, 14:44
With ver 2.5.1 looks edges processing is good enough. Very small difference with internal AVS+ resize:
Loadplugin("ResampleMT.dll")
Function Diff(clip src1, clip src2)
{
return Subtract(src1.ConvertBits(8),src2.ConvertBits(8)).Levels(120, 1, 255-120, 0, 255, coring=false)
}
BlankClip(100, 200, 100, color=$7F7F7F, pixel_type="YV12")
AddBorders(2, 2, 2, 2, r=2, param1=8)
pad=50
std=LanczosResize(width*2, height*2, taps=16).Subtitle("AVS+ Std 2xLanczosResize taps=16", align=5)
mt=LanczosResizeMT(width*2, height*2, taps=16).Subtitle("ResampleMT 2xLanczosResize taps=16", align=5)
d1 = Diff(mt,std)
d2 = Diff(mt,std)
StackHorizontal(StackVertical(std, mt), Stackvertical(d1, d2))
https://i.postimg.cc/wyRFphpT/image-2025-04-10-164423959.png (https://postimg.cc/wyRFphpT)
jpsdr
10th April 2025, 18:07
... In theory, there shouldn't have a difference...
I'll make some quick test, but for now, i will not spend more time on this.
DTL
10th April 2025, 21:45
SIMD resampling functions are still different. I use E7500 CPU pre-AVX. So it run some SSE SIMD for computing.
No difference only with SetMaxCPU("none").
pinterf
14th April 2025, 16:09
A small note on why the changes shouldn't be adopted immediately: The resampler code (SSEx, AVX2) is being modified slightly. The 8-bit and 10-16 bit cases differed only in a few lines, so their code was mostly duplicated. I've unified them and put them into templates. Additionally, the plain C code is being reorganized to help compiler auto-vectorization when built on non-Intel systems. Neither of these changes has been committed to the live system yet, as cleanup is needed on my side. Since there is no release date push on me, it may be finished in some weeks.
jpsdr
14th April 2025, 16:45
Thanks for this information.
Anyway, i've just updated the chroma position and coeff/border calcul.
I didn't update the resampler code (SSEx, AVX2), and honestly don't intend to, it will be too much work. There is allready an optimized code (which is good enough for me and compatible AVS & AVS+), and for me, i've implented what realy matters : chroma & coeff.
DTL
14th April 2025, 18:58
A small note on why the changes shouldn't be adopted immediately: The resampler code (SSEx, AVX2) is being modified slightly.
I see you use currently dual 256bit SIMD dataword load+processing+store for better performance at
https://github.com/AviSynth/AviSynthPlus/blob/718a1380c0f416cb014a1575627b3f1d2601e848/avs_core/filters/intel/resample_avx2.cpp#L84
But have you test more 'workunit size' for AVX2 (also at AVX512 chips) ? Like 4 of 256bit SIMD datawords in 'parallel' ? It is expected if some chips have more dispatch ports or ports with more width (like 512bit) it possibly may dispatch more operations per cycle. Or it is subject to test in future ? Also are AVX512 version expected ?
For some of used instructions _mm256_add_epi32 the Throughput is 3 results per cycle even at very old already chips:
https://i.postimg.cc/8k7bhdSS/2025-04-14-222721.png
The Latency of _mm256_madd_epi16
https://i.postimg.cc/SNWCrD1w/2025-04-14-223224.png
is 5 cycles so it output 2 results after 5 cycles delay but can output 2 results per cylcle each next cycle is sources were ready to compute. So to hide startup latency it may be also better to provide > 2 source datasets to compute.
We also have some example of larger 'workunit size' for AVX2 processing in vsTTempSmooth plugin - it loads and process 4x256 bit data for 1 loop spin: https://github.com/Asd-g/AviSynth-vsTTempSmooth/blob/97fda577d1ef7dc4aacb532ccbc51bbcdf5335d7/src/vsTTempSmooth_AVX2.cpp#L71 As I remember this make some performance boost over 1 and 2 256bit datasets.
DTL
30th April 2025, 04:59
I understand one more cache-unfriendly point of current dual-1D resampling engine for Resize() core filter and it looks like same for MT resize (it is currently filter-sequence):
For multi-plane formats it first processes all planes in a sequence for H or V resize and next processes all planes for the second dimension.
This cause trashing of the old 1D-processed by 1 of 2 required resizes planes from the cache and can decrease performance if all 3..4 planes of multi-planes formats can not be fitted in CPU cache.
So one more cache-friendly logic optimization is changing a sequence of frames resample to a sequence of planes of frame resample.
Though as current tests for AVS+ core resamplers shows they are mostly compute-bounded and not memory-bounded but after next stage of compute engines optimization the memory-performance bounding condition may raise again.
jpsdr
19th June 2025, 14:44
Checked my code, and noticed something i forgot : i use AVX2 functions only if i am with AVS+, meaning the non aligned issue of AVS... is just not possible (and that's probably for avoiding it that i made it this way). So, maybe i'll check if i can also update to the new AVX2 code.
jpsdr
22nd June 2025, 13:15
Hello.
New version, see first post.
jpsdr
23rd June 2025, 12:47
No promises, but i'll try to see what i can get from new resample_sse.c without breaking avs compatibility.
(Worst case, i still can rename sse avsp specific functions, and make avsp specific code path...).
jpsdr
26th June 2025, 19:36
No build yet, but i've pushed the use of the new resample_sse code.
Using DTL script with SetCPUMax added on the top of it, i've been able to check i think the big majority of path code. Total flat difference result, except... for the 8 bit pure "C" vertical code.
I've checked, checked, re-checked and re-checked, and i don't understand why there is a difference.
For the "C" code, i didn't use the "infernal" vectorised optimised "C", but the standard reference code left, which had just a little optimisation. So, i don't know why, but i don't have the exact same result with it than the "infernal" version for vertical 8 bits.
jpsdr
29th June 2025, 09:58
New version, see first post.
jpsdr
20th July 2025, 10:21
New version, see first post, but small change.
Can you add some way of planes processing control ? Most of the reasons and possible example how to implement without adding more params described in https://github.com/AviSynth/AviSynthPlus/issues/447 .
As I see users of executables in scripts select additional plugins for performance and script simplicity. It is more simple in script to set planes to process only in comparison with plane extracting (also may cause performance penalty and data copy ?).
This cause additional scripts dependencies and may suffer from additional plugins issues.
If GaussResize(MT) can do very frequently required simple gauss LPF/blur - users can use it instead of old plugins. But in real scripting they use simple additional control features like planes processing control. It was not natural for resizers and was not implemented in old versions of resizers.
jpsdr
1st August 2025, 17:50
I'll see... It's not a "yes", but it's not a "no" also.
Edit:
I didn't realised, but can you explain to me what "copy" means with a resampler...????
DTL
1st August 2025, 20:00
Copy mode only valid with no-resize processing (convolution with resize kernel only). Like in the usage example of QTGMCp script -
https://github.com/Dogway/Avisynth-Scripts/blob/c6a837107afbf2aeffecea182d021862e9c2fc36/MIX%20mods/QTGMC%2B.avsi#L678
vsTCanny(1.4,mode=-1,u=1,v=1) - user need to gauss-blur only Y-plane and skip processing of the UV planes completely (return allocated RAM from AVS core for YUV input format - fastest mode).
With updated version of resize it is expected like
GaussResize(MT)(width=inp_width, height=inp_height, s=0, p=some_val, force=3+planes_processing_control_flags)
Possible other use case - user need to perform some convolution with any supported kernel only for Y or UV planes and need other planes unchanged (copy mode). In scripting form it is a sequence like
1. Extract plane
2. Process required plane
3. Combine planes
But this is longer in scripting and also can cause performance degradation on more plane copies.
You can throw error if copy mode activated/requested in plane resize mode.
Third mode 'skip plane writing' may be valid with resize processing too.
Also some math optional view: copy is no-resize convolution with 1-kernel (delta-function normalized ?) (where f(x)=1 at x=0 and f(x)=0 at x!=0). Also no-resize PointResize action.
If the feature request will be implemented in AVS+ core - it still can not be complete replacement of your plugin becuase AVS+ core uses inter-frame MT and your plugin uses intra-frame MT. So depend on the use case we need either internal AVS+ core processing or your plugin (user need to test both ways and select the fastest).
The output expected to be equal but performance depend on current CPU and script environment.
jpsdr
1st August 2025, 20:31
I still don't understand...
You mean you call the resizer without shift, crop and without changing the size ??? What the point ? It will just do nothing, no ??? (I must confess i don't know, maybe i'll check in the code if i take a look at this feature).
Well... Whatever the resizer is doing, copy could be allowed only if :
All shift=0, all crop=0, and outputsize=inputsize, otherwise, only processing or output garbage would be allowed.
And using force is interesting, as there is no need to add another parameter...
DTL
1st August 2025, 21:13
"It will just do nothing, no ???"
With force=3 it will perform no-resize (but resampling as changing samples values) convolution with resize kernel. Square frequency response kernels with cut-off of Fs/2 will really 'do nothing' (for perfectly conditioned for band-limited channel source) or expose some Gibbs ringing. It also may be some use case for 'anti-ringing' processing when users get ringed version after convolution with such kernel and attempt to subtract with original to get ringing only difference. Examples of such kernels are Sinc/Lanczos/SincLin2. But if kernel make some amplification of suppression of frequencies in range of 0 to Fs/2 - it will cause other effects on image.
See example at https://forum.doom9.org/showthread.php?p=2016041#post2016041
With resize processing it change both samples count and samples values (most classic example of resampling). Because kernels for downsampling are typically have some low-pass filter action it will cause some low-pass plane buffer processing. These are kernels of GaussResize, SinPowResize, UserDefined2Resize at least. Most frequently used kernel for low-pass filtering (blurring) is Gauss.
Yes - I forgot about shift and crop. If shift (at sub-sample distance) is requested - the copy may be allowed (it will cause shift only requested planes and copy of marked to copy). It is also some known use case when user need some sub-sample luma/chroma alignment changes (change chroma placement or luma/chroma relative placement) and need to shift-process only Y or UV planes. Typical shift kernels are sinc-based like SincLin2Resize (or any other weighed sinc like Lanczos).
I do not use crop typically and can not quickly suggest if copy mode can be used with large (>1.0 sample size) crop requested.
"Whatever the resizer is doing, copy could be allowed only if :
All shift=0, all crop=0, and outputsize=inputsize, "
Also if shift (src_left and/or src_top) is in range 0.0 to 1.0f. If user need to do some sub-sample alignment of planes without changing planes size in samples. Most common use case is 'chroma placement adjustment'.
"using force is interesting, as there is no need to add another parameter..."
Yes - it is close to the 'force' parameter logic. It now control 'force (plane) process or not' and can control 'how to process'. And because it is integer param of at least 32bit value we can use some bithacks to encode some additional planes processing control as bitfields above currently used 2LSBs for 0,1,2,3 contol values encoding. We need to add control up to 4 possible planes (up to YUVA/RGBA) formats.
jpsdr
2nd August 2025, 10:40
Honestly, if i try to implement this, i will not try to figure out what crop/shift/etc... combination would allow copy, i'll make my life easy with the condition i've stated.
DTL
2nd August 2025, 21:12
It is enough to implement only working control of plane skip and plane copy and make note into documentation about being valid only in limited use cases.
DTL
3rd August 2025, 10:06
I am going thru a very strange bug with SinPowResizeMT and lineart.
It produces really jagged lines with halo "sparks" (please, zoom the image).
Any idea?
I found some issue with small resampling ratios (and odd-numbered output size) in current AVS+ resampler causing significant kernels distortion - https://github.com/AviSynth/AviSynthPlus/issues/431#issuecomment-3148256363
It may cause such resize distortions too. Will try to see what may be wrong or may be pinterf find this faster.
jpsdr
3rd August 2025, 11:04
@DTL
Hi.
I want to make some test, can you tell me what resizer and parameters to use to have a filter size of at least 100 ?
DTL
3rd August 2025, 14:42
As we see in current AVS+ sources - https://github.com/AviSynth/AviSynthPlus/issues/431#issuecomment-3148256363
Current filter size computing
AviSynthPlus/avs_core/filters/resample_functions.cpp
Line 339 in 7ae5d48
int fir_filter_size = std::max(int(std::ceil(filter_support * 2)), 1);
Where filter_support is support member of filter kernel function. It is equal to taps for sinc-based resizers. So to have filter size (sent to resampler for convolution) at least 100 you can call any sinc-based resizer with taps > 50. Lets test SincResize(taps=60) . Width and height may be about 200x200 or more.
As I remember taps param is internally limited to some low values for different sinc-based resamplers. And it is limited without throwing an error and user do not know about limitation. But for SincResize upper limit was relaxed to 150 (in your sources too https://github.com/jpsdr/ResampleMT/blob/e041491f9ecc10a2741dc03f01e4938066a396d8/ResampleMT/resample_functions.cpp#L296 ) to use it with low edge of kernel compute issue. I think filter size of 100 is about never used in real use cases.
Also in your sources GaussResize max support is 150 https://github.com/jpsdr/ResampleMT/blob/e041491f9ecc10a2741dc03f01e4938066a396d8/ResampleMT/resample_functions.cpp#L260
So you can also test GaussResize(s=60, p=0.01)
jpsdr
3rd August 2025, 16:56
Ouch...:eek:
I made some bench test and i must confess i didn't expect a so bad result... :(
Either i totaly misunderstood the horizontal intrinsic of pinterf (wich is possible because i was lost in the call of call of call... of functions) and so my asm implement is totaly wrong (even if the output result match perfectly), either intrinsic produce results a looooot beter than i tought, and i was totaly wrong (which is also possible) and my asm implement is what pinterf has done.
My asm is 2 time slower thant intrinsic... :(
I should have done speed test before making a release... But again, i must confess i didn't expect this result.
Life is full of surprises (good and bad).
DTL
3rd August 2025, 17:31
2 times slower is not very bad. In 3.7.4 H-resize is about 4+ times slower in comparison with V-resize and different variations of the design idea of H-resize may give 2..3+ times faster solution. But some implementations are limited with max supported filter size or resize ratio.
Universal solution for H-resize supporting any filter size and any input source offset for the currently processed output samples set in a SIMD pass (resize ratio) expected to be not of best performance. And partially limited solutions may be significantly faster.
Also intrinsic-based solution allow more easy increasing 'working unit' size for single SIMD pass without thinking about registers usage and also compiler can do some optimizations too. Also no need to make separate x86 and x64 .asm implementations manually.
If you look into some design tests of different H-resampler implementations at https://github.com/AviSynth/AviSynthPlus/pull/440 its performance may be different in about 5+ times depending on the design idea used and compiler and SIMD family. Also significantly depend on filter size supported by implementation.
Currently fastest in my test is resize ratios about 0.5 to infinity and filter size up to 8 using dual-512 bit source permutex for V-fma with AVX512. But it do not support downsize ratios below about 0.5 and do not support (directly) filter size above about 8..16. The design idea of this implementation is load part of source row into 2 512 bit registers and convert resampling program into permute program for AVX512 engine to gather source samples in required V-order for standard V-fma using AVX512 dual-512 input permutex instructions. Where data permutex performed only inside register file. Any gathering from other memory (based from offsets from resampling program) cause significant performance loss.
jpsdr
3rd August 2025, 17:46
I take a look, and indeed i made for horizontal something totaly different, which i think is probably a lot less memory cache efficient, which could explain the speed difference.
I think for vertical i made the same thing than pinterf's, code was a lot easier to read.
I'll try, but latter for now, to make an horizontal following what pinterf has done.
DTL
3rd August 2025, 19:19
Verticals are very SIMD friendly (though not very SDRAM friendly) and simple in design. The only possible performance optimization is adjusting 'workunit size' to fit current capacity of SIMD register file with better to use H-expansion. In 3.7.5 someone make V-expansion (pairs of rows processing) and it may be less cache/memory friendly because create more read streams with large stride. Better to create less number of read streams of longer size - see
https://github.com/DTL2020/AviSynthPlus/blob/034a47e2c91ad9d84ad24492d37a18f99b58d996/avs_core/filters/intel/resample_avx512.cpp#L1723 - first is 128 samples wide and last
https://github.com/DTL2020/AviSynthPlus/blob/034a47e2c91ad9d84ad24492d37a18f99b58d996/avs_core/filters/intel/resample_avx512.cpp#L1806 is 64 samples (bytes) wide. AVS+ have row size of mod 64bytes and it expected to be safe for read/write to the end of row.
This cause separation of row processing to several stages. First size is mod of largest fit in register file and end of row with lower lengths.
rgr
4th August 2025, 11:29
What happens to the 0-15 and 235/240-255 ranges in YUV limited mode? Are they clipped?
tormento
4th August 2025, 13:34
I take a look, and indeed i made for horizontal something totaly different
I am currently using AVX MSVC x64 version of your plugins.
Tried 3.8.0 and had to revert to 3.7.0.
Can't understand what's wrong. StaxRip crashes, VirtualDub shows black screen and AVSPmod exits with no warning or error.
My cpu is a i7-2600k with AVX support only.
My script:
SetMemoryMax()
SetFilterMTMode("DEFAULT_MT_MODE", 2)
LoadPlugin("D:\Eseguibili\Media\DGDecNV\DGDecodeNV.dll")
Import("D:\Eseguibili\Media\StaxRip\Apps\Plugins\AVS\DehaloAlpha\Dehalo_alpha.avsi")
Import("D:\Eseguibili\Media\StaxRip\Apps\Plugins\AVS\Dither\mt_xxpand_multi.avsi")
Import("D:\Eseguibili\Media\StaxRip\Apps\Plugins\AVS\FineDehalo\FineDehalo.avsi")
DGSource("M:\In\My hero academia S3 ~720p bil Dynit\21-4.dgi")
z_ConvertFormat(resample_filter="Bicubic", pixel_type="yuv420p16")
DeBilinearResizeMT(1280, 720, threads=1, prefetch=2, accuracy=2)
z_ConvertFormat(resample_filter="Spline64", pixel_type="yuv444ps")
BM3D_CUDA(sigma=12, radius=4, chroma=true, block_step=6, bm_range=12, ps_range=6)
BM3D_VAggregate(radius=4)
z_ConvertFormat(resample_filter="spline64",dither_type="error_diffusion",pixel_type="YUV420P16")
FineDehalo(rx=2, ry=2, thmi=80, thma=128, thlimi=50, thlima=100, darkstr=0.3, brightstr=1.0, showmask=0, contra=0.0, excl=true)
libplacebo_Deband(radius=14, iterations=6, temporal=false, planes=[3,3,3])
fmtc_bitdepth (bits=10,dmode=8)
Prefetch(2,6)
jpsdr
4th August 2025, 18:16
Ah...
My issue is that i have an SSE4.1, an AVX2 and AVX512 CPU, but not an AVX only. So, i can with AVX2 test the AVX2 code to be sure i didn't left any "more than AVX2" code, but unfortunately i can't do that for AVX, as i don't have an AVX only CPU...
I can test the AVX path code with SetCPUMax, but not the fact if i forgot some AVX2 code.
My guess is that i left some AVX2 code in the AVX code... :(
In the first time, i can provide you the following steps :
- Resample changing only horizontal or vertical size, this will tell if crash occurs in vertical or horizontal resizer.
- Then test the following video cases : 8bits, 10bits, 16bits, 32bits.
- Does the crash occurs also with the x86 version ?
If you don't have time or don't want to do it, absolutely no hard feelings, do the tests only if you want.
tormento
4th August 2025, 18:55
In the first time, i can provide you the following steps:
- Resample changing only horizontal or vertical size, this will tell if crash occurs in vertical or horizontal resizer.
- Then test the following video cases : 8bits, 10bits, 16bits, 32bits.
- Does the crash occurs also with the x86 version ?
If you don't have time or don't want to do it, absolutely no hard feelings, do the tests only if you want.
It seems that, in some cases, it affected 3.7.0 too but I never tried that specific parameter combination.
Used VirtualDub as AVSPmod crashes when not working, instead of giving black screen.
3.7.0
8,10,16,32 bit, H only resize → working
8 bit, V only resize → black screen
10,16,32 bit, V only resize → working
3.8.0
8,10,16,32 bit, H only resize → working
8 bit, V only resize → working
10,16,32 bit, V only resize → black screen
I don't use x86 at all.
jpsdr
4th August 2025, 20:30
Thanks.
It's specific to vertical, a quick check, there is only AVX instruction on the AVX ASM...
I didn't put AVX ASM on 3.7.0, so it was still using the original code. Even more strange.
Just for information : If you're using threads=1, prefetch has no effect, as in that case the code path is specific to not MT, and there is even no threadpool created.
tormento
4th August 2025, 20:32
Even more strange.
What is stranger is that the error pattern inverted from 3.7.0 to 3.8.0.
tormento
5th August 2025, 10:46
If you're using threads=1, prefetch has no effect, as in that case the code path is specific to not MT, and there is even no threadpool created.
My CPU is so limited that I almost see no differences, when using BM3D + x265 encoding :o
I need AVSMeter and remove BM3D to see the real impact:
SetMemoryMax()
SetFilterMTMode("DEFAULT_MT_MODE", 2)
LoadPlugin("D:\Eseguibili\Media\DGDecNV\DGDecodeNV.dll")
Import("D:\Eseguibili\Media\StaxRip\Apps\Plugins\AVS\DehaloAlpha\Dehalo_alpha.avsi")
Import("D:\Eseguibili\Media\StaxRip\Apps\Plugins\AVS\Dither\mt_xxpand_multi.avsi")
Import("D:\Eseguibili\Media\StaxRip\Apps\Plugins\AVS\FineDehalo\FineDehalo.avsi")
DGSource("M:\In\My hero academia S3 ~720p bil Dynit\NCED-3.dgi")
z_ConvertFormat(resample_filter="Bicubic", pixel_type="yuv420p16")
DeBilinearResizeMT(1280, 720, threads=1, prefetch=2, accuracy=2)
fmtc_bitdepth (bits=10,dmode=8)
Prefetch(2,6)
FPS (min | max | average): 67.15 | 159.5 | 130.9
Process memory usage (max): 690 MiB
Thread count: 19
CPU usage (average): 26.0%
DeBilinearResizeMT(1280, 720, prefetch=2, accuracy=2)
FPS (min | max | average): 67.22 | 252.5 | 206.8
Process memory usage (max): 689 MiB
Thread count: 35
CPU usage (average): 68.3%
DeBilinearResizeMT(1280, 720, threads=2, prefetch=2, accuracy=2)
FPS (min | max | average): 81.23 | 215.2 | 188.7
Process memory usage (max): 689 MiB
Thread count: 23
CPU usage (average): 41.2%
Could you explain me the real impact of threads and prefetch parameters? In the wiki it's a bit obscure.
DTL
5th August 2025, 16:09
Ah...
My issue is that i have an SSE4.1, an AVX2 and AVX512 CPU, but not an AVX only.
You can use intel SDE to emulate any SIMD CPU (up to some AVX512) and it will show unsupported instruction found.
So you can run at SSE CPU and enable AVX emulation only and it will crash if unsupported instruction happen. It also can be integrated in VS2017 (up to VS2017 as I remember).
jpsdr
5th August 2025, 18:17
If threads=1 no threadpool is created, and a code path purely without threads is used, so any parameter related to MT has no effect, otherwise, the MT process is instancied.
By default, the plugin creates one threadpool the filter will request in GetFrame function to run. Prefetch calls several times GetFrame at the same time, so, if there is only one threadpool, one GetFrame will be granted the threadpool, and the others will be waiting for the threadpool to be free. The filter will create a number of threapools indentical to the prefetch parameter, in that case, each GetFrame will always have a threadpool available when requested (theoricaly...).
The threads parameter is the number of threads created in each threadpool. Each GetFrame will process the frame splitted in threads parts.
So the total number of thread created is : prefetch x threads.
This is why if you're using prefetch, i think you should keep prefecth x threads = number of cores.
So if using prefetch, don't let the threads parameter open (or at 0), otherwise it will be by default to the number of cores.
jpsdr
8th August 2025, 18:05
@DTL
I have to try/test this (i've made for H resampler):
void resize_prepare_coeffs_align_offset(ResamplingProgram* p, IScriptEnvironment* env, const int filter_size_alignment)
{
p->filter_size_alignment = filter_size_alignment;
p->overread_possible = false;
int ByteSize;
if (p->bits_per_pixel == 32) ByteSize=4;
else
{
if (p->bits_per_pixel == 8) ByteSize=1;
else ByteSize=2;
}
const int end_pos_safe = AlignNumber(p->target_size, ALIGN_FRAME / ByteSize);
// note: filter_size_real was the max(kernel_sizes[])
const int filter_size_aligned = AlignNumber(p->filter_size_real+filter_size_alignment-1,p->filter_size_alignment);
const int target_size_aligned = AlignNumber(p->target_size, ALIGN_RESIZER_TARGET_SIZE);
// Common variables for both float and integer paths
void* new_coeff = NULL;
void* src_coeff = NULL;
size_t element_size = 0;
// allocate for a larger target_size area and nullify the coeffs.
// Even between target_size and target_size_aligned.
if (p->bits_per_pixel == 32)
{
element_size = sizeof(float);
src_coeff = p->pixel_coefficient_float;
new_coeff = (void *)_aligned_malloc(element_size*target_size_aligned*filter_size_aligned, 64);
if (new_coeff==NULL)
{
myalignedfree(new_coeff);
env->ThrowError("Could not reserve memory in a resampler.");
}
std::fill_n((float*)new_coeff, target_size_aligned * filter_size_aligned, 0.0f);
}
else
{
element_size = sizeof(short);
src_coeff = p->pixel_coefficient;
new_coeff = (void *)_aligned_malloc(element_size*target_size_aligned*filter_size_aligned, 64);
if (new_coeff==NULL)
{
myalignedfree(new_coeff);
env->ThrowError("Could not reserve memory in a resampler.");
}
memset(new_coeff, 0, element_size * target_size_aligned * filter_size_aligned);
}
const int last_line = p->source_size - 1;
// Process coefficients - common code for both types
for (int i = 0; i < p->target_size; i++)
{
const int kernel_size = p->kernel_sizes[i];
const int offset = p->pixel_offset[i];
const int last_coeff_index = offset + p->filter_size_real - 1;
const int shift_needed = last_coeff_index > last_line ? p->filter_size_real - kernel_size : 0;
const int offsetD = (offset-shift_needed) % filter_size_alignment;
const int end_pos_aligned = offset - (shift_needed+offsetD) + filter_size_aligned - 1;
const int offset2 = end_pos_aligned >= end_pos_safe ? filter_size_alignment : 0;
// Copy coefficients with appropriate shift
if (p->bits_per_pixel == 32)
{
float* dst = (float*)new_coeff + i * filter_size_aligned;
float* src = (float*)src_coeff + i * p->filter_size;
for (int j = 0; j < kernel_size; j++)
dst[j + shift_needed+offsetD+offset2] = src[j];
}
else
{
short* dst = (short*)new_coeff + i * filter_size_aligned;
short* src = (short*)src_coeff + i * p->filter_size;
for (int j = 0; j < kernel_size; j++)
dst[j + shift_needed+offsetD+offset2] = src[j];
}
// Update offsets and kernel sizes
p->pixel_offset[i] -= (shift_needed+offsetD+offset2);
p->kernel_sizes[i] += (shift_needed+offsetD+offset2);
// left side, already right padded with zero coeffs, we can
// change to actual width to the common one
if(p->kernel_sizes[i] < filter_size_aligned)
p->kernel_sizes[i] = filter_size_aligned;
}
// Fill the extra offset after target_size with fake values.
// Our aim is to have a safe, up to 8 pixels/cycle simd loop for V resizers.
// Their coeffs will be 0, so they don't count if such coeffs
// are multiplied with invalid pixels.
if (p->target_size < target_size_aligned)
{
p->kernel_sizes.resize(target_size_aligned);
p->pixel_offset.resize(target_size_aligned);
for (int i = p->target_size; i < target_size_aligned; ++i)
{
p->kernel_sizes[i] =filter_size_aligned;
p->pixel_offset[i] = 0; // 0th pixel offset makes no harm
}
}
// Free old coefficients and assign new ones
if (p->bits_per_pixel == 32)
{
myalignedfree(p->pixel_coefficient_float);
p->pixel_coefficient_float = (float*)new_coeff;
}
else
{
myalignedfree(p->pixel_coefficient);
p->pixel_coefficient = (short*)new_coeff;
}
p->filter_size = filter_size_aligned;
p->filter_size_real = filter_size_aligned; // Worst case can produce shit needing all the size.
// by now coeffs[old_filter_size][target_size] was copied and padded into coeffs[new_filter_size][target_size] with offsetx aligned to filter_size_alignment
}
DTL
8th August 2025, 21:47
" // Update offsets and kernel sizes
p->pixel_offset[i] -= (shift_needed+offsetD+offset2);"
I not sure if it valid to change offsets generated in the resampling program.
Offset for each (i) - each output sample computing convolution of source samples with FIR filter kernel is computed as:
https://github.com/DTL2020/AviSynthPlus/blob/034a47e2c91ad9d84ad24492d37a18f99b58d996/avs_core/filters/resample_functions.cpp#L390
int start_pos = (int)(pos + filter_support) - fir_filter_size + 1;
program->pixel_offset[i] = clamp(start_pos, 0, last_line);
filter_support and fir_filter_size are constants inside resampling program generation loop and for each next output sample we have advance for double-precision variable pos as
pos += src_step;
where
double src_step = crop_size / double(target_size); // Distance between source pixels for adjacent dest pixels - it is real resampling/resizing ratio in float format
So program->pixel_offset[i] advances by some values proportional to resize ratio. And it points to the start sample in the source samples buffer to read for convolution computing for each output sample. I not understand how you can align this pointer to some SIMD-friendly value (or change it in any other way) and still have the same convolution result for output.
The second input for convolution - the kernel samples sets for each output sample convolution can be aligned (and as I understand they are already aligned to attempt to help performance increase with aligned SIMD loads). These samples sets are simply structure of arrays (1D vectors) and each array/vector can be placed anywhere in the address space.
But the first input for convolution - the source samples sequence is read from the input buffer and can not be divided into sets of source samples aligned to some address to be SIMD-loading friendly. They are pointed by (random) pixel_offset[] members-pointers to the single buffer in memory.
Addition: You can make pixel_offset[] aligned by padding kernel samples with zeroes to skip non-aligned loaded source samples from convolution computing. But it may create significant performance penalty for typical small size upsampling kernels if you still keep load full set of source samples for each output. Other way possible is load aligned sources and skip source samples by shift or other shuffling in register file.
In the AVX512 (and AVX2) with programmable fetch-permutation it is possible to load aligned part of row samples once for several output samples (for upsampling at least and small downsampling ratios) and convert global row pixel_offset[] source fetching program into local part of row (currently loaded into register file) for several output samples. And this saves from several aligned/unaligned loads for each output samples. But this limited to only small downsampling and possibly all upsampling ratios. Example is https://github.com/DTL2020/AviSynthPlus/blob/034a47e2c91ad9d84ad24492d37a18f99b58d996/avs_core/filters/intel/resample_avx512.cpp#L381
jpsdr
9th August 2025, 11:26
I not sure if it valid to change offsets generated in the resampling program.
Yes, i'm just padding with zero.
You said:
p->pixel_offset[i] -= (shift_needed+offsetD+offset2);
but forgot:
dst[j + shift_needed+offsetD+offset2] = src[j];
You can't have one without the other.
Example, aligned 8:
You have, original : offset=3, FIR size = 3 -> size_filter=8
[0][1][2][3][4][5][6][7] ->pixels
[X][X][X][0][0][0][0][0] -> Coeffs
Changed to : offset=0
[0][1][2][3][4][5][6][7] ->pixels
[0][0][0][X][X][X][0][0] -> Coeffs
Indeed, it increases the FIR size of "filter_alignment", meaning it will produce one more access in the filter loop compute.
For small filter size, it changes nothing, as you always load and compute at least one packet. For exemple, on AVX512, you'll load and process at least 32 pixels, so if FIR size=3 or 18 it doesn't matter.
I allready made a quick test on my new horizontal ASM, it seems to work (meaning no garbage and no crash, that just what a "quick test" is) for 8,10,16 bits, bur for now i have a crash with my float ASM, i didn't figure out yet why.
After, when everything will be "finished", i'll made benchmark, before pushing anything this time, and i'll see.
The issue will be with too small image size with my method, because it requires that the image width (aligned with the frame alignment) is at least twice the padding, but i'll see this later.
DTL
9th August 2025, 13:39
"For small filter size, it changes nothing, as you always load and compute at least one packet. For exemple, on AVX512, you'll load and process at least 32 pixels, so if FIR size=3 or 18 it doesn't matter."
It may be for the compute engine uses H-sums of the multiplication. If you use engine with H to V transposition you can compute more output samples per loop spin/pass. And use lower filter size minimum granularity. Small size kernels are widely used for upsampling - like most of support=2 filters. They require only 4 source samples for any upsample ratio scaling. If your engine only process with minimal size of convolution for each output sample of 32 it may lost performance for many upsampling use cases.
After some time of design resampling engines I think it is good to separate at least 2 scale ratios for different engines -
1. Scale ratio about 1.0 and higher
2. Downscaling with scale ratios about 0.5..0.9 and lower.
1 and 2 uses very different number of source samples to compute output result and resampling engines can uses different ways of load H-sources and feed to FMA convolution engine for better performance in each case.
For upsample number of source samples is about equal to support*2 and typically very small and for downsample it can be much more larger (about support*2 x 1/scale_ratio).
jpsdr
9th August 2025, 14:01
Figure out my crash... In my fast rewrite of the C calling ASM, i was using program->pixel_coefficient instead of program->pixel_coefficient_float in the float data case...
Otherwise, used your compare script (finaly very usefull, thanks for it), and for now, with my aligned offset, output are identicals with core resample.
Only done x86 ASM, i'll benchmark only the intersting : x64 ASM, more effecient. I hope this time i'll have a better result... :D
Otherwise, for now, i don't intend to change method (like swapping H/V for small size FIR).
Edit:
I've also made (and tested...) an ASM unligned using normal pixel position. This way, i'll be able to benchmark both versions.
jpsdr
15th August 2025, 18:39
You can use intel SDE to emulate any SIMD CPU (up to some AVX512) and it will show unsupported instruction found.
.
I've downloaded it, i just now have to figure out how it works...
If i'm lucky, it will be easy with just a command line with few parameters to emulate a specific CPU (AVX, AVX512,...).
Edit:
It seems to be a dead end if i understood the few i've read, as i'm with VS2019 under Windows 7.
wonkey_monkey
15th August 2025, 21:22
The following:
BicubicResizeMT(1288,720, src_Left=0.00001)
doesn't produce the same result as BicubicResize. It spreads the leftmost column across the whole output (even if src_width = 1288 is specified).
That's with W7_AVX2 anyway, haven't tried the rest.
jpsdr
16th August 2025, 11:50
What's the source size ?
jpsdr
16th August 2025, 13:42
I've been able to get the last Intel SDE working with Windows 7, and lucky, it can emulate AVX-512.
But...
Despite my efforts and my research, i don't know how to debug my DLL on a program started with SDE.
What i found said to attach the process to sde.exe, not Virtualdub, i tried both, and also run directly from a command line or a VS native tools command line, no change.
Breakpoints have no effect, and when crashing, program just quit and not the usual debug code show.
Of course, as for the DLL to appears in the "Debug -> Windows -> Modules" you have VDub to start avisynth, wich loads the DLL, which occurs only when you open the script... And crash !
So, if anyone know how to debug with VDub started with SDE, i'm interested...
tormento
16th August 2025, 14:32
So, if anyone know how to debug with VDub started with SDE, i'm interested...
Caveat: I don’t know anything about programming.
As a curiosity, why don’t you use AVSmeter or AVSPmod to launch scripts?
wonkey_monkey
16th August 2025, 14:41
What's the source size ?
1288x720 (result of ColorBarsHD)
The result seems to be the same with any input and any target size, as long as src_left is specified.
I think src_width is being overwritten with the value of src_left somewhere, or the two values are getting swapped. Same with src_top and src_height.
jpsdr
16th August 2025, 14:46
Virtualdub allow to see the result, also to reload directly script (when testing).
Also, i start VDub, i attach the process in Visual Studio, and then, after, i load/start the script.
It's impossible to attach to a process with AVSMeter, because it starts the script at startup.
I don't konw AVSPmod.
jpsdr
16th August 2025, 14:52
The result seems to be the same with any input and any target size, as long as src_left is specified.
The following doesn't show differences :
Function Diff(clip src1, clip src2)
{
return Subtract(src1,src2).Levels(120, 1, 255-120, 0, 255, coring=false)
}
BlankClip(100, 200, 100, color=$7F7F7F, pixel_type="YV24")
AddBorders(2, 2, 2, 2)
pad=50
Subtitle("Resample test", align=5).convertbits(8)
std=LanczosResize(width*2, ((height/4)*8)+4+2+1, taps=16, src_left=0.0001)
mt=LanczosResizeMT(width*2, ((height/4)*8)+4+2+1, taps=16, src_left=0.0001)
d1 = Diff(mt,std)
d2 = Diff(mt,std)
StackHorizontal(StackVertical(std, mt), Stackvertical(d1, d2))
Didn't test specificaly with Bicubic yet.
wonkey_monkey
16th August 2025, 16:48
It's specific to BicubicResizeMT.
Perhaps because of the b and c parameters shifting everything out of expected index. Create_BicubicResize seems okay though...
jpsdr
16th August 2025, 18:14
It's specific to BicubicResizeMT.
In that case, there is a big change i've made a mistake in the parameters list. I'll check Create_BicubicResize next week.
wonkey_monkey
16th August 2025, 19:53
Perhaps line 3304 of resample.cpp:
args[Offset_Arg+5].AsInt(0),args[Offset_Arg+6].AsInt(1),false,0,0,args[Offset_Arg+7].AsInt(6),&args[3],&f,
should read
args[Offset_Arg+5].AsInt(0),args[Offset_Arg+6].AsInt(1),false,0,0,args[Offset_Arg+7].AsInt(6),&args[5],&f,
?
Edit: Yup, was &args[5] prior to 1b9ef36.
jpsdr
17th August 2025, 10:14
Good catch, thanks.
DTL
17th August 2025, 12:36
I've downloaded it, i just now have to figure out how it works...
It works with Win7 . But latest VS integration may be limited to VS2017 only. I do not know why intel stop support of new VS versions.
You can install it as standalone software pack (decompress files to some folder) and use from command line only
like
sde -options (path_to)virtualdub.exe
When it is integrated in VS you can select SDE debugger and debug applications as if you have emulated CPU installed.
" it will be easy with just a command line with few parameters to emulate a specific CPU (AVX, AVX512,...)."
Yes - it can do emulation in stand alone mode.
"how to debug with VDub started with SDE, i'm interested..."
You need VS2017 and install SDE with VS integration. And simply select SDE Debugger to use. For new VS I do now know how to debug with SDE. Only emulation and crash check is working.
jpsdr
18th August 2025, 08:54
Well, i was finaly indeed in just emulation and crash test mode, with the last Windows 7 compatible version (8.63). Luckily, this version already emulate AVX-512.
I finaly fix all my crash issues, and begun some tests.
On a little long filter, unaligned is a little faster than aligned. Didn't check on small filter, but difference will probably be bigger. So, aligned was a nice try, but failed.
I made a quick check of my ASM AVX-512 vs Intrinsic AVX-2, on horizontal only, and my ASM was a little slower... :(
But i checked the core vs external. The core being "core", it can be a little faster, so i have to check my actual version vs the last version of mine with intrinsic... But, again, odds are unfortunately not good. I'll see...
jpsdr
18th August 2025, 13:25
Hmm... I may have understood why if the filter size is too small, AVX-512 is slower than AVX2...
I need to finish my AVX2 asm...
tormento
18th August 2025, 13:34
Hmm... I may have understood why if the filter size is too small, AVX-512 is slower than AVX2...
I need to finish my AVX2 asm...
Please, when time is on your side, check AVX emulation too [emoji3526]
jpsdr
18th August 2025, 13:47
It's on the roadmap.
Now that i've finished rewriting the AVX512, i'm doing AVX2. And when finished AVX2, i'll do AVX. Useless for me to check AVX asm as there will be a total rewrite of the actual horizontal.
Why this order ? Because i'm using "uper code" (AVX2 for exemple) to write "lower" code (AVX for exemple).
Why AVX512 can be slower than AVX2, or AVX2 slower than AVX for horizontal ?
Quick answer :
There is 2 steps. The first step (a loop) make the multiplication pixel/coeff, the second step the sum of these results.
With small filter, the first step will loop only once, so, should you be AVX512, AVX2 or AVX, the computation time will be the same.
After this first step, you have a register with 32 values to sum in AVX512, only 16 with AVX2 and only 8 with AVX.
Summing 32 takes more time than 16 taking more time than 8.
So, in case of small loop, for exemple a filter of size 6, AVX will be faster than AVX2, also faster than AVX512.
There will be a threshold of filter size where AVX512 will be faster than AVX2, and the same with AVX2 vs AVX.
But this will be after i've finished all the code re-write.
jpsdr
18th August 2025, 19:12
I'll stop and remove/roll-back all asm, benchmark are not good, no more time waisted on this.
And so no AVX issue anymore...
FranceBB
24th August 2025, 19:34
I'll stop and remove/roll-back all asm, benchmark are not good, no more time waisted on this
Really sorry to hear that.
Still a lot of kudos for the commits, we saw how much time you spent on this and we appreciated it regardless, even if it didn't pay off. :(
jpsdr
28th August 2025, 11:16
New version, see first post.
tormento
28th August 2025, 16:36
And so no AVX issue anymore...
I tried 3.9.0 plugins and they work fine on my ancient CPU.
If you want to gain some speed, can I suggest you to give a try to Intel Compiler? Its community version is absolutely free and it has great optimizations working very well even on AMD processors.
jpsdr
28th August 2025, 18:17
My PC with VS is under Windows 7, i think Intel compiler will not work with this.
Also, I'm allready making LLVM builds, probably not far from Intel compiler.
Anyway, there is allready Intrinsics, which for now are faster than my ASM...
For now, i'll keep things the way they are.
tormento
28th August 2025, 19:08
My PC with VS is under Windows 7, i think Intel compiler will not work with this.
Also, I'm allready making LLVM builds, probably not far from Intel compiler.
Anyway, there is allready Intrinsics, which for now are faster than my ASM...
For now, i'll keep things the way they are.
On my computer Clang is slower than MSVC builds.
jpsdr
29th August 2025, 10:42
Questions for DTL, our resampler expert... :D
What do you think of JincResize vs the resamplers of the core AVS ?
And is JincResize more suitable for upscale, downscale, or both ?
It's just a question, there will not be a JincResizeMT as the code is allready multithreaded, and i understand nothing, the ++ level of the C++ is too much for me... :(
DTL
29th August 2025, 20:44
"What do you think of JincResize vs the resamplers of the core AVS ?"
I hope someone (pinterf ?) will someday implement 2D single pass resampling engine in AVS core and JincResize will be available as internal resizer. It is simply one of infinite number possible kernels for 2D single pass resampling engine. All 1D kernels possible too - simply treat 1D f(x) kernel function as rotation around 0,0 point in 2D space (use f(r) where r is raidius to current 2D point).
"And is JincResize more suitable for upscale, downscale, or both ?"
JincResize is equal to LanczosResize (from 2 pass 1D + 1D resizers) in 2D single pass resizers domain. Its kernel is jinc weighted by sinc (or jinc - the weighting function is not very critical). So it is expected as reference upsampler (with enough taps number). For downsample also equal to UserDefined2Resize possible to setup low-pass de-Gibbs downsampling kernel. Simply replace base function from sinc(x) to jinc(x) and the number of kernel members may be > 2 for best case (and thus more complex to setup and control). But simple form of 2D_UserDefined2Resize may be started with 2 members b and c.
For quality - 2D (single pass) resizers expected to be more natural for processing 2D images and expected to provide some better quality (at special cases visibly better). But in real digital imaging we do not have standard for reference image upsampler. For example is it must be dual-1D like SincResize or 2D single pass like JincResize ? So in real use cases it is recommended to test possible workflows of:
1. 2D downsize (compression) and 2D upsize (displaying, decompression). Expected in the future. Much computing required.
2. 1D+1D downsize (compression) and 1D+1D upsize (displaying, decompression). Typically widely used today. Low computing required and better performance.
The mix of 1 and 2 may give some average results. For general natural imaging there is not great difference between 1 and 2 workflows but MPEG compression may be more friendly with one of 2.
"there will not be a JincResizeMT as the code is allready multithreaded"
JincResize in Asd-g repository uses OpenMP for intra-frame MT. So it is easy to set threads task separation in your threads pool implementation. Simply cut output frame to H-stripes (or V-stripes) and set each stripe process to different threads. OpenMP do this by y-variable of the loop.
It is simple C-program. The only ++ part is AVS+ interface and some vector ++ class for resampling program generation. But it can be replaced with simple 1D or 2D C-array.
jpsdr
30th August 2025, 11:02
For now, i just made very small update in original code.
- Now improper size send an error message instead of crashing.
- AVX512 also in automatic detect.
- Add "range" parameter, same as mine in ResampleMT.
I don't know if it's relevent putting on my github...
I easely see how it can be using my threadpool, vector c++ is not realy the issue, my issue is with a lot of parts i don't understand, for now, what it's doing.
Especialy the coeff creations.
Also, i don't understand why it needs AVS+...
There is several syntax i've never seen, and don't know what they mean.
Anyway, for now, i'll stay like this. Little by little (but very little) i understand a little more what is done.
Edit:
Finaly, i'll try to do an MT version... :D
First step, full rewrite without threadpool...
Edit 2:
LOL !!!!!!
I just dig out the fact that i've allready begun to create a JincResizeMT a long time ago, i totaly forgot...
DTL
30th August 2025, 16:40
AVS is C++ .dll interface for plugin to work with AVS environment. Though as I remember C AVS interface is also possible (still working in some Asd-g plugins ?).
The resize process structure is equal to 1D (dual-1D):
1. Get kernel function f(x) for 1D or f(r) or f(x,y) for 2D
2. Create a resampling program from the resample ratio and kernel samples (from 1.). It is typically kernel samples for each output sample convolution and some service stuff to handle edge cases The most complex and performance-limited with 2D resize is that resampling program in 2D case typically pre-computed for the total output frame size. And it can take GBytes in size with not very big kernel (filter) size and not very big output size. And reading from RAM is RAM-performance limited. Second possible way is run-time creation of a resampling program for each output sample. But it also required kernel function (1.) to be computed fast enough.
For jinc resize we need bessel _j1(x) and it may be not fast to compute at runtime and may require some faster (and lower quality) approximation if possible.
For 1D resize the resampling program only computed for 1 row or 1 column and much smaller.
3. Send a resampling program to the resampling engine to make resize.
"There are several syntax i've never seen, and don't know what they mean."
It is some very new C++ text like C++17 or later. You can take an older version of JincResize with more simple C++.
Really most important question is in resize naming or some other way to mark the resize engine used. Because many kernels (may be all) can be processed in both dual-pass 1D+1D and in single-pass 2D resize engines. Some plugins to show it is single pass 2D resize add ewa_ prefix. But it does not look universal because many weighting ways are not elliptical but simply round or squared.
One possible suggestion is add 2D_ prefix. So BilinearResize executed with a 2D single pass engine will be 2D_BilinearResize(). Or simply to keep old filters, add one new param to resize filter - use dual-pass 1D + 1D resize engine or single-pass 2D resize engine.
So you can run JincResize(engine="1pass") as typical JincResize() and can run JincResize(engine="2pass") with 1D+1D resize engine (as today in AVS+ core and in your ResampleMT) with simply using jinc weighted by jinc as kernel function. It will produce a bit different result in comparison with SincResize() (or any sinc-based kernel). It decays a bit faster and has not completely equal distance between zeroes.
And with this way users can run any other old resize kernel with a 2D single pass resize engine like LanczosResize(engine="1pass"). For single-sample excitation and upsampling it will produce significantly different output:
1D+1D is checker-board like pattern
2D is nice round circles from rotation of the sinc kernel around the input sample in 2D plane.
The UserDefined2Resize() will also run somehow not bad but for better result with 1pass resize mode it is better to replace sinc() base function to jinc() in https://github.com/jpsdr/ResampleMT/blob/3c604eba81d9c550933c7f5fc9d34bcd6aad6e77/ResampleMT/resample_functions.cpp#L370 . It may be done automatically if the user requests this resize to be dispatched by 2D 1pass resize engine. So no more control params are required.
Difference example between dual-1D and single-2D resampling engines result with close looking ringing kernels (sinc and jinc):
LoadPlugin("JincResize.dll")
BlankClip(200,1,1, pixel_type="YV24", color_yuv=$D07f7f)
AddBorders(21,21,21,21, color_yuv=$307f7f)
dual1D=SincLin2Resize(width*10, height*10).Subtitle("sinc dual-1D")
single2D=JincResize(width*10, height*10, tap=16).Subtitle("jinc single-2D")
StackHorizontal(dual1D, single2D)
https://i.postimg.cc/2jZk23tT/2025-08-31-110140.png
Dual-1D processing causes 'secondary-ringing' from primary ringed 1D upsampled result when processing in second dimension. This ends in sort of checker-board pattern. Single-2D processing outputs kernel shape in 2D form non-distorted as expected.
jpsdr
31st August 2025, 12:41
I will not create another thread, this one is still good for also talking of JincResize, we are still in the resampler world.
I've created my repo Git. What i've pushed is still a lot WIP, but it builds, it produces an output and works with AVS 2.6, it's not crashing, but... The output is blury as hell, so i have to figure out why... as i don't have this blur with the build i've made from the true original...
Otherwise, with your output now i see the difference between the two methods... If i would have to choose, for me, single-2D over dual-1D, for this specific case.
DTL
31st August 2025, 15:32
The performance difference is great:
At i5-9600K CPU with Prefetch(6) and latest build of JincResize from Asd-g and AVS+ 3.7.4:
JincResize(tap=16) - 9.8 fps 99% CPU load
LanczosResize(taps=16) - 1452 fps 16% CPU load. About 150 times faster.
"is blury as hell, so i have to figure out why..."
It may be issue with kernel scaling in resampling program generation or in the kernel source function. Like too wide kernel causing too much low-pass filtering.
I think you can integrate single pass 2D resampling engine into ResampleMT plugin and use (share) single kernel sources for 2 different resampling engines for user to select. So all old/classic resize kernels can be processed by different engines if required.
The only new addition is jinc kernel function and new 2D resampling program generator and (single) 2D resampling engine (C and SIMD versions). It even more simple in comparison with dual-1D resize where we need to make and run 2 different H and V resample engines.
jpsdr
31st August 2025, 16:04
Before running (integrate a 2D engine), begin to walk properly (having a not multi-threaded, simple code C of plugin working).
I've made a build (with just a very few tweaks) of Asd-g version.
Then i've made a build of my starting version.
Calling with the exact same input with the same parameters, mine is a lot blurry, so, obviously, i made a mistake somewhere when porting for my own version.
Also i had only tested an x86 version under AVS 2.6, i've tested an x64 build, and it crashes. Odd... There is no ASM and so no differences should occur between x86 and x64, another thing to check...
DTL
31st August 2025, 16:13
x64 typically more stable (and only can run) with medium ouput frame size and medium taps number (filter size) because it can allocate > 2..3 GBs of (contigous virtual) RAM for coeffs table. Though if RAM allocation fail it expected to throw allocation error instead of some exception (like unknown C++ or crash with zero pointer).
Better start testing with small output frame size like 400x400 and taps about 8.
x86 version sometime can fail even with 512 MBs allocation (because it is single buffer (vector ?) allocation and must have contigous RAM virtual addresses and if process virtual address space is already fragmeted the allocation can fail with very low size below 1 GB even).
StvG
31st August 2025, 16:55
The performance difference is great:
At i5-9600K CPU with Prefetch(6) and latest build of JincResize from Asd-g and AVS+ 3.7.4:
JincResize(tap=16) - 9.8 fps 99% CPU load
LanczosResize(taps=16) - 1452 fps 16% CPU load. About 150 times faster.
JincResize(tap=16) (default threads=0) and prefretch(6)? Try JincResize(tap=16, threads=1) when you're using prefetch.
jpsdr
31st August 2025, 17:12
Test is made with small pitcure, of course... :D
Both blur and crash issues come from the same source, the llround computation index. Short version, don't have llround, so replaced with floor(x+0.5), but doing this, as the original code has some kind of "i don't understand" magic_round, it was now producing bad index in x86 (-> Blur), and negative index in x64 (crash).
Removed the magic_round, and now i have the same output in both x86 and x64.
It's a big first step.
DTL
31st August 2025, 17:58
JincResize(tap=16) (default threads=0) and prefretch(6)? Try JincResize(tap=16, threads=1) when you're using prefetch.
Yes - I forgot about internal intra-frame stripe-based OpenMP mulththreading. But it not helps any:
JincResize(tap=16, threads=1)
5.5 fps (single threaded performance)
And both AVS+ MT and internal OpenMP MT gives only about 9.5fps. It looks the RAM performance for reading large 2D coeffs table limits performance greatly.
Some strange note for building: Building need at least Visual Studio 2019 for AVX512.
At least VS2017 support intrinsincs of AVX512 (and the SDE integration work with AVX512 too as expected). The only difference may be with compiler control - in VS2017 if user want to force C compiler to use AVX512 instructions for C-part of program the manual command line switch /arch:AVX512 can be added. Not included in drop-down menu in GUI. But for manually written intrinsics it expected to work with default settings.
JincResize resampling engines uses manually written intrinsincs for AVX512 so expected to work with VS2017 too. If user expect some more optimization - the manual command line switch /arch:AVX512 may be added but it will cause full executable to have any AVX512 instruction in any place and not controlled by CPU selection and can be executed only at AVX512 capable chip.
https://devblogs.microsoft.com/cppblog/microsoft-visual-studio-2017-supports-intel-avx-512/
Microsoft Visual Studio 2017 supports Intel AVX-512, and with Visual Studio 2017 version 15.3 we’re enhancing that support to include more Intel AVX-512 instructions than ever before. We implemented over 1500 Intel AVX-512 intrinsic functions in Microsoft Visual C++ for Visual Studio 2017 version 15.3, and we have more to do. The available functions are mostly for 512-bit vectors or floating-point scalar values. We plan to add more functions for 256-bit and 128-bit vectors and floating-point scalars in an upcoming release, which will more than double the number of AVX-512 functions available. There are also many additional optimizations for the new AVX-512 features that we are planning to roll out over several releases.
About quant_x,y and blur params - I do not know what are they for and changing from default values causes more or less severe distortion of the jinc kernel. For simplified version of resize I think you can skip these params and use pure jinc kernel (with its implemented weighting).
At some old versions blur param was non-1.0 default (and scary extra precision value like 0.9812505644269356) https://github.com/Kiyamou/VapourSynth-JincResize/blob/master/README.md . But for latest version looks like this modification was disabled (blur=1.0).
Unfortunately the sources are significantly messy in kernel and resampling program generation. Looks like initial design was at times with no required bessel C-library function _j1(x) available and have its own implementation of bessel j(x) and this makes sources more complex. In latest C compilers (language math libraries ?) we finally have bessel j(x) implementation and can call it simply without manual computing and this makes kernel as simple as sinc(x) - see example in second branch
https://github.com/Asd-g/AviSynth-JincResize/blob/b7fbf5d680a2950dff65b907134e6719efd11916/src/JincResize.cpp#L916
The expected idea of importance of the jinc kernel function for resampling - it is (2D) impulse responce of ideal round (2D) imaging device (ideal round lens). https://en.wikipedia.org/wiki/Airy_disk
For most of natural non-coherent light sources it is squared jinc and as I remember for coherent light it is not squared. No any more magic numbers and transforms required.
Also this hints - we can check squared jinc as version of resize kernel too (it may make more blurry but better simulate imaging from natural non-coherent light).
So at least for main production operation downsampling usage of jinc and 2D resampling engine expected to be good simulation of creation of natural image from infinity resolution source by emulated ideal (diffraction-limited) lens. By low-pass filtering (convolution) with its 2D impulse responce that is desicribed by jinc function (or squared jinc as option).
jpsdr
1st September 2025, 18:35
Out of curiosity, i've tested on my Broadwell CPU the build i've made from the original JincResize (so with OpenMP), with threads=0 and threads=1.
720x480 -> 775x583 : 196.9 fps / 40.01 fps
3480x2160 -> 775x583 : 13.90 fps / 2.646 fps
720x480 -> 3480x2160 : 25.25 fps / 2.645 fps
StvG
1st September 2025, 18:44
From 2.1.0 (https://github.com/Asd-g/AviSynth-JincResize/blob/master/CHANGELOG.md#210) the parallel execution isn't done with OpenMP. What version of the code you use for your fork?
jpsdr
1st September 2025, 20:33
The 2.1.4 (https://github.com/Asd-g/AviSynth-JincResize.git).
It's done with a class called "execution" (from the few i understood), i don't know if it's OpenMP or not, DTL said it was OpenMP, so i assumed it was.
DTL
1st September 2025, 22:20
Initial internal MT was designed with OpenMP. It is simple
#pragma omp parallel for num_threads(threads_)
before y-loop. See example https://github.com/Asd-g/AviSynth-JincResize/blob/b7fbf5d680a2950dff65b907134e6719efd11916/src/KernelRow_avx2.cpp#L20
But in latest versions Asd-g redesign it for some other auto-MT may be implemented in new C++.
With your better MT performance results - may be you use small kernel (low taps). I use JincResize(width*10, height*10, tap=16) (from the script above for kernel image footprint, post https://forum.doom9.org/showthread.php?p=2022230#post2022230).
jpsdr
2nd September 2025, 13:18
@DTL
Your width & height is very small, so the memory allocated for coeff is small.
PM me your email, i'll send you my build if you want.
DTL
3rd September 2025, 20:14
Allocated memory for 2D resampling program is about output size multiplied to kernel size for each output sample (2D filter size). For tap=16 filter size expected about 32x32 floats = 1024 x 4 bytes - about 4 kbyte (if coefs in 32bit floats).
Input size is about 44x44 and 10x output is 440x440. It is 193 600 samples and 4096 bytes filter for each = 800 MBs resampling program coefs size. It is much larger in comparison with CPU cache size. Before resampling program reusage for equal planes in older versions the memory usage was even worse - the separate resampling program for each plane of 4:4:4 format and RAM usage was about 3x800 MBs.
Script:
LoadPlugin("JincResize.dll")
BlankClip(20000,1,1, pixel_type="YV24", color_yuv=$D07f7f)
AddBorders(21,21,21,21, color_yuv=$307f7f)
dual1D=LanczosResize(width*10, height*10, taps=16).Subtitle("sinc dual-1D")
single2D=JincResize(width*10, height*10, tap=16).Subtitle("jinc single-2D")
return single2D
AVSmeter report close:
Number of frames: 20000
Length (hh:mm:ss.ms): 00:13:53.333
Frame width: 430
Frame height: 430
Framerate: 24.000 (24/1)
Colorspace: YV24
Frames processed: 105 (0 - 104)
FPS (min | max | average): 7.236 | 10.12 | 9.655
Process memory usage (max): 1062 MiB
Thread count: 16
CPU usage (average): 90.8%
At i5-9600K CPU of 6 cores.
With threads=1
FPS (cur | min | max | avg): 5.451 | 4.515 | 5.764 | 5.469
Process memory usage: 1062 MiB
Thread count: 10
CPU usage (current | average): 16.2% | 15.1%
To process 3 planes of YV24 in current implementation CPU must read coefs from RAM 3 times so 800x3=2.4 GBytes read per frame for resampling program only. And with 10fps it cause RAM read about 24 GBytes per second. May be close to the performance of this old host.
What I remember of data locality optimization:
Make process all equal sized planes with same resampling program read once. It is for RGB and RGBA formats read coefs once for 3 and 4 planes instead of re-read coefs for each plane 3 or 4 times. For 4:2:2 and 4:2:0 formats benefit is lower - only UV planes of smaller size can be combined.
So we can need to add several more resampling functions in addition to single plane resize_plane_(SIMDfamily)(). They are resize_2planes(), resize_3planes() and resize_4planes().
They are also simple but we send several planes in and out pointers and strides and single resampling program input pointer. Inside it is interleaved processing of several planes with single resampling program coefs load for each output sample for each plane.
This looks like working finally as expected - see pull request as example https://github.com/Asd-g/AviSynth-JincResize/pull/21
Test build https://github.com/DTL2020/AviSynth-JincResize/releases/tag/post_2.1.4_1
for script
LoadPlugin("JincResize.dll")
ColorBarsHD(100,100)
ConvertToYUV444()
JincResize(width*10, height*10, tap=16)
About the same performance boost for downsample:
LoadPlugin("JincResize.dll")
ColorBarsHD(1000,1000)
ConvertToYUV444()
JincResize(width/10, height/10, tap=16)
As I see the performance of 2D resampling with pre-computed resampling program is hardly limited by host RAM performance. And no computing optimizations are useful. The next optimization idea still attempt to compute resampling program at runtime at SIMD unit. But it will be greatly kernel-function dependant and will require to create separate 2D resampling function for each kernel function (though some like bicubic and bilinear and possibly gauss are simple enough).
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.
jpsdr
3rd October 2025, 09:04
Yes, 32 zmm registers is only in x64, it's 8 in x86. ymm is 16 in x64 and 8 in x86 for AVX2, but if i remember properly, you have 32 xmm and ymm in AVX512.
It's logical because xmm/ymm/zmm are in fact only one register. Bits 0..127 of zmm0 or ymm0 are xmm0, bits 0..255 of zmm0 are ymm0.
The other thing, is that in x64 you have to save "only" xmm6 to xmm15. If you're using xmm16 or more, you don't need to save them.
Meaning the dual row vs single row is probably negative for x86 builds, but positive for x64 builds.
This is why i have, in my failed ASM resample attempt, customized the ASM according x86 or x64.
But, in our days, most of people are in x64.
It's easy in ASM to use the 32 registers, you just "have to", i don't know how intrinsics behave.
For AVX512/AVX2, i'll have to check again speed with large filters, but if i remember properly i've allready tried, even with large filters, AVX512 was slower (at least on my CPU).
If there is realy such a threshold, maybe in the init filter, if opt=-1, it can estimate the filter size and choose between AVX512/AVX2 if AVX512 is detected. But this is for a second time.
DTL
3rd October 2025, 10:40
Intrinsics guide shows we have 128/256/512bit wide instructions in AVX512 group. https://www.laruence.com/sse/#techs=AVX_512&text=cvtph_ps . For AVX512 and non-512 datawords (256 and 128 bits) they are 'masked'. But it is easy to init all-bits (static const) mask (in separate from 32 main 8 additional mask registers in AVX512 architecture). It looks the way to mark these instructions for AVX512 architecture with lower data word size.
https://i.ibb.co/JwhsbSgv/image-2025-10-03-124014626.png (https://ibb.co/rRWwLXNx)
I expect all other required instructions also have 'masked' AVX512 form with 256bit data word size.
Though it is just an idea to test. May be other ways exist to force C compiler to use 32 registers in AVX512 x64 mode for 256bit data word size. If it will over-read 16 coeffs (8 next) from coeff_stride=8 buffer we can skip upper half of register at the final operations. It will not degrade performance. Though buffer memory must be still allocated to allow mod16 stride loads without causing page protection errors at the last buffer row reads. If missed this can cause rare and random memory read errors/crashes.
The key idea to use coeffs_stride=8 for filter_size <=8 and process it with 32 registers AVX512 architecture.
jpsdr
3rd October 2025, 14:40
mmask is totaly different, it allows to apply the instruction only on parts of registers. For example, an and, with the mask you can say something : apply on bits 0..15, but leaves unchanged bit 16..31, apply to bits 32..63, etc... Using mmask is also very slow (according Intel doc) vs not using it.
It's a guess, but likely, for compiler, just enabling AVX512 features will grant access to the 32 registers in x64 mode. I've made an 2 rows x4 AVX2 code put in avx512 cpp, i'll push when back home, but test has to wait, not time right now.
DTL
3rd October 2025, 15:02
"2 rows x4 AVX2 code put in avx512 cpp"
If it will work with coef_stride=8 and use up to 32 registers it may be complete solution to best performance. But with coef_stride=16 (and more) the 512bit version expected to be faster. Though 4 planes formats may be (very) rare in use. But currently if AVX512 enabled system sets coef_stride to 16 at
https://github.com/jpsdr/JincResizeMT/blob/750bf15230147de1b9c1746043dbe6fccbe10b6e/Src/JincResizeMT.cpp#L555
https://github.com/jpsdr/JincResizeMT/blob/750bf15230147de1b9c1746043dbe6fccbe10b6e/Src/JincResizeMT.cpp#L1472
. Need some solution here too. With avx512 not enabled (opt=2) it need to call AVX512 function from _avx512.cpp file ?
jpsdr
3rd October 2025, 18:12
With opt=2 and AVX512 autodetected, it will call the function. I've just pushed things.
DTL
3rd October 2025, 21:57
You miss deleting memory for out_fp16 at class destruction (FreeData) - https://github.com/DTL2020/JincResizeMT/blob/5f42a8511efba36d4e9f0660fcb3c913ae3847a3/Src/JincResizeMT.cpp#L1590
With sources compiled with VS2022 and forced AVX512 for _avx512.cpp file the asm listing shows it really uses >16 SIMD registers:
; 1357 : result1_2 = _mm256_fmadd_ps(src_ps1_2, coeff2, result1_2);
vfmadd231ps ymm18, ymm1, ymm3
mov rax, rcx
sub rax, r12
vpmovzxbd ymm0, XMMWORD PTR [rax]
vcvtdq2ps ymm1, ymm0
vpmovzxbd ymm0, XMMWORD PTR [rcx]
; 1358 : result2 = _mm256_fmadd_ps(src_ps2, coeff, result2);
vfmadd231ps ymm5, ymm1, ymm2
vcvtdq2ps ymm1, ymm0
vpmovzxbd ymm0, XMMWORD PTR [r10+rcx]
; 1359 : result2_2 = _mm256_fmadd_ps(src_ps2_2, coeff2, result2_2);
vfmadd231ps ymm19, ymm1, ymm3
vcvtdq2ps ymm1, ymm0
vpmovzxbd ymm0, XMMWORD PTR [rdi+rcx]
; 1360 : result3 = _mm256_fmadd_ps(src_ps3, coeff, result3);
vfmadd231ps ymm16, ymm1, ymm2
vcvtdq2ps ymm1, ymm0
vpmovzxbd ymm0, XMMWORD PTR [r11+rcx]
; 1361 : result3_2 = _mm256_fmadd_ps(src_ps3_2, coeff2, result3_2);
vfmadd231ps ymm20, ymm1, ymm3
vcvtdq2ps ymm1, ymm0
vpmovzxbd ymm0, XMMWORD PTR [rsi+rcx]
; 1362 : result4 = _mm256_fmadd_ps(src_ps4, coeff, result4);
vfmadd231ps ymm17, ymm1, ymm2
add rcx, 8
vcvtdq2ps ymm1, ymm0
; 1363 : result4_2 = _mm256_fmadd_ps(src_ps4_2, coeff2, result4_2);
vfmadd231ps ymm21, ymm1, ymm3
Performance test shows also some better result - about 25 vs 23 fps with AVX512 enabled and SetMaxCPU("AVX2").
Also UserDefined4ResizeSPMT() missed argument 'opt' and used CPU features can not be controlled. It looks was copy from Jinc256Resize(). Why that presets do not have 'opt' ?
Good news - usage of >16 SIMD registers for 256bit words processing at AVX512 CPU do not cause CPU clock trottling.
Also usage of 512bit words processing cause clock trottling from 3.39 to 2.69 GHz at some old Xeon Gold but it still process a bit faster with tap=7 in comparison with AVX2 mode (opt=2).
Asm output of VS2022 C compiler looks not good for performance - it uses chained fma instructions with dependend sources ymm1, ymm2, ymm3. But fma instructions have big latency (like 4..5 clocktics) and good throughput (up to 2 results per clocktick with 2 FMA units ?) and expected to have better performance if used in a group with independent sources (parallel). I hope clang LLVM compiler may make better result. But with VS2022 clang LLVM can not found include file for AVX2 and other SIMD headers and fail to compile. Need to found why it is. I have same issue with some other AVS plugin projects.
Addition: LLVM build with VS2022 is fixed by adding AVX2 and AVX512 direct setting in the _avx2.cpp and _avx512.cpp files compile settings.
I see addition of
#if defined(CLANG)
__attribute__((__target__("avx2")))
#endif
in program text but it looks not working with VS2022 and LLVM compiler. Only direct setting of Enable Enhanced Instruction Set -> AVX2 (/arch:AVX2) in the project setting for .cpp file is working.
And it looks it make more expected asm with grouped fma instructions at the end of loop as expected:
vpmovzxbd ymm18, qword ptr [r8 + rbx] # ymm18 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vpmovzxbd ymm19, qword ptr [rcx + rbx] # ymm19 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vcvtdq2ps ymm18, ymm18
vcvtdq2ps ymm19, ymm19
vpmovzxbd ymm20, qword ptr [r15 + rbx] # ymm20 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vcvtdq2ps ymm20, ymm20
vpmovzxbd ymm21, qword ptr [r10 + rbx] # ymm21 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vcvtdq2ps ymm21, ymm21
vpmovzxbd ymm22, qword ptr [r12 + rbx] # ymm22 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vpmovzxbd ymm23, qword ptr [rax + rbx] # ymm23 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vcvtdq2ps ymm22, ymm22
vcvtdq2ps ymm23, ymm23
vpmovzxbd ymm24, qword ptr [r9 + rbx] # ymm24 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vcvtdq2ps ymm24, ymm24
vpmovzxbd ymm25, qword ptr [r11 + rbx] # ymm25 = mem[0],zero,zero,zero,mem[1],zero,zero,zero,mem[2],zero,zero,zero,mem[3],zero,zero,zero,mem[4],zero,zero,zero,mem[5],zero,zero,zero,mem[6],zero,zero,zero,mem[7],zero,zero,zero
vcvtdq2ps ymm25, ymm25
vmovaps ymm26, ymmword ptr [r14 + 4*rbx]
vmovaps ymm27, ymmword ptr [rdx + 4*rbx]
vfmadd231ps ymm0, ymm26, ymm18 # ymm0 = (ymm26 * ymm18) + ymm0
vfmadd231ps ymm17, ymm27, ymm19 # ymm17 = (ymm27 * ymm19) + ymm17
vfmadd231ps ymm3, ymm26, ymm20 # ymm3 = (ymm26 * ymm20) + ymm3
vfmadd231ps ymm16, ymm27, ymm21 # ymm16 = (ymm27 * ymm21) + ymm16
vfmadd231ps ymm2, ymm26, ymm22 # ymm2 = (ymm26 * ymm22) + ymm2
vfmadd231ps ymm5, ymm27, ymm23 # ymm5 = (ymm27 * ymm23) + ymm5
vfmadd231ps ymm1, ymm26, ymm24 # ymm1 = (ymm26 * ymm24) + ymm1
vfmadd231ps ymm4, ymm27, ymm25 # ymm4 = (ymm27 * ymm25) + ymm4
As expected LLVM (clang-cl) builds are faster in all cases.
For JincResizeMT(width*20, height*20) and YUVA 4:4:4:4 8bit source -
tap=3 at AVX512 chip best performance is opt=2 + AVX512 enabled in AVS+ environment = 34 fps. (coeff_stride=8 +32registers +2rows(?))
tap=7 at AVX512 chip is always better with opt=3 even with clock down from 3.38 to 2.7 GHz = 14.6 fps (with FP16=true). (coeff_stride=16)
jpsdr
4th October 2025, 10:56
You miss deleting memory for out_fp16 at class destruction (FreeData) -
:thanks:
jpsdr
4th October 2025, 11:09
I forgot... About LLVM, i've encountered issue building this project.
According what i've found, it's tricky !
LLVM and GCC behave differently. With GCC, putting __attribute__((__target__("avx512f"))) is enough to enable AVX512 even if the global build option doesn't allow AVX512, the intrinsic will work.
But with LLVM, there is a nasty trick :( (according Chat GPT).
When "getting" the intrinsic from #include <immintrin.h>, the global build option will configure what intrinsic are allowed. So with LLVM, putting __attribute__((__target__("avx512f"))) will still allow to build using AVX512 code, BUT... the AVX512 intrinsic will still not be avaible if the global build option is for exemple only SSE2. So, you have with LLVM to add, for this specific file, the global option to build with AVX512 (just the file, not the whole project fortunately).
This is why i had to move the FP16 create coeff to the AVX2 file, otherwise, with LLVM, it will build only if I build with AVX2 the core file, so putting the whole plugin usable only to AVX2 CPU for LLVM build.
DTL
5th October 2025, 10:04
More close to UserDefined2Resize(b=80, c=-20) kernel members for UserDefined2ResizeSPMT() and comparison of result of downsize from UHD 2160p to 1080p of some natural footage with difference frame:
UD4=UserDefined4ResizeSPMT(width/2, height/2, k10=97, k20=-2, k11=35, k21=-1, s=3)
UD2=UserDefined2Resize(width/2, height/2, b=80, c=-20).Subtitle("UD2")
Interleave(UD4, UD2, Subtract(UD4, UD2))
ConvertBits(8, dither=1) # input source is 10bit
https://imgsli.com/NDIwMjEy Vertical and horizontal transients looks close and most difference at diagonals as expected.
Subtract result - https://postimg.cc/FYFqzskG
https://i.postimg.cc/FYFqzskG/2025-10-05-120356.png (https://postimg.cc/FYFqzskG)
Subtraction shows most difference in fine sharp areas and looks also like checker-board pattern or mostly diagonal lines.
You can update defaults to better known now k10=97, k20=-2, k11=35, k21=-1 at https://github.com/jpsdr/JincResizeMT/blob/b7a653ac443d874f95ac0c7db98b6114fcaddb28/Src/JincResizeMT.cpp#L2386 and in the readme.
Test encoding of UHD->FHD rip of nature views with ffmpeg of 46 min length with settings -x264opts "crf=22:level=4.1:ref=4" -preset veryslow
shows slightly lower bitrate with UD4 downsize vs UD2 with settings from above - 5780 and 5809 kbit/s.
FranceBB
5th October 2025, 23:32
Vertical and horizontal transients looks close and most difference at diagonals as expected.
A tiny bit less haloing on the tip of the leaf on UD4 as well in the bottom left of the picture.
https://i.postimg.cc/R0PRHdwK/Screenshot-2025-10-05-232749.png
DTL
6th October 2025, 06:13
More important is flat colouring at the high saturation red OSD/logo patch. Like constant +-1LSB (or more) error/difference (at UV planes ?). It may mean some still non-corrected rounding error with float->integer conversion or something else. Maybe a resampling program needs +0.5f shift somewhere. Or resampling engine before downconversion from float32 to integer.
"A tiny bit less haloing on the tip of the leaf on UD4 "
With jinc-based kernels I think the thinnest possible over/undershoot (and also the speed of the gradient defining visual sharpness) is not as thin as we can get with sinc-based. Also single-pass 2D processing looks less sharp in smallest details (also with diagonal transients). It may be partially because dual-pass 1D resizers leave more diagonal ringing.
To compensate for this possible details loss on finest details we need to increase gain and medium-high frequencies (while having ringing suppressed). All this means (in my current tests) to have comparable sharp output we need to have thicker and higher halo/over/under shoot defined by the filter kernel with UD4 downsize in comparison with UD2 downsize.
Though when watching an image at real (recommended or more) distance from the screen the finest details are typically lost and sharpness defined more by medium-high (valid) frequencies. Not the highest valid. In comparison Spline64Resize keeps much more fine details without amplification of medium-fine but much of that detail is lost at real viewing distance.
Selection of production downsizer (and its settings) for broadcasting may be a complex task with real quality evaluation by non-expert viewers too. Also with different ages and different real viewing conditions. And select some average.
For example small children sitting too close to the screen will see how UD2/UD4 lose finest details and create significant halos over the high contrast standalone transients. And count this as significant distortion.
Though old people sitting at recommended distance (ITU/EBU) or far will see how medium-fine enhanced details create more sharpness, also medium-detail sized textures look more sharp/detailed.
It is a really complex task to select 'best' settings because if we use a linear kernel without significant halos on high contrast transients - we lose significant details sharpness on many more valuable texture patches. Practical selected settings may be some in between too soft look and too high halo on rare standalone high contrast transients. Better may be to use some non-linear sharpening engine (up to NN/AI-based) with some linear downsampling engine.
DTL
8th October 2025, 20:23
I check output of UD2 and UD4 resize of the color bars downsize with AvsPmod for YUV values of samples and they looks equal at the flat patches. In both 8 bit and 16 bit modes. I do not understand why OSD logo of that test footage after Subtract() looks colored. May be issue in the Subtract() ?
So it looks no issues with computing and rounding in current JincResizeMT version.
jpsdr
29th November 2025, 12:05
First page updated, minor changes.
DTL
6th December 2025, 20:57
A tiny bit less haloing on the tip of the leaf on UD4 as well in the bottom left of the picture.
I found your post some at Internet https://www.linkedin.com/posts/francesco-bucciantini-3392b4ab_over-the-years-several-different-resizers-activity-7395870781030809600-jJpG and a question about math around this.
Some days ago I tried to found if it is possible to make even 'sharper' 2D resize using our current 2D resize engine and more spectrum-powered kernel.
The foundings with some Mathcad simulation and also some AI reading from math about IFFT for 2D spaces:
1. About jinc kernel - it is 2D IFFT of the round spectrum in 2D :
F(u,v) = 1 if sqrt(u^2 + v^2) < 0.5 and 0 elsewhere.
Its IFFT2(F(u,v)) is f(x,y)=jinc(x,y). And it is really function of radius - f(x,y)=jinc(sqrt(x^2+y^2))= jinc(radius).
And it is the impulse kernel for filtering/resizing. Its 2D view you see as JincResize(width*10, height*10) of single non_zero sample.
2. The possible disadvantage of jinc kernel - its FFT spectrum not fill all possible frequencies for square sampling grid.
The full filled spectrum in square 2D object is
F(u,v) = 1 if (|u|<0.5 and |v|< 0.5) and 0 elsewhere.
And as Mathcad solver shows and AI tools hints - its
IFFT2 (impulse kernel for resizer) is
f(x,y)=sinc(x)*sinc(y).
But if we either make this 2D kernel for 2D resize or process 2 times with 1D + 1D SincResize - the output is equal. And it is that checker-borad like pattern instead of 'nice circles' like with JincResize.
Current conclusions from this:
1. It looks SincResize (and all weighted single sinc-based resizers like Lanczos and others) is 'separable' to 2 pass 1D and 1D resize and attempt to process with 2D engine will only be slower.
2. The jinc-based resize is not 'separable' and can be only processed with single pass 2D engine.
3. May be most important about sharpness: It looks jinc-based resize will always be less sharp in comparison with sinc-based because its Fourier spectrum is less powerful (as relation of square field with side size r (square is (2r)^2=4(r^2)) for sinc() and square of circle of radius r (square is pi(r^2)) - the jinc resize has less total 2D integrated spectrum power of (pi(r^2))/(4(r^2))=pi/4 ~ about 0.785.
The 2D spectrum power difference between sinc and jinc kernels is about 1-0.785=0.215. The total supported spectrum power of jinc is about 21% less in comparison with max possible in square sampling grid (possible with sinc) (?) .
Possible advantage of the jinc-based resize - it may process all same bandwidth 2D frequencies (with bandwidth of 0.5 in all directions from 0,0 - in H and V and all angled directions) without ringing-like effects that we see with 2D sinc-based resizers. So it may be maximum possible sharpness resizer with ability to display any 2D angle positioned shapes without ringing-like distortions. Sinc-based resizers only can display H and V oriented spatial frequencies without ringing but may expose some ringing-like (or even aliasing-like ?) artifacts at the angled spatial frequencies. It may looks like rectangular sampling grid for 2D frequencies can only support all possible frequencies fitted in the jinc 2D FFT.
Here is some Mathcad project to compute 2D impulse kernels f2D(x,y) as IFFT2 of the spectrum shapes (F2D(u,v))
https://i.ibb.co/fV5V0fM5/2d-fft2.png (https://imgbb.com/)
Unfortunately at my old CPU Mathcad 15 takes too large time to compute even 1D graphs sections like f2D(x,0). May be someone with more powerful PC and better math simulation tools can make nice 2D graph plots (really 3D function of x,y).
Additional notes:
I still not sure if it is valid for Nyquist theorem to use >0.5 radius spectrum in 2D space as we have with 2D sinc kernel. For 1D case it is all simple - definitely not valid.
So JincResize with jinc kernel and 2D spectrum limited by 0.5 radius may be used as definitely aliasing-safe resize method. But it still produce a bit less sharp results in comparison with sinc-based resize.
SincResize (and sinc-based resizers) may be not aliasing-safe for angled (diagonal) spatial frequencies and only safe for H and V directions.
tormento
7th December 2025, 15:24
Some days ago I tried to found if it is possible to make even 'sharper' 2D resize using our current 2D resize engine and more spectrum-powered kernel.
Don't know if this can really help you: I use ewa_lanczos4sharpest from avslibplacebo and find it sharp, pleasant and without any visible artifact.
Perhaps it's the linear + sigmoid resizing but I hope you can find more in the libplacebo source.
DTL
8th December 2025, 08:49
Tried to compare 3 ewa_lanczos resizers with JincResizeMT in filter kernel footprints:
LoadPlugin("avs_libplacebo.dll")
LoadPlugin("JincResizeMT.dll")
BlankClip(200,1,1, pixel_type="YV24", color_yuv=$D07f7f)
AddBorders(7,7,7,7, color_yuv=$307f7f)
elz4shst=libplacebo_Resample(width*20, height*20, filter="ewa_lanczos4sharpest").Subtitle("ewa_lanczos4sharpest")
elzsh=libplacebo_Resample(width*20, height*20, filter="ewa_lanczossharp").Subtitle("ewa_lanczossharp")
elz=libplacebo_Resample(width*20, height*20, filter="ewa_lanczos").Subtitle("ewa_lanczos")
jr_1=JincResizeMT(width*20, height*20,tap=2,blur=1.04).Subtitle("JR_MT_1")
jr_2=JincResizeMT(width*20, height*20,tap=3,blur=1.0).Subtitle("JR_MT_2")
jr_3=JincResizeMT(width*20, height*20,tap=3,blur=1.0).Subtitle("JR_MT_3")
avs_lp=StackVertical(elz4shst, elzsh, elz)
jr=StackVertical(jr_1,jr_2, jr_3)
StackHorizontal(avs_lp, jr, Subtract(avs_lp, jr))
https://i.ibb.co/ksk9w8x4/src3000188.jpg (https://ibb.co/S4WdMnPt)
It looks like the kernel footprint for single sample excitation does not depend on linearization/sigmoidization and it makes comparison more simple.
They look like they have some mess of duplication with filter settings - ewa_lanczossharp and ewa_lanczos are equal and completely equal to JincResizeMT(,tap=3,blur=1.0). If users use linearization/sigmoidization in libplacebo - it must be performed before and after resize with JincResizeMT().
The ewa_lanczos4sharpest looks to use non-jinc weighting with significant suppression of the first negative lobe of the jinc kernel. This can not be emulated in jinc-only weighting +some blur-scaling in current JincResizeMT(). This really expected to make less 'sharp' results in comparison with other ewa_lanczos presets. Some close but not equal settings with JincResizeMT are JincResizeMT(,tap=2,blur=1.04)
What I see from sources filters.c file:
const struct pl_filter_config pl_filter_ewa_lanczos4sharpest = {
.name = "ewa_lanczos4sharpest",
.description = "Sharpened Jinc-AR, 4 taps",
.kernel = &pl_filter_function_jinc,
.window = &pl_filter_function_jinc,
.radius = JINC_ZERO4,
// Similar to above, see:
// https://www.imagemagick.org/discourse-server/viewtopic.php?p=128587#p128587
.blur = 0.88451209326050047745788,
.antiring = 0.8,
.polar = true,
.allowed = PL_FILTER_SCALING,
.recommended = PL_FILTER_UPSCALING,
};
const struct pl_filter_config pl_filter_ewa_lanczos = {
.name = "ewa_lanczos",
.description = "Jinc (EWA Lanczos)",
.kernel = &pl_filter_function_jinc,
.window = &pl_filter_function_jinc,
.radius = JINC_ZERO3,
.polar = true,
.allowed = PL_FILTER_SCALING,
.recommended = PL_FILTER_UPSCALING,
};
const struct pl_filter_config pl_filter_ewa_lanczossharp = {
.name = "ewa_lanczossharp",
.description = "Sharpened Jinc",
.kernel = &pl_filter_function_jinc,
.window = &pl_filter_function_jinc,
.radius = JINC_ZERO3,
// Blur value determined by method originally developed by Nicolas
// Robidoux for Image Magick, see:
// https://www.imagemagick.org/discourse-server/viewtopic.php?p=89068#p89068
.blur = 0.98125058372237073562493,
.polar = true,
.allowed = PL_FILTER_SCALING,
.recommended = PL_FILTER_UPSCALING,
};
Difference between ewa_lanczossharp and ewa_lanczos only in blur of 0.98 (about invisible).
ewa_lanczos4sharpest has larger blur (more wide or more narrow scaling ?) and +1 tap but also 'antiring' of 0.8. Where 'antiring' is additional non-jinc weighting processing and it is about destroying all lobes except the first positive. Though 'antiring' may be applied after convolution resize with not-antiringed kernel (?). If users of AVS like that 'antiring' (post)processing to 2D resize jinc-based it needs to be ported separately (or as a separate filter ?).
In the description to avslibplacebo:
antiring
Antiringing strength.
A value of 0.0 disables antiringing, and a value of 1.0 enables full-strength antiringing.
Only relevant for separated/orthogonal filters.
Default: 0.0.
But it looks outdated and antiringing already implemented and working for 2D/polar resizers too. Also ewa_lanczos4sharpest preset looks like equal to manual tuning of processing 'generic' jincresize as
libplacebo_Resample(filter="ewa_lanczos", radius=4, blur=1.0, antiring=0.8).
tormento
8th December 2025, 12:14
Tried to compare 3 ewa_lanczos resizers with JincResizeMT in filter kernel footprints
Very nice and detailed analysis, thank you!
FranceBB
31st December 2025, 18:44
I found your post
Yeah I've included the latest version of plugins_JPSDR in FFAStrans (https://forum.doom9.org/showthread.php?t=176655) so that users can now choose JincResizeMT() from the drop down menu when inserting a resizing node, that's why I made the post. It will be released as soon as FFAStrans 1.4.2 is deemed stable.
https://images2.imgbox.com/60/fd/Y9fFWlIb_o.png
Anyway, it's always gonna be a tradeoff between spectral power vs directional consistency.
The checkboard-like pattern in Sinc is there and will always be there because of the 1D+1D (horizontal+vertical), in fact the resulting 2D impulse response is: f(x,y) = sinc(x) • sinc(y) In the frequency domain, it creates that square/checkboard-like pattern.
The reason why it's not there in Jinc is that it's a true 2D operation where the kernel is a function of the radius r= sqrt(x^2+y^2) which makes its frequency response a circular disk.
In other words:
SincResize()
Spectral Shape: Square
Diagonal Reach: Preserves frequencies up to 0.707 Nyquist
Ringing Pattern: Rectangular (i.e the checkboard)
Complexity: O(N) so it's linear
JincResize()
Spectral Shape: Circular (isotropic)
Diagonal Reach: Limits all directions to 0.5 Nyquist
Ringing Pattern: Circular (i.e like haloing)
Complexity: O(N^2) so it's exponential
the jinc resize has less total 2D integrated spectrum power of (pi(r^2))/(4(r^2))=pi/4 ~ about 0.785.
Yes. Given that the Sinc kernel fills the corners of the "frequency square", it preserves more diagonal information, which is why it looks sharper but that sharpness is technically anisotropic in the sense that the filter treats the diagonal frequencies differently from the horizontal and vertical ones. The checkboard pattern is actually a diagonal ringing - albeit a weird one given that it's a synthetic benchmark. The square spectrum has "corners" so it allows higher-frequency components on the diagonals than the sampling grid can actually support cleanly, leading to that kind of artifact.
I still not sure if it is valid for Nyquist theorem to use >0.5 radius spectrum in 2D space as we have with 2D sinc kernel. For 1D case it is all simple - definitely not valid.
So JincResize with jinc kernel and 2D spectrum limited by 0.5 radius may be used as definitely aliasing-safe resize method. But it still produce a bit less sharp results in comparison with sinc-based resize.
SincResize (and sinc-based resizers) may be not aliasing-safe for angled (diagonal) spatial frequencies and only safe for H and V directions.
Yep, you're right, in a standard rectangular sampling grid the Nyquist limit is 0.5 in both directions (i.e both u and v). The "extra" space in the corners of a Sinc filter (i.e the area outside the 0.5 radius but inside the 0.5x0.5 square) is where the problem occurs. JincResize() is alising safe because it treats the limit as a radius. In other words, no matter the angle of the edge (diagonal lines, curves), the bandwidth is constrained in the same way. On the other hand, SincResize() creates a sort of "preferential treatment" for diagonals which then creates the problems.
When we use a separable filter, the ringing (oscillation) only occurs along the x and y axes. On a perfectly horizontal or vertical edge, the ringing is a simple 1D ripple following the edge, but on a diagonal edge the horizontal ringing and vertical ringing overlap because the kernel is a square sinc(x) • sinc(y) so the "echoes" of the edge are also square-aligned. Mathematically, it would be anisotropic ringing, but to the eye is the checkboard pattern we saw, but nonetheless not a failure of the Nyquist limit.
JincResize produces isotropic ringing instead because the kernel is circular, so the ripples propagate outwards uniformly in all directions.
https://images2.imgbox.com/41/79/Dh3SF2hg_o.png
J1 is the Bessel function of the first kind. This is to say that obviously JincResize isn't immune to everything, but the thing is that when a Jinc filter "rings", the "echo" of a diagonal line is a ripple that is parallel to that line which is much more natural to the human eye compared to the orthogonal grid of ripples produced by separable Sinc filters, which is why it looks "better". I mean, it preserves the roundness of the Airy Disk example 'cause the ringing itself is round xD
Anyway one could say "well, but if Sinc is supposed to treat diagonal lines differently to avoid staircase artifacts and I only have ripples for functions that are not limited in frequency, wouldn't putting a window like Lanczos or Blackman help?"
to which the answer would be "yes, but not quite".
I mean, using a windowed sinc does help because the window function forces the Sinc function to 0 at a certain radius and reduces the amplitude of the side lobes (i.e the ripples), but LanczosResize() is still separable, so we're still multiplying two windowed 1D functions. The "corners" of the frequency square still exist, meaning that the filter still "sees" further into the diagonal frequencies (0.707 Nyquist) than the horizontal and vertical ones (0.5 Nyquist).
Emulgator
1st January 2026, 00:54
Plus a reminder: Nyquist is the poorest mathematical vehicle which can ONLY be ridden if an infintely (!) stationary signal can be assumed.
(AND be sampled not at its nodes, which can not be taken for granted)
Since real-world signals do vary in time I have banned such narrow bridges from my calculations since 1985.
The steeper antialiasing and reconstruction filters are designed the more harm they cause.
For signal fidelity I can only suggest to keep a safe distance from the nyquist pothole.
FranceBB
2nd January 2026, 16:39
Since real-world signals do vary in time I have banned such narrow bridges from my calculations since 1985.
I wasn't even born back then xD
DTL
6th January 2026, 10:32
users can now choose JincResizeMT() from the drop down menu when inserting a resizing node,
As typical production operation (and many rip creations too) is downsizing it is good to include all downsize filters too.
Now in the drop down list I see mostly Gauss and SinPow (and partially Bicubic) filters for downsizing. No 1D+1D (sinc-based) UserDefined2 and no 2D (jinc-based) UserDefined4 .
As FFASTrans is expected to be an industrial tool for professionals it is expected they are educated enough to set params of kernel for expected best downsizers with more complex kernel control.
FFASTrans is also used in my company for many operations. So it is fun to see how a resizer designed in one room passed via different countries to different software and returned to work in a different room of the same company (in some days or years later). I hope the workers of my company in different rooms using FFASTrans may in some year finally get UserDefinedX downsizers to work via FFASTrans. They do not like to write text-based AVS scripts but can use window-based FFASTrans applications. So it may be some development task for the designer of FFASTrans to set some most commonly used params for UserDefinedX filters if GUI of FFASTrans does not have controls to setup kernel members and support size (filter params).
FranceBB
6th January 2026, 18:24
Let me just say that I'm actually extremely glad that you guys are using FFAStrans. :D
I can of course add UserDefined2 and UserDefined4 to the resize node, I just have to think about a proper way to display the options to the user in the GUI.
Thank you for everything you've been doing for the community, DTL, really. In the end, if those things are possible is thanks to the contributions of each and everyone of us.
In Latin they say "do ut des" which means "give and receive" and I guess that's exactly what the open source community is about. :)
DTL
11th January 2026, 11:01
Tried script for linear resizers kernels footprints from current version of plugins_JPSDR:
LoadPlugin("plugins_JPSDR.dll")
BlankClip(length=200, width=1, height=1, pixel_type="YV24", color_yuv=$D07f7f)
AddBorders(9, 9, 9, 9, color_yuv=$307f7f)
upr=15
SincLin2=SincLin2Resize(width*upr, height*upr,taps=8).Subtitle("SincLin2Resize taps=8 1D+1D", align=8)
Jinc=JincResizeMT(width*upr, height*upr, tap=8).Subtitle("JincResize t=8 2D", align=8)
Point=PointResizeMT(width*upr, height*upr).Subtitle("PointResize t=16 1D+1D", align=8)
Bilinear=BilinearResizeMT(width*upr, height*upr).Subtitle("BilinearResize 1D+1D", align=8)
Bicubic = BicubicResizeMT(width*upr, height*upr).Subtitle("BicubicResize 1D+1D", align=8)
Lanczos=LanczosResizeMT(width*upr, height*upr).Subtitle("LanczosResize 1D+1D", align=8)
Lanczos4=Lanczos4ResizeMT(width*upr, height*upr).Subtitle("Lanczos4Resize 1D+1D", align=8)
Blackman=BlackmanResizeMT(width*upr, height*upr).Subtitle("BlackmanResize 1D+1D", align=8)
Spline16=Spline16ResizeMT(width*upr, height*upr).Subtitle("Spline16Resize 1D+1D", align=8)
Spline36=Spline36ResizeMT(width*upr, height*upr).Subtitle("Spline36Resize 1D+1D", align=8)
Spline64=Spline64ResizeMT(width*upr, height*upr).Subtitle("Spline64Resize 1D+1D", align=8)
Gauss=GaussResizeMT(width*upr, height*upr).Subtitle("GaussResize 1D+1D", align=8)
Sinc=SincResizeMT(width*upr, height*upr).Subtitle("SincResize 1D+1D", align=8)
SinPow=SinPowResizeMT(width*upr, height*upr).Subtitle("SinPowResize 1D+1D", align=8)
UD2=UserDefined2ResizeMT(width*upr, height*upr, b=80, c=-20).Subtitle("UserDefined2 b=80 c=-20 1D+1D", align=8)
UD4=UserDefined4ResizeSPMT(width*upr, height*upr).Subtitle("UserDefined4ResizeSP 2D", align=8)
#Compare
h01=StackHorizontal(SincLin2, Jinc, Point, Bilinear)
h02=StackHorizontal(Bicubic,Lanczos,Lanczos4,Blackman)
h03=StackHorizontal(Spline16,Spline36,Spline64,Gauss)
h04=StackHorizontal(Sinc,SinPow,UD2,UD4)
StackVertical(h01,h02,h03,h04)
https://i.ibb.co/KjkTxHNL/rkfp-pjpsdr01.png (https://ibb.co/Y7MHBGDy)
https://ibb.co/Y7MHBGDy
Sadly found JincResizeMT still need some fixes of kernel at the bottom of frame - if frame size is too small (relative to kernel size ?) the bottom of frame is distorted. Also with tap=16 and upsize ratio of 15 the center point looks like has some computing issues and not looks flat (may be quant is not enough ?). It is to current 'known issues'. With general usage small taps (below 4..6 ?) num and small upscale ratios it expected not very visible.
FranceBB
11th January 2026, 14:04
By the way, I've added the missing resizers "SincLin2Resize", "SinPowerResize", "UserDefined2Resize" as well as the missing "force", "keep_center", "placement" parameters to the old wiki (http://avisynth.nl/index.php/Resize) so that it's now aligned with the new one (https://avisynthplus.readthedocs.io/en/latest/avisynthdoc/corefilters/resize.html).
Well, not quite perfectly aligned, but still... at least they're there now xD
jpsdr
11th March 2026, 20:27
Hi.
I've pushed the update of the new core, but don't have time to test right now. I will, but not for now.
In the meantime, if anyone want to build and test, go ahead :D !
DTL
11th March 2026, 21:28
In resample.cpp -
// VS 2019 v16.2
#if _MSC_VER >= 1922
#define AVX512_BUILD_POSSIBLE
#endif
To make all builds for development and testing I still use VS2017 because it is the last version supported intel SDE for AVX512 emulation with debugger from VS. Better to allow VS2017 builds too. Though it may not support all AVX512 versions of text but as I remember AVS+ sources still compatible with VS2017. The not-supported found things are very few like some ways of loading of bits constants to mask registers. But other ways exist to workaround without performance penalty.
jpsdr
11th March 2026, 23:39
I'll not change it on my side, but you can make your own branch and change it. There is the same in cpp avx512/avx512b on all the file. This allow to have all the files included and activated in the project, but not build if unsuported, instead of included but not activated
jpsdr
12th March 2026, 09:51
Tell me anyway if with your Visual Studio version you can build without any issue. If it's the case, i'll change the minimum requirement.
I took it from check_avx512.h in the avisynth code, so i've assumed it was the minimum requirement.
... ...
Ok... I'll took a look again at check_avx512.h and i was clearly in the wrong, it was just for a few command. I'll change the code to go back to proper check.
jpsdr
12th March 2026, 18:49
@DTL
I've rolled back (and pushed) to a proper AVX512 requierement, i was too quick when looking to check_avx512.h.
Edit:
Tell me if you can build now.
New version, see first post.
It may be interesting to check performance differences at multithreading execution of permutex-based resamplers between AVS+ core inter-frame based MT and ResampleMT intra-frame based MT. Unfortunately those algorithms are fast in single threading (low enough main host RAM read-write streams) but quickly lose performance at many threads execution with poor low-RAM channels host memory. Sort of very non-SDRAM friendly algorithms and can only run fast with small image sizes fits in CPU caches. Though with the next generation CPUs like Xeon MAX with large HBM RAM onboard and AMD with large cache this limitation is expected to be lower.
This is why special L2-cache size granularity was added to make this performance issue lower at bigger image sizes. Though this causes more register file coeffs reloads and adds more performance penalty. Though if each loaded once in the register file subset of coeffs can be used to process at least 10 rows this overhead is already 10 times lower in comparison with each row coeffs reload. With intra-frame MT in ResampleMT other solutions are possible for testing like running each H-stripe of L2-cache size in a separate thread. Though thread synchronization overhead may damage performance too. Or at least each thread may get an interleaved list of H-stripes of an image instead of a single H-stripe to process. This type of frame scan order is only possible in ResampleMT and was not tested in AVS+ core.
Tested release 2.10.0 and it looks follows the performance features of current AVS+ sources. They were optimized for frame-based MT but looks still not completely resolve the memory transfer issues.
Test script:
LoadPlugin("ResampleMT.dll")
BlankClip(100000, width=400, height=400, pixel_type="YUV444PS")
#BicubicResize(width*4,height) # permute, H kernel size 4
BicubicResizeMT(width*4,height, threads=1) # permute, H kernel size 4
Both AVS+ and ResampleMT now runs at about 800 fps with full (6) threads enabled at i5-9600K. And if set no-Prefetch for AVS+ core or threads=1 for ResampleMT - at about 360 fps.
But non-L2 cache optimized AVS+ release from 30 Nov 2025 runs at about 1200 fps no-Prefetch. It was found the stream_ps (uncached store) added to help decrease performance drop with MT-enabled causes significant single threaded performance degradation. The lastest L2-cache optimized version runs at about 2200 fps single threaded with cached store store_ps().
So with current version of <=ks4 resize it may be used either templated or 2 different copies of function with cached or not store for 1threaded or MT processing. Also for MT meander scan and prefetch for write also adds performance (MT performance drop not as big). See commit https://github.com/DTL2020/AviSynthPlus/commit/fbda23c47c9fcf073b3cf3ae138237886f5b2fe4 as eaxmple. Also others permute-based resample functions (AVX2 and AVX512) need to be tested for this issue and possible workarounds (also at different CPUs architectures).
It is still sort of anomality when single threaded (1 core) function runs faster in comparison with any attempt to run in >1 threads. You may test threads=1 execution with _mm256_store_ps() at https://github.com/jpsdr/ResampleMT/blob/360d443fbc5323909736478d45a106755094f22d/ResampleMT/resample_avx2.cpp#L1237 . I hope it is processing function for BicubicResizeMT(width*4,height, threads=1) in ResampleMT too.
jpsdr
12th May 2026, 18:51
I don't have time to make experiments.
If you said that it's better with the "store" instead of "stream", i will change it, i may have a little time to make a new version until the end of the week, but that's all.
In my tests it is better only for single thread execution. Looks like the CPU memory subsystem can only arrange in the best way the memory read-write requests from a single core only and greatly degrade performance if >1 core is used. Maybe some caches synchronization traffic eats all performance additions ? The performance degradation from not best memory access (?) is more in comparison with performance addition from several cores running convolution computing. So my current partial solution - use 'store' in single threaded execution and 'stream' (+ meander scan and prefetch for write) in multithreaded. This needs either 2 separate functions text or templated functions with some additional templating param like bool bMT. Also for the ResampleMT filter with mostly expected multithreaded execution use cases (?) this may cause additional users confusion because at least for some frame sizes and scale ratios single threaded execution is still faster in comparison with any attempt to run 2 and more threads.
jpsdr
16th May 2026, 11:15
By single threaded do you mean using threads=1 but with eventualy prefech>1 (the AVS+ command, not the parameter filter), or pure one core, with both threads=1 and prefech=1 (so no prefetch command in the AVS script) ?
"By single threaded do you mean using threads=1 "
Yes - threads=1 and no Prefetch in script makes best performance. Adding Prefetch(1) already visibly degrades performance (may be -20..30%). But it looks unavoidable if user want to use 1 threaded run of filter in a long script ?
Like
some_filters
Prefetch(N) #to run previous filters with full multithreading
ResizeMT(threads=1)
Preftech(1) # to run ResizeMT in 1 thread mode (?)
? Not yet tested.
jpsdr
17th May 2026, 10:51
If i understand properly, using "store" instead of "stream" is interesting only in the case threads=1 and without prefetch ?
In that case, i will not waste time for checking this too much specific case.
Yes - it may be not very important for MT-specific filter. It is a note about possible better performance mode found at testing. Though it is currently sort of anomaly with best performance at single threaded execution. May be subject of future research if this performance boost may be used in more or less limited multithreaded mode. Currently it is planned for main usage in the future partial plane (tiled) processing inbetween filters. This is what mean note around 'store' operation in current sources.
hello_hello
25th May 2026, 21:20
For a long time my memory has been that the MT resizers are slower with Prefetch in a script than without it, but I can't remember if that includes the use of the Threads argument or not. I haven't bench-marked them in a long time, so after reading DTL's post I thought I'd give it a spin.
The FPS numbers below aren't extremely accurate as for some reason AvsPmod doesn't display the final frame rate at the end of a benchmark when it's running in Wine, only the duration, so the FPS numbers are roughly what they appeared to be while the benchmark was running.
I also remember AvsResize being considerably slower when Avisynth's MT is enabled, but that no longer seems to be the case.
Zen 4 Ryzen 9 7900X, Avisynth+ r4565, running DLT's script from post #432.
BicubicResizeMT
No Threads, Prefetch(12) 121 seconds 800 fps
Threads=12, Prefetch(12) 76 seconds 1300 fps
Threads=1, No Prefetch 24 seconds 4000 fps
Threads=1, Prefetch(12) 13 seconds 8000 fps
z_BicubicResize
No Prefetch 31 seconds 3300 fps
Prefetch(12) 24 seconds 4000 fps
BicubicResize
No Prefetch 26 seconds 3800 fps
Prefetch(12) 11 seconds 8500 fps
jpsdr
26th May 2026, 18:02
What do you mean by "No Threads" ? The parameter threads is not set ? (I'll assume that for now, it's identical to threads=0, meaning for you CPU it's the same as threads=24).
Have yo read the Multi-threading information part of the first post ?
You should try the following if you want to keep at 12 threads (even if your CPU can do 24).
- No Threads (threads=0), No prefetch
- Any combination (threads=p,prefetch=n), Prefetch(n) with n*p=12 & p>1.
hello_hello
27th May 2026, 04:46
I probably read the multi-threading information about 8 years ago. I'd forgotten the MT resizers have a prefetch argument as I haven't used them regularly.
No threads = Threads not set.
The main reason I tested with Prefetch(12) is because it's generally the value that gives me the highest speed when encoding scripts. Depending on the filtering the sweet spot is usually from Prefetch(8) to Prefetch(12), so when I'm too lazy to run a test encode I use Prefetch(12). For this simple script, it turns out Prefetch(8) is about 300fps faster than Prefetch(12) and nothing greater than 12 increases the speed (but at least it doesn't decrease).
Anyway, I tried again, and the only way to achieve roughly 8000 fps as I did in the previous tests, is to use Threads=1 for the resizer (with Prefetch(12) in the script). Any value for Threads other than one, at least halves the speed.
These combinations for the resizer arguments (based on you formula) run at around half the speed of Threads=1.
Threads=2, Prefetch=6
Threads=3, Prefetch=4
Maybe the moral of the story is running the MT resizers in MT mode when using Prefetch is a bad idea, at least when there's more than a few CPU cores. :)
Does anyone not use Prefetch in a script these days? I'm wondering if Threads=1 should be the default for Avisynth+.
jpsdr
27th May 2026, 12:18
As i'm not using Prefetch, i'll keep the default mode as it is.
If you use threads=1, in that case, don't bother to use my MT versions, just keep the internal original resizers.
What speed do you have with for :
No prefetch (no Prefecth command in the script and don't set the prefetch parameter) and :
- threads=0
- threads=12
Out of curiosity for compare with your others tests.
real.finder
27th May 2026, 13:37
As i'm not using Prefetch, i'll keep the default mode as it is.
or maybe do something like this https://github.com/HomeOfAviSynthPlusEvolution/neo_DFTTest/issues/6#issuecomment-3621448531
BicubicResizeMT
No Threads, Prefetch(12) 121 seconds 800 fps
Threads=12, Prefetch(12) 76 seconds 1300 fps
Threads=1, No Prefetch 24 seconds 4000 fps
Threads=1, Prefetch(12) 13 seconds 8000 fps
You can also try full intra-frame MT for 12 threads:
Threads=12 and no Prefetch().
BicubicResize with Prefetch(12) mean 12 threads with inter-frame MT in AVS+ core.
" the only way to achieve roughly 8000 fps as I did in the previous tests,"
As many testing shows the simple resizers like Bicubic with upsampling are not limited by RAW computing math performance but mostly with memory management. So it is better to test total performance with source and sink filters attached in the chain. The RAW computing performance mostly important if resizer is running at 1 thread at 1 core only. But it may be not frequent use case at the days of massive multicore consumer CPUs. Though if user runs many scripts at the separate processes at 1 threaded mode it may be an example.
jpsdr
27th May 2026, 22:22
Didn't notice that with threads=1, Prefetch(12) is only twice faster than No Prefetch... Meaning that in resample compute time is small vs memory transfert. In NNEDI3 where compute time is high vs memory transfert, the speed increase is directly linear with the number of cores with at least around 10 if memory is correct (10 threads is around 10 times faster than 1 thread). Here, 12 threads is only 2 times faster...
jpsdr
31st May 2026, 11:36
Some benchmark, and an unexpected result at the end, test i've made to check in fact something else...
Downscale:
ColorBars(3840,2160,"YV24").KillAudio().trim(0,999)
BicubicResizeMT(width/2, height/2, threads=8)
#Prefetch(8)
ColorBars(3840,2160,"YV24").KillAudio().trim(0,999)
BicubicResize(width/2, height/2)
Prefetch(8)
Upscale:
ColorBars(1920,1080,"YV24").KillAudio().trim(0,999)
BicubicResizeMT(width*2, height*2, threads=8)
#Prefetch(8)
Trim, threads or Prefetch values were adjusted according test.
Results:
Core i7 860 (4 cores / 8 threads)
-----------
Windows 7 x86
AVS 2.60 (2.6.0.6)
Downscale
threads=1: 16.74 fps (1 thread)
threads=2: 33.08 fps (3 threads)
threads=4: 42.00 fps (5 threads)
threads=8: 67.32 fps (9 threads)
Upscale
threads=1: 14.17 fps
threads=2: 28.08 fps
threads=4: 46.31 fps
threads=8: 55.84 fps
-----------
Windows 7 x64
AVS+ 3.7.5 (r4657)
Downscale
threads=1,Prefetch(2): 38,91 fps
threads=1,Prefetch(4): 72,45 fps
threads=1,Prefetch(8): 86,47 fps
threads=1,No Prefetch : 19.33 fps
threads=2,No Prefetch : 38.18 fps
threads=4,No Prefetch : 58.84 fps
threads=8,No Prefetch : 83.38 fps
Upscale
threads=1,Prefetch(2): 32.99 fps
threads=1,Prefetch(4): 61.44 fps
threads=1,Prefetch(8): 70.54 fps
threads=1,No Prefetch : 16.42 fps
threads=2,No Prefetch : 32.52 fps
threads=4,No Prefetch : 54.67 fps
threads=8,No Prefetch : 68.17 fps
=========================================
Core i7 6950X (10 cores / 20 threads)
-----------
Windows 7 x86
AVS 2.60 (2.6.0.6)
Downscale
threads=1: 32.27 fps
threads=4: 127.1 fps
threads=10: 176.5 fps
threads=20: 317.0 fps
Upscale
threads=1: 24.18 fps
threads=4: 95.90 fps
threads=10: 133.1 fps
threads=20: 226.0 fps
-----------
Windows 7 x64
AVS+ 3.7.5 (r4657)
Downscale
threads=1,Prefetch(4): 221.1 fps
threads=1,Prefetch(10): 518.3 fps
threads=1,Prefetch(20): 578.3 fps
threads=1,No Prefetch : 58.69 fps
threads=4,No Prefetch : 229.3 fps
threads=10,No Prefetch : 307.1 fps
threads=20,No Prefetch : 542.7 fps
Upscale
threads=1,Prefetch(4): 179.6 fps
threads=1,Prefetch(10): 382.8 fps
threads=1,Prefetch(20): 429.8 fps
threads=1,No Prefetch : 46.02 fps
threads=4,No Prefetch : 179.4 fps
threads=10,No Prefetch : 235.2 fps
threads=20,No Prefetch : 332.2 fps
=========================================
Rizen Threadripper Pro 7975WX (32 cores / 64 threads)
-----------
Windows 10 x64
AVS+ 3.7.5 (r4657)
Downscale
threads=1,Prefetch(4): 935.7 fps (72 threads)
threads=1,Prefetch(32): 4892 fps (100 threads)
threads=1,Prefetch(64): 4804 fps (132 threads)
threads=1,No Prefetch : 236.9 fps (68 threads)
threads=4,No Prefetch : 856.3 fps (72 threads)
threads=32,No Prefetch : 1946 fps (100 threads)
threads=64,No Prefetch : 1531 fps (132 threads)
Upscale
threads=1,Prefetch(4): 858.0 fps (72 threads)
threads=1,Prefetch(32): 2992 fps (100 threads)
threads=1,Prefetch(64): 2718 fps (132 threads)
threads=1,No Prefetch : 270.3 fps (68 threads)
threads=4,No Prefetch : 868.5 fps (72 threads)
threads=32,No Prefetch : 1922 fps (100 threads)
threads=64,No Prefetch : 1491 fps (132 threads)
Downscale, internal Bicubic
No Prefetch: 246.5 fps (68 threads)
Prefetch(4): 708.0 fps (72 threads)
Prefetch(32): 3374 fps (100 threads)
Prefetch(64): 3345 fps (132 threads)
Unexpected result:
Downscale, internal Bicubic (LLVM Zen 4 build of AVS+)
No Prefetch: 246.5 fps (68 threads)
Downscale, internal Bicubic (MSCV AVX512 build of AVS+)
No Prefetch: 163.4 fps (68 threads)
hello_hello
13th June 2026, 00:09
What speed do you have with for :
No prefetch (no Prefecth command in the script and don't set the prefetch parameter) and :
- threads=0
- threads=12
Out of curiosity for compare with your others tests.
Sorry about the very slow reply, but I forgot for some reason. To answer your question.... and once again the fps is an estimate (because when it's running in Wine, AvsPmod doesn't display the resulting fps following the benchmark) but the duration is accurate.
BlankClip(100000, width=400, height=400, pixel_type="YUV444PS")
BicubicResizeMT(width*4,height, threads=?)
Threads=0 122 seconds ~850 fps (it jumps around a bit)
Threads=12 76 seconds ~1300 fps
jpsdr
28th June 2026, 11:43
New version, see first post.
jpsdr
15th July 2026, 09:50
New version, see first post.
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.