GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 28.8% 376 / 0 / 1305
Functions: 36.2% 17 / 0 / 47
Branches: 22.4% 424 / 0 / 1896

filters/resample.cpp
Line Branch Exec Source
1 // Avisynth v2.5. Copyright 2002 Ben Rudiak-Gould et al.
2 // http://avisynth.nl
3
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit
17 // http://www.gnu.org/copyleft/gpl.html .
18 //
19 // Linking Avisynth statically or dynamically with other modules is making a
20 // combined work based on Avisynth. Thus, the terms and conditions of the GNU
21 // General Public License cover the whole combination.
22 //
23 // As a special exception, the copyright holders of Avisynth give you
24 // permission to link Avisynth with independent modules that communicate with
25 // Avisynth solely through the interfaces defined in avisynth.h, regardless of the license
26 // terms of these independent modules, and to copy and distribute the
27 // resulting combined work under terms of your choice, provided that
28 // every copy of the combined work is accompanied by a complete copy of
29 // the source code of Avisynth (the version of Avisynth used to produce the
30 // combined work), being distributed under the terms of the GNU General
31 // Public License plus this exception. An independent module is a module
32 // which is not derived from or based on Avisynth, such as 3rd-party filters,
33 // import and export plugins, or graphical user interfaces.
34
35 #include "resample.h"
36 #ifdef INTEL_INTRINSICS
37 #include "intel/resample_sse.h"
38 #include "intel/resample_avx2.h"
39 #ifdef INTEL_INTRINSICS_AVX512
40 #include "intel/resample_avx512.h"
41 #endif
42 #include "intel/turn_sse.h"
43 #include "intel/turn_avx2.h"
44 #endif
45 #ifdef NEON_INTRINSICS
46 #include "aarch64/turn_neon.h"
47 #endif
48
49 #include <avs/config.h>
50
51 #include "transform.h"
52 #include "turn.h"
53 #include <avs/alignment.h>
54 #include <avs/minmax.h>
55 #include "../convert/convert_planar.h"
56 #include "../convert/convert_helper.h"
57
58 #include <type_traits>
59 #include <algorithm>
60
61 #include "../core/avs_simd_c.h"
62 #include <cassert>
63
64 // Prepares resampling coefficients for end conditions and/or SIMD processing by:
65 // 1. Sets a "real-life" size for the filter, which at small dimensions can be less than the original
66 // 2. Aligning filter_size to 8 or 16 boundary for SIMD efficiency
67 // 3. Right-aligning coefficients within padded arrays to ensure valid access at boundaries
68 //
69 // Before: After right-alignment (filter_size=4, kernel_size=2):
70 //
71 // offset->| offset-2 ->|
72 // [x][y][ ][ ] [0][0][x][y]
73 // ^ ^ ^ ^ ^ ^
74 // | | Off-boundary | |
75 // Values used Values used
76 //
77 // This ensures SIMD instructions can safely load full vectors even at image boundaries
78 // while maintaining correct coefficient positioning and proper zero padding.
79
80
81 23997 static void checkAndSetOverread(int end_pos, SafeLimit& safelimit, int start_pos, int i, int source_size) {
82
2/2
✓ Branch 2 → 3 taken 3975 times.
✓ Branch 2 → 5 taken 20022 times.
23997 if (end_pos >= source_size) {
83
2/2
✓ Branch 3 → 4 taken 982 times.
✓ Branch 3 → 5 taken 2993 times.
3975 if (!safelimit.overread_possible) {
84 982 safelimit.overread_possible = true;
85 982 safelimit.source_overread_offset = start_pos;
86 982 safelimit.source_overread_beyond_targetx = i;
87 }
88 }
89 23997 }
90
91
92 120 void resize_prepare_coeffs(ResamplingProgram* p, IScriptEnvironment* env, int filter_size_alignment) {
93 120 p->filter_size_alignment = filter_size_alignment;
94 120 p->safelimit_filter_size_aligned.overread_possible = false;
95 120 p->safelimit_4_pixels.overread_possible = false;
96 120 p->safelimit_8_pixels.overread_possible = false;
97 120 p->safelimit_16_pixels.overread_possible = false;
98 120 p->safelimit_32_pixels.overread_possible = false;
99 120 p->safelimit_8_pixels_each8th_target.overread_possible = false;
100 120 p->safelimit_16_pixels_each16th_target.overread_possible = false;
101 120 p->safelimit_64_pixels_each32th_target.overread_possible = false; // avx512 uint16_t 32 target pixels, handling 64 source pixels in permutex-based resizers
102 120 p->safelimit_128_pixels_each64th_target.overread_possible = false; // avx512 uint8_t 64 target pixels, handling 128 source pixels in permutex-based resizers
103 // FIXME: found out how to make it general safelimit_SOURCEREADPIXELS_pixels_each_TARGETPIXELSATATIME. Not here, in each frame proecssing for sure.
104
105 // note: filter_size_real was the max(kernel_sizes[])
106 120 int filter_size_aligned = AlignNumber(p->filter_size_real, p->filter_size_alignment);
107 // FIXME: really this needs to be dynamic based on SIMD used in resizer
108
109 120 int target_size_aligned = AlignNumber(p->target_size, ALIGN_RESIZER_TARGET_SIZE);
110
111 // align target_size to X units to allow safe, up to X pixels/cycle in H resizers.
112 // also, this is the coeff table Y-size.
113 // e.g. ALIGN_RESIZER_TARGET_SIZE = 64 allows to access coefficient table elements at
114 // current_coeff + filter_size * 63, if we step current_coeff by 64 * filter_size
115 120 p->target_size_alignment = ALIGN_RESIZER_TARGET_SIZE;
116
117 // Common variables for both float and integer paths
118 120 void* new_coeff = nullptr;
119 120 void* src_coeff = nullptr;
120 120 size_t element_size = 0;
121
122 // allocate for a larger target_size area and nullify the coeffs.
123 // Even between target_size and target_size_aligned.
124
2/2
✓ Branch 4 → 5 taken 12 times.
✓ Branch 4 → 19 taken 108 times.
120 if (p->bits_per_pixel == 32) {
125 12 element_size = sizeof(float);
126 12 src_coeff = p->pixel_coefficient_float;
127 12 new_coeff = env->Allocate(element_size * target_size_aligned * filter_size_aligned, 64, AVS_NORMAL_ALLOC);
128
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 12 times.
12 if (!new_coeff) {
129 env->Free(new_coeff);
130 env->ThrowError("Could not reserve memory in a resampler.");
131 }
132 24 std::fill_n((float*)new_coeff, target_size_aligned * filter_size_aligned, 0.0f);
133 }
134 else {
135 108 element_size = sizeof(short);
136 108 src_coeff = p->pixel_coefficient;
137 108 new_coeff = env->Allocate(element_size * target_size_aligned * filter_size_aligned, 64, AVS_NORMAL_ALLOC);
138
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 23 taken 108 times.
108 if (!new_coeff) {
139 env->Free(new_coeff);
140 env->ThrowError("Could not reserve memory in a resampler.");
141 }
142 108 memset(new_coeff, 0, element_size * target_size_aligned * filter_size_aligned);
143 }
144
145 120 const int last_line = p->source_size - 1;
146
147 // Process coefficients - common code for both types
148
2/2
✓ Branch 60 → 25 taken 4421 times.
✓ Branch 60 → 61 taken 120 times.
4541 for (int i = 0; i < p->target_size; i++) {
149 4421 const int kernel_size = p->kernel_sizes[i];
150 4421 const int offset = p->pixel_offset[i];
151 4421 const int last_coeff_index = offset + p->filter_size_real - 1;
152
2/2
✓ Branch 27 → 28 taken 236 times.
✓ Branch 27 → 29 taken 4185 times.
4421 const int shift_needed = last_coeff_index > last_line ? p->filter_size_real - kernel_size : 0;
153
154 // In order to be able to read 'filter_size_real' number of coefficients safely at the
155 // image boundaries, we right-align the actual coefficients within the allocated filter
156 // size. This will require adjusting (shifting) the pixel offsets as well, and increasing
157 // the smaller kernel sizes, to reflect the new effective size: filter_size_real.
158
159 // Copy coefficients with appropriate shift
160
2/2
✓ Branch 30 → 31 taken 329 times.
✓ Branch 30 → 34 taken 4092 times.
4421 if (p->bits_per_pixel == 32) {
161 329 float* dst = (float*)new_coeff + i * filter_size_aligned;
162 329 float* src = (float*)src_coeff + i * p->filter_size;
163
2/2
✓ Branch 33 → 32 taken 818 times.
✓ Branch 33 → 37 taken 329 times.
1147 for (int j = 0; j < kernel_size; j++) {
164 818 dst[j + shift_needed] = src[j];
165 }
166 }
167 else {
168 4092 short* dst = (short*)new_coeff + i * filter_size_aligned;
169 4092 short* src = (short*)src_coeff + i * p->filter_size;
170
2/2
✓ Branch 36 → 35 taken 45080 times.
✓ Branch 36 → 37 taken 4092 times.
49172 for (int j = 0; j < kernel_size; j++) {
171 45080 dst[j + shift_needed] = src[j];
172 }
173 }
174
175 // Update offsets and kernel sizes
176 4421 p->pixel_offset[i] -= shift_needed;
177 4421 p->kernel_sizes[i] += shift_needed;
178
179 // left side, already right padded with zero coeffs, we can
180 // change to actual width to the common one
181
2/2
✓ Branch 40 → 41 taken 233 times.
✓ Branch 40 → 43 taken 4188 times.
4421 if(p->kernel_sizes[i] < p->filter_size_real)
182 233 p->kernel_sizes[i] = p->filter_size_real;
183
184 // In a horizontal resizer, when reading filter_size_alignment pixels,
185 // we must protect against source scanline overread.
186 // Using this not in only 32-bit float resizers is new in 3.7.4.
187 4421 const int start_pos = p->pixel_offset[i];
188 4421 const int end_pos = start_pos + p->filter_size_real - 1;
189 4421 if (end_pos >= p->source_size) {
190 // This issue has already been fixed, so it cannot occur.
191 }
192
193 // Check for SIMD optimization limits and record first danger positions.
194 // If reading N pixels starting from `start_pos` would reach past the end
195 // of the source (>= source_size), register that first occurrence for
196 // the corresponding SafeLimit entry so resizers can avoid unsafe wide loads.
197
198 4421 checkAndSetOverread(start_pos + filter_size_aligned - 1, p->safelimit_filter_size_aligned, start_pos, i, p->source_size);
199 4421 checkAndSetOverread(start_pos + 4 - 1, p->safelimit_4_pixels, start_pos, i, p->source_size);
200 4421 checkAndSetOverread(start_pos + 8 - 1, p->safelimit_8_pixels, start_pos, i, p->source_size);
201 4421 checkAndSetOverread(start_pos + 16 - 1, p->safelimit_16_pixels, start_pos, i, p->source_size);
202 4421 checkAndSetOverread(start_pos + 32 - 1, p->safelimit_32_pixels, start_pos, i, p->source_size);
203 // for permutex-based AVX2 ks4 float H resizers, where we read 8 pixels at a time exactly from
204 // start_pos of each Nth pixel output block
205
2/2
✓ Branch 50 → 51 taken 591 times.
✓ Branch 50 → 53 taken 3830 times.
4421 if (i % 8 == 0) {
206 591 checkAndSetOverread(start_pos + 8 - 1, p->safelimit_8_pixels_each8th_target, start_pos, i, p->source_size);
207 591 checkAndSetOverread(start_pos + 16 - 1, p->safelimit_16_pixels_each8th_target, start_pos, i, p->source_size);
208 }
209
2/2
✓ Branch 53 → 54 taken 341 times.
✓ Branch 53 → 55 taken 4080 times.
4421 if (i % 16 == 0)
210 341 checkAndSetOverread(start_pos + 16 - 1, p->safelimit_16_pixels_each16th_target, start_pos, i, p->source_size);
211
2/2
✓ Branch 55 → 56 taken 213 times.
✓ Branch 55 → 57 taken 4208 times.
4421 if (i % 32 == 0) // avx512 uint16_t 32 target pixels, handling 64 source pixels
212 213 checkAndSetOverread(start_pos + 64 - 1, p->safelimit_64_pixels_each32th_target, start_pos, i, p->source_size);
213
2/2
✓ Branch 57 → 58 taken 156 times.
✓ Branch 57 → 59 taken 4265 times.
4421 if (i % 64 == 0) // avx512 uint8_t 64 target pixels, handling 128 source pixels
214 156 checkAndSetOverread(start_pos + 128 - 1, p->safelimit_128_pixels_each64th_target, start_pos, i, p->source_size);
215
216 }
217
218 // from now on, kernel_sizes[] has no role, each is filter_size_real
219 120 p->kernel_sizes.clear();
220
221 // Fill the extra offset after target_size with fake values.
222 // Our aim is to have a safe, up to 8-32 pixels/cycle simd loop for V and specific H resizers.
223 // Their coeffs will be 0, so they don't count if such coeffs
224 // are multiplied with invalid, though existing pixels.
225
2/2
✓ Branch 62 → 63 taken 99 times.
✓ Branch 62 → 69 taken 21 times.
120 if (p->target_size < target_size_aligned) {
226 99 p->pixel_offset.resize(target_size_aligned);
227 99 int last_offset = p->pixel_offset[p->target_size - 1];
228
2/2
✓ Branch 68 → 66 taken 5563 times.
✓ Branch 68 → 69 taken 99 times.
5662 for (int i = p->target_size; i < target_size_aligned; ++i) {
229 5563 p->pixel_offset[i] = last_offset; // repeat last valid offset, helps permutex-based H resizers to stay within valid distances
230 }
231 }
232
233 // Free old coefficients and assign new ones
234
2/2
✓ Branch 69 → 70 taken 12 times.
✓ Branch 69 → 72 taken 108 times.
120 if (p->bits_per_pixel == 32) {
235 12 env->Free(p->pixel_coefficient_float);
236 12 p->pixel_coefficient_float = (float*)new_coeff;
237 }
238 else {
239 108 env->Free(p->pixel_coefficient);
240 108 p->pixel_coefficient = (short*)new_coeff;
241 }
242
243 120 p->filter_size = filter_size_aligned;
244 // by now coeffs[old_filter_size][target_size] was copied and padded into coeffs[new_filter_size][target_size]
245 120 }
246
247 /***************************************
248 ***** Vertical Resizer Assembly *******
249 ***************************************/
250
251 template<typename pixel_t>
252 static void resize_v_planar_pointresize(BYTE* dst, const BYTE* src, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int target_height, int bits_per_pixel)
253 {
254 AVS_UNUSED(bits_per_pixel);
255
256 pixel_t* src0 = (pixel_t*)src;
257 pixel_t* dst0 = (pixel_t*)dst;
258 src_pitch = src_pitch / sizeof(pixel_t);
259 dst_pitch = dst_pitch / sizeof(pixel_t);
260
261 for (int y = 0; y < target_height; y++) {
262 int offset = program->pixel_offset[y];
263 const pixel_t* src_ptr = src0 + src_pitch * offset;
264
265 memcpy(dst0, src_ptr, width * sizeof(pixel_t));
266
267 dst0 += dst_pitch;
268 }
269 }
270
271 // This C implementation isn't optimized for auto-vectorization,
272 // But on x86 MSVC SSE2 settings for 8 bit pixel types it performs better than our
273 // vectorizer-friendly version.
274 // Other compilers benefit from vectorizing by a huge margin for every case.
275 // The vector code which replaced this is already 2x faster for 10-16 bits processing
276 // and achieves a 6x speedup for 32-bit operations. LLVM has even an additional 1.5x-4x speedup
277 // compared to MSVC.
278 // Kept for reference, supports 8, 10-16 and 32 bit pixel types.
279 template<typename pixel_t, bool lessthan16bit>
280 static void resize_v_c_planar(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int target_height, int bits_per_pixel)
281 {
282
283 int filter_size = program->filter_size;
284
285 typedef typename std::conditional < std::is_floating_point<pixel_t>::value, float, short>::type coeff_t;
286 coeff_t* current_coeff;
287
288 if (!std::is_floating_point<pixel_t>::value)
289 current_coeff = (coeff_t*)program->pixel_coefficient;
290 else
291 current_coeff = (coeff_t*)program->pixel_coefficient_float;
292
293 pixel_t* src = (pixel_t*)src8;
294 pixel_t* dst = (pixel_t*)dst8;
295 src_pitch = src_pitch / sizeof(pixel_t);
296 dst_pitch = dst_pitch / sizeof(pixel_t);
297
298 pixel_t limit = 0;
299 if (!std::is_floating_point<pixel_t>::value) { // floats are unscaled and uncapped
300 if constexpr (sizeof(pixel_t) == 1) limit = 255;
301 else if constexpr (sizeof(pixel_t) == 2) limit = pixel_t((1 << bits_per_pixel) - 1);
302 }
303
304 // for 16 bits only
305 const short shifttosigned_short = -32768;
306 const int shiftfromsigned_int = 32768 << FPScale16bits;
307
308 for (int y = 0; y < target_height; y++) {
309 int offset = program->pixel_offset[y];
310 const int kernel_size = program->kernel_sizes[y];
311 const pixel_t* src_ptr = src + src_pitch * offset;
312
313 // perhaps helps vectorizing decision
314 const int ksmod4 = kernel_size / 4 * 4;
315
316 for (int x = 0; x < width; x++) {
317 if constexpr (std::is_floating_point<pixel_t>::value) {
318 const float* src2_ptr = src_ptr + x;
319
320 float result = 0;
321 for (int i = 0; i < ksmod4; i += 4) {
322 result += *(src2_ptr + 0 * src_pitch) * current_coeff[i + 0];
323 result += *(src2_ptr + 1 * src_pitch) * current_coeff[i + 1];
324 result += *(src2_ptr + 2 * src_pitch) * current_coeff[i + 2];
325 result += *(src2_ptr + 3 * src_pitch) * current_coeff[i + 3];
326 src2_ptr += 4 * src_pitch;
327 }
328 for (int i = ksmod4; i < kernel_size; i++) {
329 result += *src2_ptr * current_coeff[i];
330 src2_ptr += src_pitch;
331 }
332 dst[x] = result;
333 }
334 else if constexpr (sizeof(pixel_t) == 2) {
335 // theoretically, no need for int64 accumulator,
336 // sum of coeffs is 1.0 that is (1 << FPScale16bits) in integer arithmetic
337 const uint16_t* src2_ptr = src_ptr + x;
338 int result = 1 << (FPScale16bits - 1); // rounder;
339 for (int i = 0; i < ksmod4; i += 4) {
340 int val;
341 val = *(src2_ptr + 0 * src_pitch);
342 if constexpr (!lessthan16bit)
343 val = val + shifttosigned_short;
344 result += val * current_coeff[i + 0];
345
346 val = *(src2_ptr + 1 * src_pitch);
347 if constexpr (!lessthan16bit)
348 val = val + shifttosigned_short;
349 result += val * current_coeff[i + 1];
350
351 val = *(src2_ptr + 2 * src_pitch);
352 if constexpr (!lessthan16bit)
353 val = val + shifttosigned_short;
354 result += val * current_coeff[i + 2];
355
356 val = *(src2_ptr + 3 * src_pitch);
357 if constexpr (!lessthan16bit)
358 val = val + shifttosigned_short;
359 result += val * current_coeff[i + 3];
360
361 src2_ptr += 4 * src_pitch;
362 }
363 for (int i = ksmod4; i < kernel_size; i++) {
364 int val = *src2_ptr;
365 if constexpr (!lessthan16bit)
366 val = val + shifttosigned_short;
367 result += val * current_coeff[i];
368 src2_ptr += src_pitch;
369 }
370 if constexpr (!lessthan16bit)
371 result = result + shiftfromsigned_int;
372 result = result >> FPScale16bits;
373 result = result > limit ? limit : result < 0 ? 0 : result; // clamp 10..16 bits
374 dst[x] = (uint16_t)result;
375 }
376 else if constexpr (sizeof(pixel_t) == 1) {
377 const uint8_t* src2_ptr = src_ptr + x;
378 int result = 1 << (FPScale8bits - 1); // rounder;
379 for (int i = 0; i < ksmod4; i += 4) {
380 short val;
381 val = *(src2_ptr + 0 * src_pitch);
382 result += val * current_coeff[i + 0];
383
384 val = *(src2_ptr + 1 * src_pitch);
385 result += val * current_coeff[i + 1];
386
387 val = *(src2_ptr + 2 * src_pitch);
388 result += val * current_coeff[i + 2];
389
390 val = *(src2_ptr + 3 * src_pitch);
391 result += val * current_coeff[i + 3];
392
393 src2_ptr += 4 * src_pitch;
394 }
395 for (int i = ksmod4; i < kernel_size; i++) {
396 short val = *src2_ptr;
397 result += val * current_coeff[i];
398 src2_ptr += src_pitch;
399 }
400 result = result >> FPScale8bits;
401 result = result > limit ? limit : result < 0 ? 0 : result; // clamp 8 bits
402 dst[x] = (uint8_t)result;
403 }
404 }
405
406 dst += dst_pitch;
407 current_coeff += filter_size;
408 }
409 }
410
411
412 /*
413
414 Benchmarks.
415
416 SetMaxCPU("none")
417 #SetMaxCPU("SSSE3")
418 #SetMaxCPU("AVX2")
419 ColorbarsHD(200,200, pixel_type="YUV444P8") # P8, P10, P16, PS
420
421 wmul=2 # >1: horizontal resizer test
422 hmul=1 # >1: vertical resizer test
423
424 avs=LanczosResize(width*wmul, height*hmul, taps=16)
425 avsr=z_ConvertFormat(width=width*wmul, height=height*hmul, resample_filter="lanczos", filter_param_a=16)
426 fmtconv=fmtc_resample(w=width*wmul, h=height*hmul, kernel="lanczos", taps=16)
427 avs
428
429 Figures in general has no place in source code but it is useful to have a clue
430 and see the huge differences between compilers and code variants.
431 Also, only mazochists want to test integer optimization with MSVC (x86). Wasted time.
432 Use llvm on Arm, clangcl/llvm on Windows x86.
433
434 Horizontals Hor+Vert Verticals
435 [8] [10-14] [16] [32] [8] [10] [8] [10-14] [16] [32]
436 177 170 101 208 262 486 417 217 C-RaspberryPi5 gcc 12.2 (code variant)
437 137 469 88 208 C-RaspberryPi5 gcc 12.2 (code variant)
438 227 218 147 178 C-RaspberryPi5 gcc 12.2 no vector attrib ??! Quicker than vector attrib version gcc not recommended
439 416 611 579 404 128 186 362 609 556 624 C-RaspberryPi5 llvm 14 vector attrib
440 90 185 94 403 C-RaspberryPi5 llvm 14 vector attrib + integer madd@H
441 180 182 162 67 213 212 206 639 C-RaspberryPi5 llvm 14 no vector attrib
442 942 985 982 993 976 1114 982 1920 C-ClangCl in 3.7.6 VS2026 SSE2 (MSVC tuned simd_c and even more tuned vector-friendly code)
443 1270 1238 1186 1550 1555 1560 3670 C-ClangCl in VS2022 AVX2
444 1051 1126 1102 C-Intel ICX 2025 SSE2
445 1513 2355 1560 1128 1969 C-Intel ICX 2025 SSE4.2 smart madd!
446 1938 2413 1775 1061 442 453 1136 1126 1037 3511 C-Intel ICX 2025 AVX2
447 212 188 187 264 73 64 223 195 198 268 C-MSVC SSE2 3.7.3
448 417 463 360 449 424 384 352 C-MSVC SSE2 3.7.4 (some unrolling vs. 3.7.3)
449 215 215 97 928 79 79 220 744 96 1951 C-MSVC SSE2 (zero optim seen in asm on 8-16, did not tolerate vector-friendly code)
450 595 638 634 976 552 382 95 1940 C-MSVC SSE2 3.7.6 (MSVC tuned simd_c and vector-friendly code)
451 591 616 620 700 670 518 1950 C-MSVC SSE2 3.7.6 (MSVC tuned simd_c and even more tuned vector-friendly code)
452 853 913 924 1107 880 860 713 3777 C-MSVC AVX2 3.7.6 (MSVC tuned simd_c and vector-friendly code)
453 201 206 99 C-MSVC AVX2 3.7.4 (zero optim seen in asm also on 8-16, not even using SSE2 xmm registers)
454 597 631 651 C-Intel SSE4.2 3.7.4 code
455 1183 1193 889 C-Intel AVX2 3.7.4 code
456 5600 2140 SIMD-avsresize (AVX2 or AVX512?)
457 2260 840 SIMD-fmtconv (16 bit output for 8 bits)
458 4578 2614 2560 2250 1490 4220 4534 3887 3570 SIMD-MSVC AVX2 3.7.3 (horizontal was memory-boundary unsafe)
459 3631 3505 3221 2344 1291 1354 3804 4466 3855 2260 SIMD-MSVC AVX2 3.7.4 Float vertical regression - no time to finish
460 3720 3478 3130 2385 1566 1480 5014 5288 5077 3810 SIMD-MSVC AVX2 + incrementing offsets in V, 20-25% gain in integer verticals
461 4730 4612 4233 2487 1390 1471 3792 4476 4380 3942 SIMD-ClangCl AVX2, verticals behind MSVC by surprise
462 2373 2181 1893 1306 868 723 2660 2137 2670 1886 SIMD-MSVC SSSE3 + incrementing offsets in V, 20-25% gain in integer verticals
463 2294 2979 2595 1460 859 976 2623 2865 2625 1962 SIMD-Intel ICX 2025 SSSE3
464 4395 4616 4160 2570 1664 1720 5110 5870 5085 2999 SIMD-Intel ICX 2025 AVX2 Surprisingly slow at vertical float FIXME, slower than C :)
465 4755 4453 4255 3540 1447 1537 3560 4592 3920 7837 SIMD-ClangCL 3.7.6
466
467 * float has different optimization: 8 pixels 2 coeffs
468 ** on aarch64 the float benchmarks included a ConvertBits(8) at the end.
469
470 Non-x86 platforms are the focus of this comparison, as they lack SIMD optimized paths.
471 x86 platforms have their own SIMD optimized paths and do not use C.
472
473 Compilers can give totally different results even after some reordering of the code,
474 See gcc aarch64: the 8 bit case dropped to 2/3 speed while 16 bits increased by a factor of 250%
475 depending on where I put a line.
476
477 Considerations for aarch64 (Armv8a, includes neon, e.g. RPi5 with gcc 12.2, llvm 14.0.6)
478 - gcc is not recommended, their vectorizer is either broken or not yet ready.
479 Using vector attribute gave slower code.
480 - llvm gave consistent fast results.
481 - Vertical resampler: aarch64 + GCC, (8-16 bits) the 4-pixel 4-coefficient version performs best.
482
483 On x86 platforms MSVC is unusable regarding integer vectorization, the more the code helps to
484 recognize vectorization patterns the slower assembly it produces, e.g. horizontal resizer AVX2 16 bit:
485 MSVC 99 fps (634 after tuning), MSVC+clangcl: 1186 fps, Intel+LLVM: 1775 fps. Joke. We use
486 only for internal testing and reference, not for actual builds, everithing is SIMD optimized in Avisynth for x86.
487
488 MSVC on x86 is only capable of vectorizing 32 bit float code, and even then it is not the fastest.
489 There is no reason to not use clangcl for x86 Visual Studio builds. It comes officially with VS2022 for free.
490 2026 update: simd_c was tuned for better MSVC auto-vectorization.
491
492 */
493
494 // This is a vectorizer-friendly version of the above function.
495 template<typename pixel_t, bool lessthan16bit>
496 void resize_v_c_planar_uint8_16_t_auto_vectorized(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int target_height, int bits_per_pixel) {
497
498 const short* AVS_RESTRICT current_coeff = program->pixel_coefficient;
499
500 auto src = reinterpret_cast<const pixel_t*>(src8);
501 auto dst = reinterpret_cast<pixel_t * AVS_RESTRICT>(dst8);
502 src_pitch = src_pitch / sizeof(pixel_t);
503 dst_pitch = dst_pitch / sizeof(pixel_t);
504
505 int limit = 0;
506 if constexpr (sizeof(pixel_t) == 1) limit = 255;
507 else if constexpr (sizeof(pixel_t) == 2) limit = pixel_t((1 << bits_per_pixel) - 1);
508
509 // for 16 bits only
510 [[maybe_unused]] Int16x8 shifttosigned_short(-32768);
511 [[maybe_unused]] const Int32x4 shiftfromsigned_int(32768 << FPScale16bits);
512
513 const int filter_size = program->filter_size;
514 constexpr int fixpoint_scaler_bits = sizeof(pixel_t) == 2 ? FPScale16bits : FPScale8bits;
515 const int rounder = 1 << (fixpoint_scaler_bits - 1);
516
517 // 8-16 SIMD padding of coeffs is not usable here, work with single coeffs
518
519 const int kernel_size = program->filter_size_real;
520
521 const int ksmod4 = kernel_size / 4 * 4;
522
523 for (int y = 0; y < target_height; y++) {
524 int offset = program->pixel_offset[y];
525 const pixel_t* src_ptr = src + offset * src_pitch;
526
527 // 4 pixels at a time
528 for (int x = 0; x < width; x += 4) {
529 Int32x4 result(rounder); // master accumulator and the initial first coeff part
530 Int32x4 result_2(0);
531 Int32x4 result_3(0);
532 Int32x4 result_4(0);
533 const pixel_t* AVS_RESTRICT src2_ptr = src_ptr + x; // __restrict here
534 int i = 0;
535 // Process coefficients in pairs or quads for better instruction parallelism.
536 // Depending on platform and compiler, results may vary.
537 // Since on Intel we have SIMD optimized paths, we decide on the
538 // results of a not-yet-optimized aarch64 platform: takeout is 4 pix 4 coeff
539 for (; i < ksmod4; i += 4) {
540
541 Int32x4 src_1, src_2, src_3, src_4;
542
543 const int coeff_1 = current_coeff[i];
544 const int coeff_2 = current_coeff[i + 1];
545 const int coeff_3 = current_coeff[i + 2];
546 const int coeff_4 = current_coeff[i + 3];
547
548 if constexpr (sizeof(pixel_t) == 1) {
549 // uint8_t
550 auto src8_1 = Uint8x4::from_ptr(src2_ptr);
551 auto src8_2 = Uint8x4::from_ptr(src2_ptr + src_pitch);
552 auto src8_3 = Uint8x4::from_ptr(src2_ptr + 2 * src_pitch);
553 auto src8_4 = Uint8x4::from_ptr(src2_ptr + 3 * src_pitch);
554
555 src_1 = Int32x4::convert_from(src8_1);
556 src_2 = Int32x4::convert_from(src8_2);
557 src_3 = Int32x4::convert_from(src8_3);
558 src_4 = Int32x4::convert_from(src8_4);
559 }
560 else {
561 // uint16_t
562 // only lo 64 bit (4x16) is used
563 auto src16_1 = Int16x8::from_ptr_lo(reinterpret_cast<const short* AVS_RESTRICT>(src2_ptr));
564 auto src16_2 = Int16x8::from_ptr_lo(reinterpret_cast<const short* AVS_RESTRICT>(src2_ptr + src_pitch));
565 auto src16_3 = Int16x8::from_ptr_lo(reinterpret_cast<const short* AVS_RESTRICT>(src2_ptr + 2 * src_pitch));
566 auto src16_4 = Int16x8::from_ptr_lo(reinterpret_cast<const short* AVS_RESTRICT>(src2_ptr + 3 * src_pitch));
567
568 // Turn unsigned to signed 16 bit, will be adjusted back before scaling back and storing.
569 // Since there's a little hope that short*short pattern is recognized by the compiler, though
570 // this is different from the horizontal case where hadd can be used.
571 if constexpr (!lessthan16bit) {
572 src16_1 += shifttosigned_short;
573 src16_2 += shifttosigned_short;
574 src16_3 += shifttosigned_short;
575 src16_4 += shifttosigned_short;
576 }
577 // widen short->int
578 src_1 = Int32x4::convert_from_lo(src16_1);
579 src_2 = Int32x4::convert_from_lo(src16_2);
580 src_3 = Int32x4::convert_from_lo(src16_3);
581 src_4 = Int32x4::convert_from_lo(src16_4);
582 }
583
584 result += src_1 * coeff_1;
585 result_2 += src_2 * coeff_2;
586 result_3 += src_3 * coeff_3;
587 result_4 += src_4 * coeff_4;
588
589 src2_ptr += 4 * src_pitch;
590 }
591
592 result += result_2;
593 result_3 += result_4;
594 result += result_3;
595
596 // rest zero or one
597 for (; i < kernel_size; ++i) {
598 Int32x4 src;
599 const int a_coeff = current_coeff[i];
600 if constexpr (sizeof(pixel_t) == 1) {
601 // uint8_t
602 src.load_from_any_intptr(src2_ptr);
603 }
604 else {
605 // uint16_t
606 auto src16 = Int16x8::from_ptr_lo(reinterpret_cast<const short* AVS_RESTRICT>(src2_ptr));
607 if constexpr (!lessthan16bit) {
608 src16 += shifttosigned_short;
609 }
610 src = Int32x4::convert_from_lo(src16); // widen short->int
611 }
612
613 result += src * a_coeff;
614
615 src2_ptr += src_pitch;
616 }
617
618 // back to unsigned 16 bit for exact 16 bit source
619 if constexpr (sizeof(pixel_t) == 2 && !lessthan16bit) {
620 result += shiftfromsigned_int;
621 }
622
623 result >>= fixpoint_scaler_bits;
624
625 if constexpr (sizeof(pixel_t) == 1) {
626 Uint8x4 result8;
627 convert_and_saturate_int32x4_to_uint8x4(result, result8);
628 result8.store(dst + x);
629 }
630 else {
631 Uint16x4 result16;
632 if constexpr (lessthan16bit) {
633 convert_and_saturate_int32x4_to_uint16x4_limit(result, result16, limit);
634 }
635 else {
636 convert_and_saturate_int32x4_to_uint16x4(result, result16);
637 }
638 result16.store(dst + x);
639 }
640 }
641
642 dst += dst_pitch;
643 current_coeff += filter_size;
644 }
645 }
646
647
648 static void resize_v_c_planar_float_auto_vectorized(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int target_height, int bits_per_pixel) {
649
650 const float* AVS_RESTRICT current_coeff = program->pixel_coefficient_float;
651
652 auto src = reinterpret_cast<const float*>(src8);
653 auto dst = reinterpret_cast<float* AVS_RESTRICT>(dst8);
654 src_pitch = src_pitch / sizeof(float);
655 dst_pitch = dst_pitch / sizeof(float);
656
657 const int filter_size = program->filter_size;
658
659 // 8-16 SIMD padding of coeffs is not usable here, work with single coeffs
660 const int kernel_size = program->filter_size_real;
661 const int ksmod2 = kernel_size / 2 * 2; // Ensure kernel_size is processed in multiples of 2
662
663 for (int y = 0; y < target_height; y++) {
664 int offset = program->pixel_offset[y];
665 const float* src_ptr = src + offset * src_pitch;
666
667 for (int x = 0; x < width; x += 8) {
668 // This function written in vectorizer-friendly C unexpectedly outperformed
669 // our hand-written SSE2 version.
670 // The reason: Float8 class is probably using two 128-bit XMM registers
671 // when compiled with SSE2 enabled by MSVC, processing 8 floats per iteration.
672 // Our original SSE2 function only processed 4 pixels at once, explaining the
673 // performance difference. We've since updated the SSE2 version to also process
674 // 8 pixels simultaneously using two XMM registers.
675 // Processing two coefficients further increases the throughput.
676 Float8 result(0.f);
677 Float8 result_2(0.f);
678 const float* AVS_RESTRICT src2_ptr = src_ptr + x;
679
680 for (int i = 0; i < ksmod2; i += 2) {
681 const float coeff_1 = current_coeff[i];
682 const float coeff_2 = current_coeff[i + 1];
683 Float8 src_1 = Float8::from_ptr(src2_ptr);
684 Float8 src_2 = Float8::from_ptr(src2_ptr + src_pitch);
685 result += src_1 * coeff_1;
686 result_2 += src_2 * coeff_2;
687
688 src2_ptr += 2 * src_pitch;
689 }
690
691 result += result_2;
692
693 // Process remaining coefficients if kernel_size is odd
694 if (ksmod2 < kernel_size) {
695 const float a_coeff = current_coeff[ksmod2];
696 Float8 src = Float8::from_ptr(src2_ptr);
697 result += src * a_coeff;
698 }
699
700 // no rounding, no clamp
701 result.store(dst + x);
702 }
703
704 dst += dst_pitch;
705 current_coeff += filter_size;
706 }
707 }
708
709
710 /***************************************
711 ********* Horizontal Resizer** ********
712 ***************************************/
713
714 // Only <float> is used, which got some SIMD-C optimizations.
715 // On x86 MSVC the 8-16 bit versions may perform better due to their extremely poor integer
716 // vectorization capabilities.
717 // But gcc and llvm (clangcl) compilers perform very good, their code from the other,
718 // vector-fiendly versions are excellent, as expected in 2025.
719 // Luckily, on x86 there are handcrafted SIMD versions for all 8, 10-16 and 32 bit pixel types.
720
721 template<typename pixel_t, bool lessthan16bit>
722 static void resize_h_c_planar(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int height, int bits_per_pixel) {
723 int filter_size = program->filter_size;
724
725 typedef typename std::conditional < std::is_floating_point<pixel_t>::value, float, short>::type coeff_t;
726 const coeff_t* AVS_RESTRICT current_coeff;
727
728 pixel_t limit = 0;
729 if (!std::is_floating_point<pixel_t>::value) { // floats are unscaled and uncapped
730 if constexpr (sizeof(pixel_t) == 1) limit = 255;
731 else if constexpr (sizeof(pixel_t) == 2) limit = pixel_t((1 << bits_per_pixel) - 1);
732 }
733
734 src_pitch = src_pitch / sizeof(pixel_t);
735 dst_pitch = dst_pitch / sizeof(pixel_t);
736
737 pixel_t* src = (pixel_t*)src8;
738 pixel_t* dst = (pixel_t*)dst8;
739
740 // for 16 bits only
741 const short shifttosigned_short = -32768;
742 const int shiftfromsigned_int = 32768 << FPScale16bits;
743
744 // perhaps helps vectorizing decision
745 const int kernel_size = program->filter_size_real;
746 const int ksmod4 = kernel_size / 4 * 4;
747 const int ksmod8 = kernel_size / 8 * 8;
748
749 // external loop y is much faster
750 for (int y = 0; y < height; y++) {
751 if (!std::is_floating_point<pixel_t>::value)
752 current_coeff = (const coeff_t* AVS_RESTRICT)program->pixel_coefficient;
753 else
754 current_coeff = (const coeff_t* AVS_RESTRICT)program->pixel_coefficient_float;
755
756 pixel_t* AVS_RESTRICT dst2_ptr = dst + y * dst_pitch;
757 const pixel_t* src_ptr = src + y * src_pitch;
758
759 for (int x = 0; x < width; x++) {
760 int begin = program->pixel_offset[x];
761 const pixel_t* AVS_RESTRICT src2_ptr = src_ptr + begin;
762
763 if constexpr (std::is_floating_point<pixel_t>::value) {
764 Float4 result(0.f);
765 for (int i = 0; i < ksmod4; i += 4) {
766 Float4 src = Float4::from_ptr(src2_ptr + i);
767 Float4 coeff = Float4::from_ptr(current_coeff + i);
768 result += src * coeff;
769 }
770 float result_single = result.horiz_add_float();
771 for (int i = ksmod4; i < kernel_size; i++) {
772 result_single += src2_ptr[i] * current_coeff[i];
773 }
774 dst2_ptr[x] = result_single;
775 }
776 else if constexpr (sizeof(pixel_t) == 2) {
777 // theoretically, no need for int64 accumulator,
778 // sum of coeffs is 1.0 that is (1 << FPScale16bits) in integer arithmetic
779 int result = 1 << (FPScale16bits - 1); // rounder;
780 for (int i = 0; i < ksmod4; i += 4) {
781 int val;
782 val = src2_ptr[i+0];
783 if constexpr (!lessthan16bit)
784 val = val + shifttosigned_short;
785 result += val * current_coeff[i+0];
786
787 val = src2_ptr[i+1];
788 if constexpr (!lessthan16bit)
789 val = val + shifttosigned_short;
790 result += val * current_coeff[i+1];
791
792 val = src2_ptr[i + 2];
793 if constexpr (!lessthan16bit)
794 val = val + shifttosigned_short;
795 result += val * current_coeff[i + 2];
796
797 val = src2_ptr[i + 3];
798 if constexpr (!lessthan16bit)
799 val = val + shifttosigned_short;
800 result += val * current_coeff[i + 3];
801 }
802 for (int i = ksmod4; i < kernel_size; i++) {
803 int val = src2_ptr[i];
804 if constexpr (!lessthan16bit)
805 val = val + shifttosigned_short;
806 result += val * current_coeff[i];
807 }
808 if constexpr (!lessthan16bit)
809 result = result + shiftfromsigned_int;
810 result = result >> FPScale16bits;
811 result = result > limit ? limit : result < 0 ? 0 : result; // clamp 10..16 bits
812 dst2_ptr[x] = (uint16_t)result;
813 }
814 else if constexpr (sizeof(pixel_t) == 1) {
815 int result = 1 << (FPScale8bits - 1); // rounder;
816 for (int i = 0; i < ksmod8; i += 8) {
817 short val;
818 val = src2_ptr[i + 0];
819 result += val * current_coeff[i + 0];
820 val = src2_ptr[i + 1];
821 result += val * current_coeff[i + 1];
822 val = src2_ptr[i + 2];
823 result += val * current_coeff[i + 2];
824 val = src2_ptr[i + 3];
825 result += val * current_coeff[i + 3];
826 val = src2_ptr[i + 4];
827 result += val * current_coeff[i + 4];
828 val = src2_ptr[i + 5];
829 result += val * current_coeff[i + 5];
830 val = src2_ptr[i + 6];
831 result += val * current_coeff[i + 6];
832 val = src2_ptr[i + 7];
833 result += val * current_coeff[i + 7];
834 }
835 for (int i = ksmod8; i < ksmod4; i += 4) {
836 short val;
837 val = src2_ptr[i + 0];
838 result += val * current_coeff[i + 0];
839 val = src2_ptr[i + 1];
840 result += val * current_coeff[i + 1];
841 val = src2_ptr[i + 2];
842 result += val * current_coeff[i + 2];
843 val = src2_ptr[i + 3];
844 result += val * current_coeff[i + 3];
845 val = src2_ptr[i + 4];
846 }
847 for (int i = ksmod4; i < kernel_size; i++) {
848 short val = src2_ptr[i];
849 result += val * current_coeff[i];
850 }
851 result = result >> FPScale8bits;
852 result = result > limit ? limit : result < 0 ? 0 : result; // clamp 8 bits
853 dst2_ptr[x] = (uint8_t)result;
854 }
855 current_coeff += filter_size;
856 }
857 }
858 }
859
860 // Vectorizer-friendly 8-16 bit horizontal resampler
861
862 // 8 pixel instead of real-SIMD 16 to ease register pressure
863 // and the make the compiler task easier
864 template<typename pixel_t, bool lessthan16bit>
865 AVS_FORCEINLINE static void process_two_8pixels_h_uint8_16_core(const pixel_t* AVS_RESTRICT src_ptr, const short* AVS_RESTRICT current_coeff, Int32x4& AVS_RESTRICT result, const Int16x8& shifttosigned_8xshort) {
866
867 Int16x8 data;
868 // using signed int16 (short) until the coeff multiplication
869 if constexpr (sizeof(pixel_t) == 1) {
870 // pixel_t is uint8_t
871 Uint8x8 src = Uint8x8::from_ptr(src_ptr);
872 data = Int16x8::convert_from(src); // 8 pixels uint8_t->int16
873 }
874 else {
875 // pixel_t is uint16_t, at exact 16 bit bit depth unsigned -> signed 16 bit conversion needed
876 // this mimics the behavior of the SIMD version, which processes signed 16 bit input
877 // data.load(reinterpret_cast<const short * AVS_RESTRICT>(src_ptr));
878 Uint16x8 src = Uint16x8::from_ptr(src_ptr);
879 data = Int16x8::convert_from(src); // 8 pixels uint8_t->int16
880
881 if constexpr (!lessthan16bit) {
882 data += shifttosigned_8xshort; // 16 bit addition is invariant regarding overflow
883 }
884 }
885
886 Int16x8 coeff = Int16x8::from_ptr(current_coeff); // 8 coeffs
887
888 // no need real intel-like madd, but intel compiler can use it (experienced)
889 #if defined(__INTEL_LLVM_COMPILER)
890 // Intel proc + LLVM: only this compiler combo is compiling into madd,which is much faster.
891 Int32x4 madd_result = simul_madd_epi16(data, coeff);
892 #else
893 Int32x4 madd_result = mul16x16_reduce_to_Int32x4(data, coeff);
894 #endif
895 result += madd_result;
896 // later, the four 32 bit results will be further reduced and added together (hadd)
897 }
898
899
900 template<typename pixel_t, bool lessthan16bit>
901 AVS_FORCEINLINE static void process_two_4pixels_h_uint8_16_core(const pixel_t* AVS_RESTRICT src_ptr, const short* AVS_RESTRICT current_coeff, Int32x4& AVS_RESTRICT result, const Int32x4& shifttosigned_4xint) {
902 Int32x4 data;
903
904 // this one is using full int32 internally
905
906 if constexpr (sizeof(pixel_t) == 1) {
907 data.load_from_any_intptr(src_ptr); // 4 pixels uint8_t->int32
908 }
909 else {
910 // pixel_t is uint16_t, at exact 16 bit bit depth an unsigned -> signed 16 bit conversion needed
911 data.load_from_any_intptr(src_ptr); // 4 pixels uint16_t->int32
912
913 if constexpr (!lessthan16bit) {
914 data += shifttosigned_4xint;
915 }
916 }
917
918 // 4x signed 16 bit coeffs to int32
919 Int32x4 coeff_int;
920 coeff_int.load_from_any_intptr(current_coeff);
921
922 result += data * coeff_int;
923 // later, the four 32 bit results will be added together (hadd)
924 }
925
926 template<bool safe_aligned_mode, typename pixel_t, bool lessthan16bit>
927 AVS_FORCEINLINE static void process_two_pixels_h_uint8_16(const pixel_t* AVS_RESTRICT src_ptr, const int begin1, const int begin2, const short* AVS_RESTRICT current_coeff, const int filter_size,
928 Int32x4& _result1, Int32x4& _result2, const int kernel_size,
929 const Int16x8& shifttosigned)
930 {
931 // Reference parameters (_result1, _result2) have known addresses, so MSVC
932 // treats them as aliased memory and emits scalar extract+writeback on every
933 // loop iteration (movd/vpextrd to memory after each vpaddd).
934 // By copying to local variables, MSVC gains full ownership and keeps the
935 // accumulators in XMM registers for the entire loop, writing back only once
936 // at the end. This alone gave a ~25% speedup (106 -> 183 fps on MSVC).
937 // this problem with Clang/GCC was not observed.
938 Int32x4 result1 = _result1;
939 Int32x4 result2 = _result2;
940
941 int ksmod8;
942 // 16 is too much for C optimizer
943 if constexpr (safe_aligned_mode)
944 ksmod8 = filter_size / 8 * 8;
945 else
946 ksmod8 = kernel_size / 8 * 8; // danger zone, scanline overread possible. Use exact unaligned kernel_size
947 const pixel_t* src_ptr1 = src_ptr + begin1;
948 const pixel_t* src_ptr2 = src_ptr + begin2;
949 int i = 0;
950
951 // Process 16 elements at a time
952 for (; i < ksmod8; i += 8) {
953 process_two_8pixels_h_uint8_16_core<pixel_t, lessthan16bit>(src_ptr1 + i, current_coeff + i, result1, shifttosigned);
954 process_two_8pixels_h_uint8_16_core<pixel_t, lessthan16bit>(src_ptr2 + i, current_coeff + filter_size + i , result2, shifttosigned);
955 }
956
957 if constexpr (!safe_aligned_mode) {
958 // working with the original, unaligned kernel_size
959 if (i == kernel_size) {
960 _result1 = result1;
961 _result2 = result2;
962 return;
963 }
964
965 const int ksmod4 = kernel_size / 4 * 4;
966 // Process 4 elements if needed
967 if (i < ksmod4) {
968 Int32x4 shifttosigned_4;
969 shifttosigned_4.convert_from_lo(shifttosigned); // copy lower half of 8xshort, 4xshort to 4xint
970 process_two_4pixels_h_uint8_16_core<pixel_t, lessthan16bit>(src_ptr1 + i, current_coeff + i, result1, shifttosigned_4);
971 process_two_4pixels_h_uint8_16_core<pixel_t, lessthan16bit>(src_ptr2 + i, current_coeff + filter_size + i, result2, shifttosigned_4);
972 i += 4;
973 if (i == kernel_size) {
974 _result1 = result1;
975 _result2 = result2;
976 return;
977 }
978 }
979
980 // Process remaining 1-3 elements with scalar operations
981 if (i < kernel_size) {
982 Int32x4 scalar_sum1(0); // like an __m128i
983 Int32x4 scalar_sum2(0);
984
985 int index = 0;
986 for (; i < kernel_size; i++, index++) {
987
988 if constexpr (sizeof(pixel_t) == 1) {
989 scalar_sum1.set(index, scalar_sum1[index] + src_ptr1[i] * current_coeff[i]);
990 scalar_sum2.set(index, scalar_sum2[index] + src_ptr2[i] * current_coeff[filter_size + i]);
991 }
992 else {
993 // pixel_t is uint16_t
994 short val = src_ptr1[i];
995 if constexpr (!lessthan16bit)
996 val = val + shifttosigned[0]; // still short
997 scalar_sum1.set(index, scalar_sum1[index] + val * current_coeff[i]);
998
999 val = src_ptr2[i];
1000 if constexpr (!lessthan16bit)
1001 val = val + shifttosigned[0]; // still short
1002 scalar_sum2.set(index, scalar_sum2[index] + val * current_coeff[filter_size + i]);
1003 }
1004 }
1005
1006 // update result vectors
1007 result1 += scalar_sum1;
1008 result2 += scalar_sum2;
1009
1010 }
1011 }
1012
1013 _result1 = result1;
1014 _result2 = result2;
1015 }
1016
1017
1018 // NO Forceinline! Helps MSVC, by starting a new stack frame and have enough registers again.
1019 template<bool is_safe, typename pixel_t, bool lessthan16bit>
1020 static void process_eight_pixels_h_uint8_16(const pixel_t * AVS_RESTRICT src, int x, const short* current_coeff_base, int filter_size,
1021 const Int32x4& rounder128, const Int16x8& shifttosigned, const uint16_t clamp_limit,
1022 pixel_t* AVS_RESTRICT dst,
1023 ResamplingProgram* program)
1024 {
1025 assert(program->filter_size_alignment >= 16); // code assumes this
1026
1027 const short* AVS_RESTRICT current_coeff = current_coeff_base + x * filter_size;
1028 const int unaligned_kernel_size = program->filter_size_real;
1029
1030 // Unrolled processing of all 8 pixels
1031
1032 // 0 & 1
1033 Int32x4 result0 = rounder128;
1034 Int32x4 result1 = rounder128;
1035 int begin0 = program->pixel_offset[x + 0];
1036 int begin1 = program->pixel_offset[x + 1];
1037 process_two_pixels_h_uint8_16<is_safe, pixel_t, lessthan16bit>(src, begin0, begin1, current_coeff, filter_size, result0, result1, unaligned_kernel_size, shifttosigned);
1038 current_coeff += 2 * filter_size;
1039
1040 // 2 & 3
1041 Int32x4 result2 = rounder128;
1042 Int32x4 result3 = rounder128;
1043 begin0 = program->pixel_offset[x + 2];
1044 begin1 = program->pixel_offset[x + 3];
1045 process_two_pixels_h_uint8_16<is_safe, pixel_t, lessthan16bit>(src, begin0, begin1, current_coeff, filter_size, result2, result3, unaligned_kernel_size, shifttosigned);
1046 current_coeff += 2 * filter_size;
1047
1048 Int32x4 sumQuad1234;
1049 sumQuad1234 = make_from_horiz_sums(result0, result1, result2, result3);
1050 /* provided and optimized from simd_c.h
1051 sumQuad1234.set(0, result1.horiz_add_int32());
1052 sumQuad1234.set(1, result1.horiz_add_int32());
1053 sumQuad1234.set(2, result2.horiz_add_int32());
1054 sumQuad1234.set(3, result3.horiz_add_int32());
1055 */
1056
1057 // 4 & 5
1058 result0 = rounder128;
1059 result1 = rounder128;
1060 begin0 = program->pixel_offset[x + 4];
1061 begin1 = program->pixel_offset[x + 5];
1062 process_two_pixels_h_uint8_16<is_safe, pixel_t, lessthan16bit>(src, begin0, begin1, current_coeff, filter_size, result0, result1, unaligned_kernel_size, shifttosigned);
1063 current_coeff += 2 * filter_size;
1064
1065 // 6 & 7
1066 result2 = rounder128;
1067 result3 = rounder128;
1068 begin0 = program->pixel_offset[x + 6];
1069 begin1 = program->pixel_offset[x + 7];
1070 process_two_pixels_h_uint8_16<is_safe, pixel_t, lessthan16bit>(src, begin0, begin1, current_coeff, filter_size, result2, result3, unaligned_kernel_size, shifttosigned);
1071 // current_coeff += 2 * filter_size; // not needed anymore
1072
1073 Int32x4 sumQuad5678;
1074 sumQuad5678 = make_from_horiz_sums(result0, result1, result2, result3);
1075
1076 // correct if signed, scale back, store
1077 if constexpr (sizeof(pixel_t) == 2 && !lessthan16bit) {
1078 const Int32x4 shiftfromsigned(32768 << FPScale16bits);
1079 sumQuad1234 += shiftfromsigned;
1080 sumQuad5678 += shiftfromsigned;
1081 }
1082
1083 constexpr int current_fp_scale_bits = (sizeof(pixel_t) == 1) ? FPScale8bits : FPScale16bits;
1084 // scale back, store
1085 sumQuad1234 >>= current_fp_scale_bits;
1086 sumQuad5678 >>= current_fp_scale_bits;
1087
1088 if constexpr (sizeof(pixel_t) == 1) {
1089 Uint8x8 result_2x4x_uint8;
1090 convert_and_saturate_int32x4x2_to_uint8x8(sumQuad1234, sumQuad5678, result_2x4x_uint8);
1091 result_2x4x_uint8.store(&dst[x]);
1092 }
1093 else {
1094 // uint16_t 10-16 bit
1095 Uint16x8 result_2x4x_uint16_128;
1096 if constexpr (lessthan16bit) {
1097 convert_and_saturate_int32x4x2_to_uint16x8_limit(sumQuad1234, sumQuad5678, result_2x4x_uint16_128, clamp_limit);
1098 }
1099 else {
1100 convert_and_saturate_int32x4x2_to_uint16x8(sumQuad1234, sumQuad5678, result_2x4x_uint16_128);
1101 }
1102 result_2x4x_uint16_128.store(&dst[x]);
1103
1104 }
1105 }
1106
1107 //-------- uint8/16_t Horizontal
1108 // 4 pixels at a time.
1109 template<typename pixel_t, bool lessthan16bit>
1110 void resizer_h_c_generic_uint8_16_vectorized(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int height, int bits_per_pixel) {
1111
1112 const int filter_size = program->filter_size;
1113 const int current_fp_scale_bits = (sizeof(pixel_t) == 1) ? FPScale8bits : FPScale16bits;
1114 const Int32x4 rounder128 = { 1 << (current_fp_scale_bits - 1), 0, 0, 0 };
1115
1116 const Int16x8 shifttosigned_or_zero128(sizeof(pixel_t) == 1 ? 0 : -32768);
1117
1118 const uint16_t clamp_limit = (1 << bits_per_pixel) - 1;
1119
1120 const pixel_t* AVS_RESTRICT src = reinterpret_cast<const pixel_t*>(src8);
1121 pixel_t* AVS_RESTRICT dst = reinterpret_cast<pixel_t*>(dst8);
1122 dst_pitch /= sizeof(pixel_t);
1123 src_pitch /= sizeof(pixel_t);
1124
1125 const int w_safe_mod8 = (program->safelimit_filter_size_aligned.overread_possible ? program->safelimit_filter_size_aligned.source_overread_beyond_targetx : width) / 8 * 8;
1126
1127 for (int y = 0; y < height; y++) {
1128 const short* current_coeff_base = program->pixel_coefficient;
1129
1130 // Process safe aligned pixels
1131 for (int x = 0; x < w_safe_mod8; x += 8) {
1132 process_eight_pixels_h_uint8_16<true, pixel_t, lessthan16bit>(src, x, current_coeff_base, filter_size, rounder128, shifttosigned_or_zero128, clamp_limit, dst, program);
1133 }
1134
1135 // Process up to the actual kernel size instead of the aligned filter_size to prevent overreading beyond the last source pixel.
1136 // We assume extra offset entries were added to the p->pixel_offset array (aligned to 8 during initialization).
1137 // This may store 1-7 false pixels but it still remain in alignment-safe area.
1138 for (int x = w_safe_mod8; x < width; x += 8) {
1139 process_eight_pixels_h_uint8_16<false, pixel_t, lessthan16bit>(src, x, current_coeff_base, filter_size, rounder128, shifttosigned_or_zero128, clamp_limit, dst, program);
1140 }
1141
1142 dst += dst_pitch;
1143 src += src_pitch;
1144 }
1145 }
1146
1147 // 16 bit Horizontal
1148
1149 template void resizer_h_c_generic_uint8_16_vectorized<uint8_t, true>(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int height, int bits_per_pixel);
1150 template void resizer_h_c_generic_uint8_16_vectorized<uint16_t, false>(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int height, int bits_per_pixel);
1151 template void resizer_h_c_generic_uint8_16_vectorized<uint16_t, true>(BYTE* dst8, const BYTE* src8, int dst_pitch, int src_pitch, ResamplingProgram* program, int width, int height, int bits_per_pixel);
1152
1153 /********************************************************************
1154 ***** Declare index of new filters for Avisynth's filter engine *****
1155 ********************************************************************/
1156
1157 extern const AVSFunction Resample_filters[] = {
1158 { "PointResize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_PointResize },
1159 { "BilinearResize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_BilinearResize },
1160 { "BicubicResize", BUILTIN_FUNC_PREFIX, "cii[b]f[c]f[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_BicubicResize },
1161 { "LanczosResize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[taps]i[force]i[keep_center]b[placement]s", FilteredResize::Create_LanczosResize},
1162 { "Lanczos4Resize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_Lanczos4Resize},
1163 { "BlackmanResize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[taps]i[force]i[keep_center]b[placement]s", FilteredResize::Create_BlackmanResize},
1164 { "Spline16Resize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_Spline16Resize},
1165 { "Spline36Resize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_Spline36Resize},
1166 { "Spline64Resize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_Spline64Resize},
1167 { "GaussResize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[p]f[b]f[s]f[force]i[keep_center]b[placement]s", FilteredResize::Create_GaussianResize},
1168 { "SincResize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[taps]i[force]i[keep_center]b[placement]s", FilteredResize::Create_SincResize},
1169 { "SinPowerResize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[p]f[force]i[keep_center]b[placement]s", FilteredResize::Create_SinPowerResize},
1170 { "SincLin2Resize", BUILTIN_FUNC_PREFIX, "cii[src_left]f[src_top]f[src_width]f[src_height]f[taps]i[force]i[keep_center]b[placement]s", FilteredResize::Create_SincLin2Resize},
1171 { "UserDefined2Resize", BUILTIN_FUNC_PREFIX, "cii[b]f[c]f[s]f[src_left]f[src_top]f[src_width]f[src_height]f[force]i[keep_center]b[placement]s", FilteredResize::Create_UserDefined2Resize},
1172 /**
1173 * Resize(PClip clip, dst_width, dst_height [src_left, src_top, src_width, int src_height,] )
1174 *
1175 * src_left et al. = when these optional arguments are given, the filter acts just like
1176 * a Crop was performed with those parameters before resizing, only faster
1177 **/
1178
1179 { 0 }
1180 };
1181
1182 // Borrowed from fmtconv
1183 // ChromaPlacement.cpp
1184 // Author : Laurent de Soras, 2015
1185
1186 // Fixes the vertical chroma placement when the picture is interlaced.
1187 // ofs = ordinate to skip between TFF and BFF, relative to the chroma grid. A
1188 // single line of full-res picture is 0.25.
1189 12 static inline void ChromaPlacement_fix_itl(double& cp_v, bool interlaced_flag, bool top_flag, double ofs = 0.5)
1190 {
1191
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 12 times.
12 assert(cp_v >= 0);
1192
1193
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 12 times.
12 if (interlaced_flag)
1194 {
1195 cp_v *= 0.5;
1196 if (!top_flag)
1197 {
1198 cp_v += ofs;
1199 }
1200 }
1201 12 }
1202 /*
1203 ss_h and ss_v are log2(subsampling)
1204 rgb_flag actually means that chroma subsampling doesn't apply.
1205
1206 http://www.mir.com/DMG/chroma.html
1207
1208 cp_* is the position of the sampling point relative to the frame
1209 top/left border, in the plane coordinates. For reference, the border
1210 of the frame is at 0.5 units of luma from the first luma sampling point.
1211 I. e., the luma sampling point is at the pixel's center.
1212 */
1213
1214 // PF added BOTTOM, BOTTOM_LEFT, TOP
1215 // Pass ChromaLocation_e::AVS_CHROMA_UNUSED for defaults
1216 // plane index 0:Y, 1:U, 2:V
1217 // cplace is a ChromaLocation_e constant
1218 10 static void ChromaPlacement_compute_cplace(double& cp_h, double& cp_v, int cplace, int plane_index, int ss_h, int ss_v, bool rgb_flag, bool interlaced_flag, bool top_flag)
1219 {
1220
3/4
✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 5 taken 6 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 4 times.
10 assert(cplace >= 0 || cplace == ChromaLocation_e::AVS_CHROMA_UNUSED);
1221
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 10 times.
10 assert(cplace < ChromaLocation_e::AVS_CHROMA_DV);
1222
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 10 times.
10 assert(ss_h >= 0);
1223
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 10 times.
10 assert(ss_v >= 0);
1224
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 10 times.
10 assert(plane_index >= 0);
1225
1226 // Generic case for luma, non-subsampled chroma and center (MPEG-1) chroma.
1227 10 cp_h = 0.5;
1228 10 cp_v = 0.5;
1229 10 ChromaPlacement_fix_itl(cp_v, interlaced_flag, top_flag);
1230
1231 // Subsampled chroma
1232
2/4
✓ Branch 14 → 15 taken 10 times.
✗ Branch 14 → 37 not taken.
✓ Branch 15 → 16 taken 10 times.
✗ Branch 15 → 37 not taken.
10 if (!rgb_flag && plane_index > 0)
1233 {
1234
2/2
✓ Branch 16 → 17 taken 6 times.
✓ Branch 16 → 22 taken 4 times.
10 if (ss_h > 0) // horizontal subsampling 420 411
1235 {
1236
2/2
✓ Branch 17 → 18 taken 4 times.
✓ Branch 17 → 21 taken 2 times.
6 if (cplace == ChromaLocation_e::AVS_CHROMA_LEFT // mpeg2
1237
1/2
✓ Branch 18 → 19 taken 4 times.
✗ Branch 18 → 21 not taken.
4 || cplace == ChromaLocation_e::AVS_CHROMA_DV
1238
2/2
✓ Branch 19 → 20 taken 3 times.
✓ Branch 19 → 21 taken 1 time.
4 || cplace == ChromaLocation_e::AVS_CHROMA_TOP_LEFT
1239
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 3 times.
3 || cplace == ChromaLocation_e::AVS_CHROMA_BOTTOM_LEFT
1240 )
1241 {
1242 3 cp_h = 0.5 / (1 << ss_h);
1243 }
1244 }
1245
1246
2/2
✓ Branch 22 → 23 taken 4 times.
✓ Branch 22 → 37 taken 6 times.
10 if (ss_v == 1) // vertical subsampling 420, 422
1247 {
1248
2/2
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 3 times.
4 if (cplace == ChromaLocation_e::AVS_CHROMA_LEFT)
1249 {
1250 1 cp_v = 0.5;
1251 1 ChromaPlacement_fix_itl(cp_v, interlaced_flag, top_flag);
1252 }
1253
1/2
✓ Branch 25 → 26 taken 3 times.
✗ Branch 25 → 28 not taken.
3 else if (cplace == ChromaLocation_e::AVS_CHROMA_DV
1254
2/2
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 28 taken 1 time.
3 || cplace == ChromaLocation_e::AVS_CHROMA_TOP_LEFT
1255
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 33 taken 2 times.
2 || cplace == ChromaLocation_e::AVS_CHROMA_TOP
1256 )
1257 {
1258 1 cp_v = 0.25;
1259 1 ChromaPlacement_fix_itl(cp_v, interlaced_flag, top_flag, 0.25);
1260
1261
1/4
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 32 taken 1 time.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
1 if (cplace == ChromaLocation_e::AVS_CHROMA_DV && plane_index == 2) // V
1262 {
1263 cp_v += 0.5;
1264 }
1265 }
1266
1/2
✓ Branch 33 → 34 taken 2 times.
✗ Branch 33 → 35 not taken.
2 else if (cplace == ChromaLocation_e::AVS_CHROMA_BOTTOM_LEFT
1267
1/2
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 2 times.
2 || cplace == ChromaLocation_e::AVS_CHROMA_BOTTOM
1268 )
1269 {
1270 cp_v = 0.75;
1271 ChromaPlacement_fix_itl(cp_v, interlaced_flag, top_flag, 0.25);
1272 }
1273 } // ss_v == 1
1274 }
1275 10 }
1276
1277
1278 // returns the requested horizontal or vertical pixel center position
1279 42 static void GetCenterShiftForResizers(double& center_pos_luma, double& center_pos_chroma, bool preserve_center, int chroma_placement, VideoInfo &vi, bool for_horizontal) {
1280 42 double center_pos_h_luma = 0.0;
1281 42 double center_pos_v_luma = 0.0;
1282 // if not needed, these won't be used
1283 42 double center_pos_h_chroma = 0.0;
1284 42 double center_pos_v_chroma = 0.0;
1285
1286 // chroma, only if applicable
1287
8/8
✓ Branch 3 → 4 taken 38 times.
✓ Branch 3 → 9 taken 4 times.
✓ Branch 5 → 6 taken 14 times.
✓ Branch 5 → 9 taken 24 times.
✓ Branch 7 → 8 taken 12 times.
✓ Branch 7 → 9 taken 2 times.
✓ Branch 10 → 11 taken 12 times.
✓ Branch 10 → 17 taken 30 times.
42 if (vi.IsPlanar() && vi.NumComponents() > 1 && !vi.IsRGB()) {
1288 12 double cp_s_h = 0;
1289 12 double cp_s_v = 0;
1290
1291
2/2
✓ Branch 11 → 12 taken 10 times.
✓ Branch 11 → 16 taken 2 times.
12 if (preserve_center) {
1292 // same for source and destination
1293 10 int plane_index = 1; // U
1294
1/2
✓ Branch 12 → 13 taken 10 times.
✗ Branch 12 → 24 not taken.
10 int src_ss_h = vi.GetPlaneWidthSubsampling(PLANAR_U);
1295
1/2
✓ Branch 13 → 14 taken 10 times.
✗ Branch 13 → 24 not taken.
10 int src_ss_v = vi.GetPlaneHeightSubsampling(PLANAR_U);
1296
1297 // int chromaplace = ChromaLocation_e::AVS_CHROMA_CENTER; // MPEG1
1298
1299 10 ChromaPlacement_compute_cplace(
1300 cp_s_h, cp_s_v, chroma_placement, plane_index, src_ss_h, src_ss_v,
1301
1/2
✓ Branch 14 → 15 taken 10 times.
✗ Branch 14 → 24 not taken.
10 vi.IsRGB(),
1302 false, // interlacing flag, we don't handle it here
1303 false // top_flag, we don't handle it here
1304 );
1305 }
1306
1307 12 center_pos_h_chroma = cp_s_h;
1308 12 center_pos_v_chroma = cp_s_v;
1309 }
1310
1311 // luma/rgb planes
1312
2/2
✓ Branch 17 → 18 taken 40 times.
✓ Branch 17 → 19 taken 2 times.
42 if (preserve_center) {
1313 40 center_pos_h_luma = 0.5;
1314 40 center_pos_v_luma = 0.5;
1315 }
1316 else {
1317 2 center_pos_h_luma = 0.0;
1318 2 center_pos_v_luma = 0.0;
1319 }
1320
1321 // fill return ref values
1322
2/2
✓ Branch 20 → 21 taken 21 times.
✓ Branch 20 → 22 taken 21 times.
42 if (for_horizontal) {
1323 21 center_pos_luma = center_pos_h_luma;
1324 21 center_pos_chroma = center_pos_h_chroma;
1325 }
1326 else {
1327 // vertical
1328 21 center_pos_luma = center_pos_v_luma;
1329 21 center_pos_chroma = center_pos_v_chroma;
1330 }
1331
1332 42 }
1333
1334 22 FilteredResizeH::FilteredResizeH(PClip _child, double subrange_left, double subrange_width,
1335 22 int target_width, ResamplingFunction* func, bool preserve_center, int chroma_placement, IScriptEnvironment* env)
1336 : GenericVideoFilter(_child),
1337 22 resampling_program_luma(nullptr), resampling_program_chroma(nullptr),
1338 22 resampler_h_luma(nullptr), resampler_h_chroma(nullptr),
1339 22 resampler_h_luma_mt(nullptr), resampler_h_chroma_mt(nullptr),
1340 22 use_alternative_h_avx512_mt_fallback(false),
1341 22 resampler_luma(nullptr), resampler_chroma(nullptr),
1342
2/4
✓ Branch 2 → 3 taken 22 times.
✗ Branch 2 → 105 not taken.
✓ Branch 3 → 4 taken 22 times.
✗ Branch 3 → 103 not taken.
22 num_threads(0)
1343
1344 {
1345 22 src_width = vi.width;
1346 22 src_height = vi.height;
1347 22 dst_width = target_width;
1348 22 dst_height = vi.height;
1349
1350
1/2
✓ Branch 5 → 6 taken 22 times.
✗ Branch 5 → 106 not taken.
22 pixelsize = vi.ComponentSize(); // AVS16
1351
1/2
✓ Branch 6 → 7 taken 22 times.
✗ Branch 6 → 106 not taken.
22 bits_per_pixel = vi.BitsPerComponent();
1352
1/2
✓ Branch 7 → 8 taken 22 times.
✗ Branch 7 → 106 not taken.
22 grey = vi.IsY();
1353
1354
5/8
✓ Branch 8 → 9 taken 22 times.
✗ Branch 8 → 106 not taken.
✓ Branch 9 → 10 taken 21 times.
✓ Branch 9 → 12 taken 1 time.
✓ Branch 10 → 11 taken 21 times.
✗ Branch 10 → 106 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 21 times.
22 bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
1355
1356
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 22 times.
22 if (target_width <= 0) {
1357 env->ThrowError("Resize: Width must be greater than 0.");
1358 }
1359
1360
9/10
✓ Branch 16 → 17 taken 22 times.
✗ Branch 16 → 106 not taken.
✓ Branch 17 → 18 taken 20 times.
✓ Branch 17 → 21 taken 2 times.
✓ Branch 18 → 19 taken 8 times.
✓ Branch 18 → 21 taken 12 times.
✓ Branch 19 → 20 taken 7 times.
✓ Branch 19 → 21 taken 1 time.
✓ Branch 22 → 23 taken 7 times.
✓ Branch 22 → 26 taken 15 times.
22 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
1361
1/2
✓ Branch 23 → 24 taken 7 times.
✗ Branch 23 → 106 not taken.
7 const int mask = (1 << vi.GetPlaneWidthSubsampling(PLANAR_U)) - 1;
1362
1363
2/2
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 6 times.
7 if (target_width & mask)
1364
1/2
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 106 taken 1 time.
1 env->ThrowError("Resize: Planar destination height must be a multiple of %d.", mask + 1);
1365 }
1366
1367 double center_pos_h_luma;
1368 double center_pos_h_chroma;
1369
1/2
✓ Branch 26 → 27 taken 21 times.
✗ Branch 26 → 106 not taken.
21 GetCenterShiftForResizers(center_pos_h_luma, center_pos_h_chroma, preserve_center, chroma_placement, vi, true /* for horizontal */);
1370 // 3.7.4- parameter, old Avisynth behavior: 0.5, 0.5
1371
1372 // Main resampling program
1373
1/2
✓ Branch 27 → 28 taken 21 times.
✗ Branch 27 → 106 not taken.
21 resampling_program_luma = func->GetResamplingProgram(vi.width, subrange_left, subrange_width, target_width, bits_per_pixel,
1374 center_pos_h_luma, center_pos_h_luma, // for resizing it's the same for source and dest
1375 env);
1376
9/10
✓ Branch 28 → 29 taken 21 times.
✗ Branch 28 → 106 not taken.
✓ Branch 29 → 30 taken 19 times.
✓ Branch 29 → 33 taken 2 times.
✓ Branch 30 → 31 taken 7 times.
✓ Branch 30 → 33 taken 12 times.
✓ Branch 31 → 32 taken 6 times.
✓ Branch 31 → 33 taken 1 time.
✓ Branch 34 → 35 taken 6 times.
✓ Branch 34 → 38 taken 15 times.
21 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
1377
1/2
✓ Branch 35 → 36 taken 6 times.
✗ Branch 35 → 106 not taken.
6 const int shift = vi.GetPlaneWidthSubsampling(PLANAR_U);
1378 6 const int div = 1 << shift;
1379
1380
1381 6 resampling_program_chroma = func->GetResamplingProgram(
1382
1/2
✓ Branch 36 → 37 taken 6 times.
✗ Branch 36 → 106 not taken.
6 vi.width >> shift,
1383 subrange_left / div,
1384 subrange_width / div,
1385 target_width >> shift,
1386 bits_per_pixel,
1387 center_pos_h_chroma, center_pos_h_chroma, // horizontal
1388 env);
1389 }
1390
1391 // when not fast_resize, then we use vertical resizers between turnleft/turnright
1392 #ifdef INTEL_INTRINSICS
1393
1/2
✓ Branch 38 → 39 taken 21 times.
✗ Branch 38 → 106 not taken.
21 int cpu = env->GetCPUFlags();
1394 21 bool has_sse2 = (cpu & CPUF_SSE2) != 0;
1395 21 bool has_avx2 = (cpu & CPUF_AVX2) != 0;
1396 #elif defined(NEON_INTRINSICS)
1397 int cpu = env->GetCPUFlags();
1398 bool has_neon = (cpu & CPUF_ARM_NEON) != 0;
1399 #else
1400 int cpu = 0;
1401 #endif
1402
1403
1/2
✓ Branch 39 → 40 taken 21 times.
✗ Branch 39 → 106 not taken.
21 fast_resize = vi.IsPlanar();
1404 // PF 2025: H is not slower than V in C implementation.
1405 // Still, H resizers are incompatible with packed RGB formats
1406
1407
2/2
✓ Branch 40 → 41 taken 2 times.
✓ Branch 40 → 95 taken 19 times.
21 if (!fast_resize) {
1408
1409 // nonfast-resize: using V resizer for horizontal resizing between a turnleft/right
1410 // For packed RGB formats this is the only way
1411
1412
1/2
✓ Branch 41 → 42 taken 2 times.
✗ Branch 41 → 106 not taken.
2 resampler_luma = FilteredResizeV::GetResampler(cpu, pixelsize, bits_per_pixel, resampling_program_luma, env);
1413
1414
3/10
✓ Branch 42 → 43 taken 2 times.
✗ Branch 42 → 106 not taken.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 47 taken 2 times.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 47 not taken.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 51 taken 2 times.
2 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
1415 resampler_chroma = FilteredResizeV::GetResampler(cpu, pixelsize, bits_per_pixel, resampling_program_chroma, env);
1416 }
1417
1418 // Temporary buffer size for turns
1419
1/2
✓ Branch 51 → 52 taken 2 times.
✗ Branch 51 → 106 not taken.
2 temp_1_pitch = AlignNumber(vi.BytesFromPixels(src_height), FRAME_ALIGN);
1420
1/2
✓ Branch 53 → 54 taken 2 times.
✗ Branch 53 → 106 not taken.
2 temp_2_pitch = AlignNumber(vi.BytesFromPixels(dst_height), FRAME_ALIGN);
1421
1422 // Initialize Turn function
1423 // see turn.cpp
1424
3/4
✓ Branch 55 → 56 taken 2 times.
✗ Branch 55 → 106 not taken.
✓ Branch 56 → 57 taken 1 time.
✓ Branch 56 → 58 taken 1 time.
2 if (vi.IsRGB24()) {
1425 #ifdef INTEL_INTRINSICS
1426 // no intel intentionally
1427 #endif
1428 1 turn_left = turn_left_rgb24;
1429 1 turn_right = turn_right_rgb24;
1430 }
1431
2/4
✓ Branch 58 → 59 taken 1 time.
✗ Branch 58 → 106 not taken.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 65 taken 1 time.
1 else if (vi.IsRGB32()) {
1432 #ifdef INTEL_INTRINSICS
1433 if (has_avx2) {
1434 turn_left = turn_left_rgb32_avx2;
1435 turn_right = turn_right_rgb32_avx2;
1436 }
1437 else if (has_sse2) {
1438 turn_left = turn_left_rgb32_sse2;
1439 turn_right = turn_right_rgb32_sse2;
1440 }
1441 else
1442 #elif NEON_INTRINSICS
1443 if (has_neon) {
1444 turn_left = turn_left_rgb32_neon;
1445 turn_right = turn_right_rgb32_neon;
1446 }
1447 else
1448 #endif
1449 {
1450 turn_left = turn_left_rgb32_c;
1451 turn_right = turn_right_rgb32_c;
1452 }
1453 }
1454
2/4
✓ Branch 65 → 66 taken 1 time.
✗ Branch 65 → 106 not taken.
✗ Branch 66 → 67 not taken.
✓ Branch 66 → 68 taken 1 time.
1 else if (vi.IsRGB48()) {
1455 #ifdef INTEL_INTRINSICS
1456 // no intel intentionally
1457 #endif
1458 turn_left = turn_left_rgb48_c;
1459 turn_right = turn_right_rgb48_c;
1460 }
1461
2/4
✓ Branch 68 → 69 taken 1 time.
✗ Branch 68 → 106 not taken.
✓ Branch 69 → 70 taken 1 time.
✗ Branch 69 → 75 not taken.
1 else if (vi.IsRGB64()) {
1462 #ifdef INTEL_INTRINSICS
1463
1/2
✓ Branch 70 → 71 taken 1 time.
✗ Branch 70 → 72 not taken.
1 if (has_avx2) {
1464 1 turn_left = turn_left_rgb64_avx2;
1465 1 turn_right = turn_right_rgb64_avx2;
1466 }
1467 else if (has_sse2) {
1468 turn_left = turn_left_rgb64_sse2;
1469 turn_right = turn_right_rgb64_sse2;
1470 }
1471 else
1472 #elif defined(NEON_INTRINSICS)
1473 if (has_neon) {
1474 turn_left = turn_left_rgb64_neon;
1475 turn_right = turn_right_rgb64_neon;
1476 }
1477 else
1478 #endif
1479 {
1480 turn_left = turn_left_rgb64_c;
1481 turn_right = turn_right_rgb64_c;
1482 }
1483 }
1484 else {
1485 switch (vi.ComponentSize()) {// AVS16
1486 case 1: // 8 bit
1487 #ifdef INTEL_INTRINSICS
1488 if (has_avx2) {
1489 turn_left = turn_left_plane_8_avx2;
1490 turn_right = turn_right_plane_8_avx2;
1491 }
1492 else if (has_sse2) {
1493 turn_left = turn_left_plane_8_sse2;
1494 turn_right = turn_right_plane_8_sse2;
1495 }
1496 else
1497 #elif defined(NEON_INTRINSICS)
1498 if (has_neon) {
1499 turn_left = turn_left_plane_8_neon;
1500 turn_right = turn_right_plane_8_neon;
1501 }
1502 else
1503 #endif
1504 {
1505 turn_left = turn_left_plane_8_c;
1506 turn_right = turn_right_plane_8_c;
1507 }
1508 break;
1509 case 2: // 16 bit
1510 #ifdef INTEL_INTRINSICS
1511 if (has_avx2) {
1512 turn_left = turn_left_plane_16_avx2;
1513 turn_right = turn_right_plane_16_avx2;
1514 }
1515 else if (has_sse2) {
1516 turn_left = turn_left_plane_16_sse2;
1517 turn_right = turn_right_plane_16_sse2;
1518 }
1519 else
1520 #elif defined(NEON_INTRINSICS)
1521 if (has_neon) {
1522 turn_left = turn_left_plane_16_neon;
1523 turn_right = turn_right_plane_16_neon;
1524 }
1525 else
1526 #endif
1527 {
1528 turn_left = turn_left_plane_16_c;
1529 turn_right = turn_right_plane_16_c;
1530 }
1531 break;
1532 default: // 32 bit
1533 #ifdef INTEL_INTRINSICS
1534 if (has_avx2) {
1535 turn_left = turn_left_plane_32_avx2;
1536 turn_right = turn_right_plane_32_avx2;
1537 }
1538 else if (has_sse2) {
1539 turn_left = turn_left_plane_32_sse2;
1540 turn_right = turn_right_plane_32_sse2;
1541 }
1542 else
1543 #endif
1544 {
1545 turn_left = turn_left_plane_32_c;
1546 turn_right = turn_right_plane_32_c;
1547 }
1548 }
1549 }
1550 }
1551 else {
1552 // planar format (or Y)
1553
1/2
✓ Branch 95 → 96 taken 19 times.
✗ Branch 95 → 106 not taken.
19 resampler_h_luma = GetResampler(cpu, pixelsize, bits_per_pixel, resampling_program_luma, /*out*/resampler_h_luma_mt, env);
1554
1555
4/4
✓ Branch 96 → 97 taken 7 times.
✓ Branch 96 → 100 taken 12 times.
✓ Branch 97 → 98 taken 6 times.
✓ Branch 97 → 100 taken 1 time.
19 if (!grey && !isRGBPfamily) {
1556
1/2
✓ Branch 98 → 99 taken 6 times.
✗ Branch 98 → 106 not taken.
6 resampler_h_chroma = GetResampler(cpu, pixelsize, bits_per_pixel, resampling_program_chroma, /*out*/resampler_h_chroma_mt, env);
1557 }
1558
1559
1/2
✓ Branch 100 → 101 taken 19 times.
✗ Branch 100 → 102 not taken.
19 if (!use_alternative_h_avx512_mt_fallback) {
1560 19 resampler_h_luma_mt = nullptr;
1561 19 resampler_h_chroma_mt = nullptr;
1562 }
1563 }
1564 // Change target video info size
1565 21 vi.width = target_width;
1566 22 }
1567
1568 13 PVideoFrame __stdcall FilteredResizeH::GetFrame(int n, IScriptEnvironment* env)
1569 {
1570
1/2
✓ Branch 3 → 4 taken 13 times.
✗ Branch 3 → 198 not taken.
13 PVideoFrame src = child->GetFrame(n, env);
1571
1/2
✓ Branch 4 → 5 taken 13 times.
✗ Branch 4 → 196 not taken.
13 PVideoFrame dst = env->NewVideoFrameP(vi, &src);
1572
1573
5/8
✓ Branch 5 → 6 taken 13 times.
✗ Branch 5 → 194 not taken.
✓ Branch 6 → 7 taken 12 times.
✓ Branch 6 → 9 taken 1 time.
✓ Branch 7 → 8 taken 12 times.
✗ Branch 7 → 194 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 12 times.
13 bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
1574
1575
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 118 taken 11 times.
13 if (!fast_resize) {
1576 // e.g. not aligned, not mod4
1577 // temp_1_pitch and temp_2_pitch is pixelsize-aware
1578
1/2
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 194 not taken.
2 BYTE* temp_1 = static_cast<BYTE*>(env->Allocate(temp_1_pitch * src_width, FRAME_ALIGN, AVS_POOLED_ALLOC));
1579
1/2
✓ Branch 13 → 14 taken 2 times.
✗ Branch 13 → 194 not taken.
2 BYTE* temp_2 = static_cast<BYTE*>(env->Allocate(temp_2_pitch * dst_width, FRAME_ALIGN, AVS_POOLED_ALLOC));
1580
2/4
✓ Branch 14 → 15 taken 2 times.
✗ Branch 14 → 16 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 19 taken 2 times.
2 if (!temp_1 || !temp_2) {
1581 env->Free(temp_1);
1582 env->Free(temp_2);
1583 env->ThrowError("Could not reserve memory in a resampler.");
1584 }
1585
1586
4/8
✓ Branch 19 → 20 taken 2 times.
✗ Branch 19 → 194 not taken.
✓ Branch 20 → 21 taken 2 times.
✗ Branch 20 → 22 not taken.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 2 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 102 taken 2 times.
2 if (!vi.IsRGB() || isRGBPfamily) {
1587 // Y/G Plane
1588 turn_right(src->GetReadPtr(), temp_1, src_width * pixelsize, src_height, src->GetPitch(), temp_1_pitch); // * pixelsize: turn_right needs GetPlaneWidth full size
1589 resampler_luma(temp_2, temp_1, temp_2_pitch, temp_1_pitch, resampling_program_luma, src_height, dst_width, bits_per_pixel);
1590 turn_left(temp_2, dst->GetWritePtr(), dst_height * pixelsize, dst_width, temp_2_pitch, dst->GetPitch());
1591
1592 if (isRGBPfamily)
1593 {
1594 turn_right(src->GetReadPtr(PLANAR_B), temp_1, src_width * pixelsize, src_height, src->GetPitch(PLANAR_B), temp_1_pitch); // * pixelsize: turn_right needs GetPlaneWidth full size
1595 resampler_luma(temp_2, temp_1, temp_2_pitch, temp_1_pitch, resampling_program_luma, src_height, dst_width, bits_per_pixel);
1596 turn_left(temp_2, dst->GetWritePtr(PLANAR_B), dst_height * pixelsize, dst_width, temp_2_pitch, dst->GetPitch(PLANAR_B));
1597
1598 turn_right(src->GetReadPtr(PLANAR_R), temp_1, src_width * pixelsize, src_height, src->GetPitch(PLANAR_R), temp_1_pitch); // * pixelsize: turn_right needs GetPlaneWidth full size
1599 resampler_luma(temp_2, temp_1, temp_2_pitch, temp_1_pitch, resampling_program_luma, src_height, dst_width, bits_per_pixel);
1600 turn_left(temp_2, dst->GetWritePtr(PLANAR_R), dst_height * pixelsize, dst_width, temp_2_pitch, dst->GetPitch(PLANAR_R));
1601 }
1602 else if (!grey) {
1603 const int shift = vi.GetPlaneWidthSubsampling(PLANAR_U);
1604 const int shift_h = vi.GetPlaneHeightSubsampling(PLANAR_U);
1605
1606 const int src_chroma_width = src_width >> shift;
1607 const int dst_chroma_width = dst_width >> shift;
1608 const int src_chroma_height = src_height >> shift_h;
1609 const int dst_chroma_height = dst_height >> shift_h;
1610
1611 // turn_xxx: width * pixelsize: needs GetPlaneWidth-like full size
1612 // U Plane
1613 turn_right(src->GetReadPtr(PLANAR_U), temp_1, src_chroma_width * pixelsize, src_chroma_height, src->GetPitch(PLANAR_U), temp_1_pitch);
1614 resampler_luma(temp_2, temp_1, temp_2_pitch, temp_1_pitch, resampling_program_chroma, src_chroma_height, dst_chroma_width, bits_per_pixel);
1615 turn_left(temp_2, dst->GetWritePtr(PLANAR_U), dst_chroma_height * pixelsize, dst_chroma_width, temp_2_pitch, dst->GetPitch(PLANAR_U));
1616
1617 // V Plane
1618 turn_right(src->GetReadPtr(PLANAR_V), temp_1, src_chroma_width * pixelsize, src_chroma_height, src->GetPitch(PLANAR_V), temp_1_pitch);
1619 resampler_luma(temp_2, temp_1, temp_2_pitch, temp_1_pitch, resampling_program_chroma, src_chroma_height, dst_chroma_width, bits_per_pixel);
1620 turn_left(temp_2, dst->GetWritePtr(PLANAR_V), dst_chroma_height * pixelsize, dst_chroma_width, temp_2_pitch, dst->GetPitch(PLANAR_V));
1621 }
1622 if (vi.IsYUVA() || vi.IsPlanarRGBA())
1623 {
1624 turn_right(src->GetReadPtr(PLANAR_A), temp_1, src_width * pixelsize, src_height, src->GetPitch(PLANAR_A), temp_1_pitch); // * pixelsize: turn_right needs GetPlaneWidth full size
1625 resampler_luma(temp_2, temp_1, temp_2_pitch, temp_1_pitch, resampling_program_luma, src_height, dst_width, bits_per_pixel);
1626 turn_left(temp_2, dst->GetWritePtr(PLANAR_A), dst_height * pixelsize, dst_width, temp_2_pitch, dst->GetPitch(PLANAR_A));
1627 }
1628
1629 }
1630 else {
1631 // packed RGB
1632 // First left, then right. Reason: packed RGB bottom to top. Right+left shifts RGB24/RGB32 image to the opposite horizontal direction
1633
4/8
✓ Branch 103 → 104 taken 2 times.
✗ Branch 103 → 194 not taken.
✓ Branch 104 → 105 taken 2 times.
✗ Branch 104 → 194 not taken.
✓ Branch 106 → 107 taken 2 times.
✗ Branch 106 → 194 not taken.
✓ Branch 107 → 108 taken 2 times.
✗ Branch 107 → 194 not taken.
2 turn_left(src->GetReadPtr(), temp_1, vi.BytesFromPixels(src_width), src_height, src->GetPitch(), temp_1_pitch);
1634
2/4
✓ Branch 108 → 109 taken 2 times.
✗ Branch 108 → 194 not taken.
✓ Branch 109 → 110 taken 2 times.
✗ Branch 109 → 194 not taken.
2 resampler_luma(temp_2, temp_1, temp_2_pitch, temp_1_pitch, resampling_program_luma, vi.BytesFromPixels(src_height) / pixelsize, dst_width, bits_per_pixel);
1635
4/8
✓ Branch 111 → 112 taken 2 times.
✗ Branch 111 → 194 not taken.
✓ Branch 112 → 113 taken 2 times.
✗ Branch 112 → 194 not taken.
✓ Branch 114 → 115 taken 2 times.
✗ Branch 114 → 194 not taken.
✓ Branch 115 → 116 taken 2 times.
✗ Branch 115 → 194 not taken.
2 turn_right(temp_2, dst->GetWritePtr(), vi.BytesFromPixels(dst_height), dst_width, temp_2_pitch, dst->GetPitch());
1636 }
1637
1638
1/2
✓ Branch 116 → 117 taken 2 times.
✗ Branch 116 → 194 not taken.
2 env->Free(temp_1);
1639
1/2
✓ Branch 117 → 191 taken 2 times.
✗ Branch 117 → 194 not taken.
2 env->Free(temp_2);
1640 }
1641 else {
1642 // depending on MT or not, select proper resizer if alternative is available
1643
1/4
✗ Branch 118 → 119 not taken.
✓ Branch 118 → 121 taken 11 times.
✗ Branch 119 → 120 not taken.
✗ Branch 119 → 121 not taken.
11 ResamplerH current_resampler_h_luma = (num_threads > 1 && resampler_h_luma_mt != nullptr) ? resampler_h_luma_mt : resampler_h_luma;
1644
1/4
✗ Branch 122 → 123 not taken.
✓ Branch 122 → 125 taken 11 times.
✗ Branch 123 → 124 not taken.
✗ Branch 123 → 125 not taken.
11 ResamplerH current_resampler_h_chroma = (num_threads > 1 && resampler_h_chroma_mt != nullptr) ? resampler_h_chroma_mt : resampler_h_chroma;
1645
1646 // Y Plane
1647
5/10
✓ Branch 127 → 128 taken 11 times.
✗ Branch 127 → 194 not taken.
✓ Branch 129 → 130 taken 11 times.
✗ Branch 129 → 194 not taken.
✓ Branch 131 → 132 taken 11 times.
✗ Branch 131 → 194 not taken.
✓ Branch 133 → 134 taken 11 times.
✗ Branch 133 → 194 not taken.
✓ Branch 134 → 135 taken 11 times.
✗ Branch 134 → 194 not taken.
11 current_resampler_h_luma(dst->GetWritePtr(), src->GetReadPtr(), dst->GetPitch(), src->GetPitch(), resampling_program_luma, dst_width, dst_height, bits_per_pixel);
1648
1649
2/2
✓ Branch 135 → 136 taken 1 time.
✓ Branch 135 → 154 taken 10 times.
11 if (isRGBPfamily) {
1650
5/10
✓ Branch 137 → 138 taken 1 time.
✗ Branch 137 → 194 not taken.
✓ Branch 139 → 140 taken 1 time.
✗ Branch 139 → 194 not taken.
✓ Branch 141 → 142 taken 1 time.
✗ Branch 141 → 194 not taken.
✓ Branch 143 → 144 taken 1 time.
✗ Branch 143 → 194 not taken.
✓ Branch 144 → 145 taken 1 time.
✗ Branch 144 → 194 not taken.
1 current_resampler_h_luma(dst->GetWritePtr(PLANAR_B), src->GetReadPtr(PLANAR_B), dst->GetPitch(PLANAR_B), src->GetPitch(PLANAR_B), resampling_program_luma, dst_width, dst_height, bits_per_pixel);
1651
5/10
✓ Branch 146 → 147 taken 1 time.
✗ Branch 146 → 194 not taken.
✓ Branch 148 → 149 taken 1 time.
✗ Branch 148 → 194 not taken.
✓ Branch 150 → 151 taken 1 time.
✗ Branch 150 → 194 not taken.
✓ Branch 152 → 153 taken 1 time.
✗ Branch 152 → 194 not taken.
✓ Branch 153 → 175 taken 1 time.
✗ Branch 153 → 194 not taken.
1 current_resampler_h_luma(dst->GetWritePtr(PLANAR_R), src->GetReadPtr(PLANAR_R), dst->GetPitch(PLANAR_R), src->GetPitch(PLANAR_R), resampling_program_luma, dst_width, dst_height, bits_per_pixel);
1652 }
1653
2/2
✓ Branch 154 → 155 taken 6 times.
✓ Branch 154 → 175 taken 4 times.
10 else if (!grey) {
1654
1/2
✓ Branch 155 → 156 taken 6 times.
✗ Branch 155 → 194 not taken.
6 const int dst_chroma_width = dst_width >> vi.GetPlaneWidthSubsampling(PLANAR_U);
1655
1/2
✓ Branch 156 → 157 taken 6 times.
✗ Branch 156 → 194 not taken.
6 const int dst_chroma_height = dst_height >> vi.GetPlaneHeightSubsampling(PLANAR_U);
1656
1657 // U Plane
1658
5/10
✓ Branch 158 → 159 taken 6 times.
✗ Branch 158 → 194 not taken.
✓ Branch 160 → 161 taken 6 times.
✗ Branch 160 → 194 not taken.
✓ Branch 162 → 163 taken 6 times.
✗ Branch 162 → 194 not taken.
✓ Branch 164 → 165 taken 6 times.
✗ Branch 164 → 194 not taken.
✓ Branch 165 → 166 taken 6 times.
✗ Branch 165 → 194 not taken.
6 current_resampler_h_chroma(dst->GetWritePtr(PLANAR_U), src->GetReadPtr(PLANAR_U), dst->GetPitch(PLANAR_U), src->GetPitch(PLANAR_U), resampling_program_chroma, dst_chroma_width, dst_chroma_height, bits_per_pixel);
1659
1660 // V Plane
1661
5/10
✓ Branch 167 → 168 taken 6 times.
✗ Branch 167 → 194 not taken.
✓ Branch 169 → 170 taken 6 times.
✗ Branch 169 → 194 not taken.
✓ Branch 171 → 172 taken 6 times.
✗ Branch 171 → 194 not taken.
✓ Branch 173 → 174 taken 6 times.
✗ Branch 173 → 194 not taken.
✓ Branch 174 → 175 taken 6 times.
✗ Branch 174 → 194 not taken.
6 current_resampler_h_chroma(dst->GetWritePtr(PLANAR_V), src->GetReadPtr(PLANAR_V), dst->GetPitch(PLANAR_V), src->GetPitch(PLANAR_V), resampling_program_chroma, dst_chroma_width, dst_chroma_height, bits_per_pixel);
1662 }
1663
7/10
✓ Branch 175 → 176 taken 11 times.
✗ Branch 175 → 194 not taken.
✓ Branch 176 → 177 taken 10 times.
✓ Branch 176 → 179 taken 1 time.
✓ Branch 177 → 178 taken 10 times.
✗ Branch 177 → 194 not taken.
✗ Branch 178 → 179 not taken.
✓ Branch 178 → 180 taken 10 times.
✓ Branch 181 → 182 taken 1 time.
✓ Branch 181 → 191 taken 10 times.
11 if (vi.IsYUVA() || vi.IsPlanarRGBA())
1664 {
1665
5/10
✓ Branch 183 → 184 taken 1 time.
✗ Branch 183 → 194 not taken.
✓ Branch 185 → 186 taken 1 time.
✗ Branch 185 → 194 not taken.
✓ Branch 187 → 188 taken 1 time.
✗ Branch 187 → 194 not taken.
✓ Branch 189 → 190 taken 1 time.
✗ Branch 189 → 194 not taken.
✓ Branch 190 → 191 taken 1 time.
✗ Branch 190 → 194 not taken.
1 current_resampler_h_luma(dst->GetWritePtr(PLANAR_A), src->GetReadPtr(PLANAR_A), dst->GetPitch(PLANAR_A), src->GetPitch(PLANAR_A), resampling_program_luma, dst_width, dst_height, bits_per_pixel);
1666 }
1667
1668 }
1669
1670 13 return dst;
1671 13 }
1672
1673 25 ResamplerH FilteredResizeH::GetResampler(int CPU, int pixelsize, int bits_per_pixel, ResamplingProgram* program, ResamplerH &out_resampler_h_alternative_for_mt, IScriptEnvironment* env)
1674 {
1675 25 out_resampler_h_alternative_for_mt = nullptr;
1676 25 int simd_coeff_count_padding = 8; // even for _ks16_float this is enough, it works differently inside
1677
1678 // Both 8-bit and 16-bit SSSE3 and AVX2 horizontal resizers benefit from processing 16 pixels per cycle.
1679 // Floats also use 32 bytes, but since 32/sizeof(float) = 8, processing 16 pixels is unnecessary.
1680 // Even in C, the code is optimized to be vector-friendly.
1681
3/4
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 24 times.
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 5 not taken.
25 if (pixelsize == 1 || pixelsize == 2)
1682 25 simd_coeff_count_padding = 16; // FIXME: make it 8
1683
1684 // Not only does it prepare and pad for SIMD/vector code, but it also corrects, reorders, and equalizes coefficients
1685 // at the right and bottom ends, since we may have variable kernel sizes due to boundary conditions.
1686 25 resize_prepare_coeffs(program, env, simd_coeff_count_padding);
1687
1688 #ifdef INTEL_INTRINSICS_AVX512
1689 25 const bool has_AVX512_base = (CPU & CPUF_AVX512_BASE) == CPUF_AVX512_BASE; // group flag!
1690 25 const bool has_AVX512_fast = (CPU & CPUF_AVX512_FAST) == CPUF_AVX512_FAST; // group flag!
1691 #endif
1692
1693
2/2
✓ Branch 6 → 7 taken 24 times.
✓ Branch 6 → 47 taken 1 time.
25 if (pixelsize == 1)
1694 {
1695 #ifdef INTEL_INTRINSICS
1696 #ifdef INTEL_INTRINSICS_AVX512
1697
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 42 taken 24 times.
24 if (has_AVX512_base) {
1698 #ifdef PF_GENERIC_UINT_TEST
1699 return resizer_h_avx512_generic_uint8_t; // PF debug
1700 #endif
1701 // feature flag, grouping many avx512 features
1702 // in case of optimized avx512_permutex_vstripe resizer found, set alternative resizer for MT use
1703 out_resampler_h_alternative_for_mt = resizer_h_avx2_generic_uint8_t; // AVX2 should present if AVX512 present
1704 //
1705 // AVX-512 uint8 horizontal resizer selection (pretransposed-coeffs variants, normal builds):
1706 //
1707 // Function | Pixels/iter | Source window | Use case
1708 // ------------------------------------------|-------------|---------------|------------------------------------------
1709 // mpz_ks4_pretransposed_coeffs | 64 | 128 bytes | filter_size <= 4, upscale / mild downscale
1710 // mpz_ks8_pretransposed_coeffs | 64 | 128 bytes | filter_size <= 8, upscale / mild downscale (1st choice)
1711 // 2s32_ks8_pretransposed_coeffs | 2x32 | 2x128 bytes | filter_size <= 8, heavier downscale (~0.5x), two independent source strips
1712 // mpz_ks16_pretransposed_coeffs | 32 | 128 bytes | filter_size <= 16, upscale / mild downscale
1713 // 2s32_ks64_pretransposed_coeffs | 2x32 | 2x128 bytes | filter_size up to ~64, variable inner loop over taps
1714 //
1715 // "mpz" = maskz-permutex: single 128-byte source window per 64 (or 32) target pixels.
1716 // "2s32" = two strips of 32: two independent 128-byte windows to cover wider source spans.
1717 // VNNI path: uses native vpermi2b (VBMI) + dpwssd. BASE path: simulates vpermi2b via
1718 // precomputed word-index/shift-amount pairs fed to vpermw+vpsrlvw (avoids costly VBMI instruction).
1719 //
1720 if (program->filter_size_real <= 4) {
1721 if (!program->resize_h_planar_gather_permutex_vstripe_check(64/*iSamplesInTheGroup*/, 128/*permutex_index_diff_limit*/, 4/*kernel_size*/)) {
1722 /*
1723 (Rocket Lake i7 - 11700, Expr-vertical-stripes + BicubicResize(width*2,height))
1724 Contenders
1725 AVX512 Fast
1726 resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks4_vnni 2760fps (VNNI is not even really used in clangcl)
1727 resize_h_planar_uint8_avx512_permutex_vstripe_ks4_vbmi 2548fps
1728 AVX512 base; No VNNI, No VBMI, both simulating the 8 bit VBMI shuffle
1729 resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks4_base 1478fps (register pressure - twice as much _mm512_permutex2var_epi8 simulation than non-mpz)
1730 resize_h_planar_uint8_avx512_permutex_vstripe_ks4_base 2493fps (despite the _mm512_permutex2var_epi8 simulation, this is only 2-3% slower than vbmi version
1731 resizer_h_avx2_generic_uint8_t 752fps
1732
1733 Winners: (Fast) resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks4_vnni (Base) resize_h_planar_uint8_avx512_permutex_vstripe_ks4_base
1734 */
1735 resize_prepare_coeffs_AVX512_H(program, env, 64/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 4/*fixed_kernel_size*/);
1736 if (has_AVX512_fast)
1737 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks4_pretransposed_coeffs_vnni;
1738 else
1739 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks4_pretransposed_coeffs_base;
1740 }
1741 }
1742 if (program->filter_size_real <= 8) {
1743 /*
1744 resize_h_planar_uint8_avx512_permutex_vstripe_2s32_ks8
1745 - support more downsampling ratios, like
1746 Bicubic/BilinearResize(width/2) and even SinPowResize(width/2) for downsampling of UHD 4k to FHD is working.
1747 - Expected to support scaling ratios from about a bit below 0.5 to infinity (with filter support <=2).
1748
1749 resize_h_planar_uint8_avx512_permutex_vstripe_ks8
1750 - faster with scale ratios from about 1.0 to infinity (with filter support <=4).
1751
1752 These two functions selected in order from faster to slower.
1753 */
1754 if (!program->resize_h_planar_gather_permutex_vstripe_check(64/*iSamplesInTheGroup*/, 128/*permutex_index_diff_limit*/, 8/*kernel_size*/)) { // first try faster ks8
1755 /*
1756 Contenders
1757 AVX512 Fast
1758 resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks8_vnni 3420fps
1759 resize_h_planar_uint8_avx512_permutex_vstripe_ks8_vbmi 3240fps
1760 AVX512 base
1761 resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks8_base 1720fps (register pressure)
1762 resize_h_planar_uint8_avx512_permutex_vstripe_ks8_base 2940fps
1763
1764 Winners: (Fast) resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks8_vnni (Base) resize_h_planar_uint8_avx512_permutex_vstripe_ks8_base
1765 */
1766 resize_prepare_coeffs_AVX512_H(program, env, 64/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 8/*fixed_kernel_size*/);
1767 if (has_AVX512_fast)
1768 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks8_pretransposed_coeffs_vnni;
1769 else
1770 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks8_pretransposed_coeffs_base;
1771 }
1772 if (!program->resize_h_planar_gather_permutex_vstripe_check(32/*iSamplesInTheGroup*/, 128/*permutex_index_diff_limit*/, 8/*kernel_size*/)) { // slower ks8 but more downsample ratio for /2
1773 resize_prepare_coeffs_AVX512_H(program, env, 32/*iSamplesInTheGroup*/, 2/*iGroupsCount*/, 8/*fixed_kernel_size*/);
1774 if (has_AVX512_fast)
1775 return resize_h_planar_uint8_avx512_permutex_vstripe_2s32_ks8_pretransposed_coeffs_vnni;
1776 else
1777 return resize_h_planar_uint8_avx512_permutex_vstripe_2s32_ks8_pretransposed_coeffs_base;
1778 }
1779 }
1780 if (program->filter_size_real <= 16) {
1781 // yes: LanczosResize(int(width*0.9 + 0.5), height, taps=4) kernel size 9 (K)
1782 // yes: LanczosResize(int(width*1.1 + 0.5), height, taps=5) kernel size 10 (L)
1783 // yes: LanczosResize(int(width*1.1 + 0.5), height, taps=6) kernel size 12 (M)
1784 // yes: LanczosResize(int(width*0.5 + 0.5), height, taps=3) kernel size 12 (N) (in float only 2s8_ks16 covered this resampling ratio)
1785 if (!program->resize_h_planar_gather_permutex_vstripe_check(32/*iSamplesInTheGroup*/, 128/*permutex_index_diff_limit*/, 16/*kernel_size*/)) {
1786 /*
1787 Contenders:
1788 AVX512 fast
1789 resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks16_vnni 3440fps
1790 resize_h_planar_uint8_avx512_permutex_vstripe_ks16_vbmi 3037fps
1791 AVX512 base
1792 resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks16_base 1686fps (register pressure)
1793 resize_h_planar_uint8_avx512_permutex_vstripe_ks16_base 2909fps
1794
1795 Winners: (Fast) resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks16_vnni (Base) resize_h_planar_uint8_avx512_permutex_vstripe_ks16_base
1796 */
1797 resize_prepare_coeffs_AVX512_H(program, env, 32/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 16/*fixed_kernel_size*/);
1798 if (has_AVX512_fast)
1799 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks16_pretransposed_coeffs_vnni;
1800 else
1801 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_ks16_pretransposed_coeffs_base;
1802 }
1803 }
1804 if (!program->resize_h_planar_gather_permutex_vstripe_check(32/*iSamplesInTheGroup*/, 128/*permutex_index_diff_limit*/, program->filter_size_real/*kernel_size*/))
1805 {
1806 resize_prepare_coeffs_AVX512_H(program, env, 32/*iSamplesInTheGroup*/, 2/*iGroupsCount*/, 0/*fixed_kernel_size: variable-loop kernel*/);
1807 if (has_AVX512_fast)
1808 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_2s32_ks64_pretransposed_coeffs_vnni;
1809 else
1810 return resize_h_planar_uint8_avx512_permutex_vstripe_mpz_2s32_ks64_pretransposed_coeffs_base;
1811 }
1812 out_resampler_h_alternative_for_mt = nullptr; // not needed
1813 }
1814 #endif
1815
1/2
✓ Branch 42 → 43 taken 24 times.
✗ Branch 42 → 44 not taken.
24 if (CPU & CPUF_AVX2) {
1816 24 return resizer_h_avx2_generic_uint8_t;
1817 }
1818 if (CPU & CPUF_SSSE3) {
1819 return resizer_h_ssse3_generic_uint8_16<uint8_t, true>;
1820 }
1821 #endif
1822 return resizer_h_c_generic_uint8_16_vectorized<uint8_t, true>;
1823 //return resize_h_c_planar<uint8_t, 1>;
1824 }
1825
1/2
✓ Branch 47 → 48 taken 1 time.
✗ Branch 47 → 117 not taken.
1 else if (pixelsize == 2) {
1826 #ifdef INTEL_INTRINSICS
1827 #ifdef INTEL_INTRINSICS_AVX512
1828
1/2
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 106 taken 1 time.
1 if (has_AVX512_base) {
1829 #ifdef PF_GENERIC_UINT_TEST
1830 if (bits_per_pixel < 16)
1831 return resizer_h_avx512_generic_uint16_t<true>; // PF debug
1832 else
1833 return resizer_h_avx512_generic_uint16_t<false>; // PF debug
1834 #endif
1835 if (bits_per_pixel < 16)
1836 out_resampler_h_alternative_for_mt = resizer_h_avx2_generic_uint16_t<true>; // AVX2 should present if AVX512 present
1837 else
1838 out_resampler_h_alternative_for_mt = resizer_h_avx2_generic_uint16_t<false>;
1839
1840 // feature flag, grouping many avx512 features
1841 if (program->filter_size_real <= 4) {
1842 if (!program->resize_h_planar_gather_permutex_vstripe_check(32/*iSamplesInTheGroup*/, 64/*permutex_index_diff_limit*/, 4/*kernel_size*/))
1843 {
1844 /*
1845 Contenders :
1846 AVX512 Fast
1847 resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks4_vnni 2578fps
1848 resize_h_planar_uint16_avx512_permutex_vstripe_ks4 2310fps (only base)
1849 resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks4 2643fps
1850 AVX512 Base
1851 resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks4_base 2556fps
1852 resize_h_planar_uint16_avx512_permutex_vstripe_ks4 2310fps (only base)
1853 resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks4 2685fps
1854 Fazit: The MP versions' difference is only two VNNI instructions between BASE/FAST, in benchmarks zero visible speed benefit is seen.
1855 Winners: (Both mp) (Fast) resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks4_vnni (Base) resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks4_base
1856 */
1857 resize_prepare_coeffs_AVX512_H(program, env, 64/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 4/*fixed_kernel_size*/);
1858 if (bits_per_pixel < 16) {
1859 if (has_AVX512_fast)
1860 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks4_pretransposed_coeffs_vnni<true>;
1861 else
1862 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks4_pretransposed_coeffs_base<true>;
1863 }
1864 else {
1865 if (has_AVX512_fast)
1866 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks4_pretransposed_coeffs_vnni<false>;
1867 else
1868 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks4_pretransposed_coeffs_base<false>;
1869 }
1870 }
1871 }
1872 if (program->filter_size_real <= 8) {
1873 if (!program->resize_h_planar_gather_permutex_vstripe_check(32/*iSamplesInTheGroup*/, 64/*permutex_index_diff_limit*/, 8/*kernel_size*/)) {
1874 /*
1875 Contenders:
1876 resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks8_vnni
1877 resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks8_base
1878 resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks8_vnni (20260118) Case Q
1879 resize_h_planar_uint16_avx512_permutex_vstripe_ks8 (base avx512 only, no special intructions)
1880 Fazit: The MP versions' difference is only two VNNI instructions between BASE/FAST, in benchmarks 1-2% visible speed benefit is seen.
1881 Winners: (Both mp) (Fast) resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks8_vnni (Base) resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks8_base
1882 */
1883 resize_prepare_coeffs_AVX512_H(program, env, 64/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 8/*fixed_kernel_size*/);
1884 if (bits_per_pixel < 16) {
1885 if (has_AVX512_fast)
1886 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks8_pretransposed_coeffs_vnni<true>;
1887 else
1888 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks8_pretransposed_coeffs_base<true>;
1889 }
1890 else {
1891 if (has_AVX512_fast)
1892 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks8_pretransposed_coeffs_vnni<false>;
1893 else
1894 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_2s32_ks8_pretransposed_coeffs_base<false>;
1895 }
1896 } // check(32/*iSamplesInTheGroup*/, 64/*permutex_index_diff_limit*/, 8/*kernel_size*/)
1897
1898 //
1899 // Analysis C_VNNI C_BASE E_VNNI E_BASE R_VNNI R_BASE
1900 // resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks8 3588 3604 3576 3601 3426 3611 [fps]
1901 // resize_h_planar_uint16_avx512_permutex_vstripe_2s16_ks8 3383 3269 3378 3287 3293 3270
1902 // Case C LanczosResize(int(width*0.5 + 0.5), height, taps=1) kernel size 4, downsampling
1903 // Case E LanczosResize(int(width*0.5 + 0.5), height, taps=2) kernel size 8
1904 // Case R: LanczosResize(int(width*0.5 + 0.5), height, taps=2) kernel size 8
1905 if (!program->resize_h_planar_gather_permutex_vstripe_check(16/*iSamplesInTheGroup*/, 64/*permutex_index_diff_limit*/, 8/*kernel_size*/)) {
1906 resize_prepare_coeffs_AVX512_H(program, env, 64/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 8/*fixed_kernel_size*/);
1907 if (bits_per_pixel < 16) {
1908 if (has_AVX512_fast)
1909 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks8_pretransposed_coeffs_vnni<true>;
1910 else
1911 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks8_pretransposed_coeffs_base<true>;
1912 }
1913 else {
1914 // bits_per_pixel == 16
1915 if (has_AVX512_fast)
1916 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks8_pretransposed_coeffs_vnni<false>;
1917 else
1918 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks8_pretransposed_coeffs_base<false>;
1919 }
1920 } // check(16/*iSamplesInTheGroup*/, 64/*permutex_index_diff_limit*/, 8/*kernel_size*/)
1921
1922 } // if (program->filter_size_real <= 8)
1923
1924 if (program->filter_size_real <= 16) {
1925 if (!program->resize_h_planar_gather_permutex_vstripe_check(32/*iSamplesInTheGroup*/, 64/*permutex_index_diff_limit*/, 16/*kernel_size*/))
1926 {
1927 // yes: LanczosResize(int(width*0.9 + 0.5), height, taps=4) kernel size 9 (K)
1928 // yes: LanczosResize(int(width*1.1 + 0.5), height, taps=5) kernel size 10 (L)
1929 // yes: LanczosResize(int(width*1.1 + 0.5), height, taps=6) kernel size 12 (M)
1930 // no: LanczosResize(int(width*0.5 + 0.5), height, taps=3) kernel size 12 (N) (in float only 2s8_ks16 covered this resampling ratio)
1931 /*
1932 Contenders (none):
1933 AVX512 Fast
1934 resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks16_vnni 1851 1853 2189
1935 AVX512 Base
1936 resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks16_base 1889 1869 2203 (? within measurement error, but quicker than VNNI??)
1937 resizer_h_avx2_generic_uint16_t (fallback) 1156 1085 1292
1938 Winners: (Both mp) (Fast) resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks8_vnni (Base) resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks8_base
1939 */
1940 resize_prepare_coeffs_AVX512_H(program, env, 32/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 16/*fixed_kernel_size*/);
1941 if (bits_per_pixel < 16) {
1942 if (has_AVX512_fast)
1943 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks16_pretransposed_coeffs_vnni<true>;
1944 else
1945 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks16_pretransposed_coeffs_base<true>;
1946 }
1947 else {
1948 if (has_AVX512_fast)
1949 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks16_pretransposed_coeffs_vnni<false>;
1950 else
1951 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_ks16_pretransposed_coeffs_base<false>;
1952 }
1953 }
1954 }
1955 // "ks48" catch-all: handles any filter_size_real that fits within the
1956 // _mm512_permutex2var_epi16 index range of 64 uint16 elements (two ZMMs).
1957 // The check condition is: pixel_offset[x+15] - pixel_offset[x] + filter_size_real - 1 < 64.
1958 // At 1:1 scale the 16-pixel group spans 15 source positions, leaving room for
1959 // filter_size_real up to 48 (max even value: 15 + 48 - 1 = 62 < 64; 49 rounds up
1960 // to 50 → 15 + 49 = 64, fails). For downscaling the offset span grows and the
1961 // usable kernel shrinks — the check enforces this automatically per x-group.
1962 // The function itself has no hard 48 limit; it loops over filter_size_real directly.
1963 if (!program->resize_h_planar_gather_permutex_vstripe_check(16/*iSamplesInTheGroup*/, 64/*permutex_index_diff_limit*/, program->filter_size_real/*kernel_size*/))
1964 {
1965 resize_prepare_coeffs_AVX512_H(program, env, 64/*iSamplesInTheGroup*/, 1/*iGroupsCount*/, 0/*fixed_kernel_size: variable-loop kernel*/);
1966 if (bits_per_pixel < 16) {
1967 if (has_AVX512_fast)
1968 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks48_pretransposed_coeffs_vnni<true>;
1969 else
1970 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks48_pretransposed_coeffs_base<true>;
1971 }
1972 else {
1973 if (has_AVX512_fast)
1974 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks48_pretransposed_coeffs_vnni<false>;
1975 else
1976 return resize_h_planar_uint16_avx512_permutex_vstripe_mp_4s16_ks48_pretransposed_coeffs_base<false>;
1977 }
1978 }
1979 out_resampler_h_alternative_for_mt = nullptr; // not needed
1980 } // has_AVX512_base
1981 #endif
1982
1/2
✓ Branch 106 → 107 taken 1 time.
✗ Branch 106 → 110 not taken.
1 if (CPU & CPUF_AVX2) {
1983
1/2
✗ Branch 107 → 108 not taken.
✓ Branch 107 → 109 taken 1 time.
1 if (bits_per_pixel < 16)
1984 return resizer_h_avx2_generic_uint16_t<true>;
1985 else
1986 1 return resizer_h_avx2_generic_uint16_t<false>;
1987 }
1988 if (CPU & CPUF_SSSE3) {
1989 if (bits_per_pixel < 16)
1990 return resizer_h_ssse3_generic_uint8_16<uint16_t, true>;
1991 else
1992 return resizer_h_ssse3_generic_uint8_16<uint16_t, false>;
1993 }
1994 #endif
1995 if (bits_per_pixel == 16)
1996 return resizer_h_c_generic_uint8_16_vectorized<uint16_t, false>;
1997 // return resize_h_c_planar<uint16_t, 0>;
1998 else
1999 return resizer_h_c_generic_uint8_16_vectorized<uint16_t, true>;
2000 // return resize_h_c_planar<uint16_t, 1>;
2001 }
2002 else { //if (pixelsize == 4)
2003 #ifdef INTEL_INTRINSICS
2004 #ifdef INTEL_INTRINSICS_AVX512
2005 if (has_AVX512_base) {
2006 // feature flag, grouping many avx512 features
2007
2008 // these perform very poorly in Prefetch, so we provide alternative generic version for MT
2009
2010 if (program->filter_size_real <= 4) {
2011 // up to 4 coeffs it can be highly optimized with transposes, gather/permutex choice
2012 out_resampler_h_alternative_for_mt = resizer_h_avx512_generic_float_pix16_sub4_ks_4_8_16; // jolly joker
2013 if (!program->resize_h_planar_gather_permutex_vstripe_check(16 /*iSamplesInTheGroup*/, 32 /*permutex_index_diff_limit*/, 4 /*kernel_size*/)) {
2014 return resize_h_planar_float_avx512_permutex_vstripe_ks4;
2015 }
2016 return resize_h_planar_float_avx512_transpose_vstripe_ks4;
2017 }
2018 if (program->filter_size_real <= 8) {
2019 // up to 8 coeffs it can be highly optimized with transposes, gather/permutex choice
2020 out_resampler_h_alternative_for_mt = resizer_h_avx512_generic_float_pix16_sub4_ks_4_8_16; // jolly joker
2021 // first check 16 pixels per cycle version, probably resize_h_planar_float_avx512_permutex_vstripe_2s8_ks8 is faster,
2022 // if not possible, then 8 pixels per cycle
2023 if (program->resize_h_planar_gather_permutex_vstripe_check(16/*iSamplesInTheGroup*/, 32/*permutex_index_diff_limit*/, 8/*kernel_size*/)) {
2024 // 16 pixels per cycle version of permutex was not possible, try 2x8 version
2025 if (!program->resize_h_planar_gather_permutex_vstripe_check(8/*iSamplesInTheGroup*/, 32/*permutex_index_diff_limit*/, 8/*kernel_size*/)) {
2026 return resize_h_planar_float_avx512_permutex_vstripe_2s8_ks8; // 2x8 output version: better than transpose and generic
2027 }
2028 return resize_h_planar_float_avx512_transpose_vstripe_ks8;
2029 // Speed ranking fps, just to have a clue, higher is better.
2030 // resize_h_planar_float_avx512_permutex_vstripe_2s8_ks8: 3482
2031 // resize_h_planar_float_avx512_transpose_vstripe_ks8: 3186
2032 // generic resizer_h_avx512_generic_float_pix16_sub4_ks_4_8_16: 2772
2033 }
2034 // Speed ranking fps, just to have a clue, higher is better.
2035 // resize_h_planar_float_avx512_permutex_vstripe_2s8_ks8: 2040 2390 1221
2036 // resize_h_planar_float_avx512_permutex_vstripe_ks8: 2847 3236 1775
2037 return resize_h_planar_float_avx512_permutex_vstripe_ks8;
2038 }
2039
2040 if (program->filter_size_real <= 16) {
2041 // Dispatcher for float ks16 (filter_size_real 9..16).
2042 //
2043 // check(N, 32, ks) returns TRUE when N consecutive output pixels' source span
2044 // EXCEEDS 32 floats (two ZMMs), making _mm512_permutex2var_ps unusable for that
2045 // group size. FALSE means the group fits and permutex is valid.
2046 //
2047 // Strategy: try the widest feasible group first (best parallelism), fall back
2048 // to progressively narrower groups, then to generic if none fit.
2049 //
2050 // check(16)=false → ks16: all 16-px groups fit in 32 sources (mild downscale / upscale)
2051 // check(16)=true,
2052 // check(8)=false → 2s8: 16-px groups too wide, but 8-px groups fit (moderate downscale)
2053 // check(8)=true,
2054 // check(4)=false → 4s4: even 8-px groups too wide, but 4-px groups fit (heavy downscale)
2055 // check(4)=true → generic: even 4-px groups too wide (very heavy downscale)
2056 //
2057 // Note: 4s4_ks16 is benchmarked SLOWER than generic in its applicable range because
2058 // at such heavy downscale ratios the source taps are nearly consecutive in memory,
2059 // so the generic's sequential-load FMA tree outperforms 4x permutex2var + 3x blend
2060 // (112 port-5 ops vs generic's streaming loads). Kept for possible future use.
2061 out_resampler_h_alternative_for_mt = resizer_h_avx512_generic_float_pix16_sub4_ks_4_8_16; // jolly joker
2062 if (program->resize_h_planar_gather_permutex_vstripe_check(16/*iSamplesInTheGroup*/, 32/*permutex_index_diff_limit*/, 16/*kernel_size*/)) {
2063 // 16-px groups are too wide for permutex2var_ps — ks16 cannot be used.
2064 if (!program->resize_h_planar_gather_permutex_vstripe_check(8/*iSamplesInTheGroup*/, 32/*permutex_index_diff_limit*/, 16/*kernel_size*/)) {
2065 // 8-px groups fit: use 2s8_ks16 (2 independent groups of 8, each in a 32-float window).
2066 // generic resizer_h_avx512_generic_float_pix16_sub4_ks_4_8_16: 2650 fps
2067 // 2s8: 3392 fps
2068 resize_prepare_coeffs_AVX512_float_H(program, env);
2069 return resize_h_planar_float_avx512_permutex_vstripe_2s8_ks16;
2070 }
2071 // 8-px groups are also too wide.
2072 #if 0
2073 // 4s4_ks16 DISABLED: slower than generic in this range (heavy downscale).
2074 // At such ratios source taps are nearly sequential, so generic's streaming
2075 // loads beat 4x permutex2var + 3x blend (112 vs ~48 port-5 ops per y).
2076 // generic: 4081 fps, 4s4_ks16: 3113 fps
2077 if (!program->resize_h_planar_gather_permutex_vstripe_check(4/*iSamplesInTheGroup*/, 32/*permutex_index_diff_limit*/, 16/*kernel_size*/)) {
2078 resize_prepare_coeffs_AVX512_float_H(program, env);
2079 return resize_h_planar_float_avx512_permutex_vstripe_4s4_ks16;
2080 }
2081 #endif
2082 // Even 4-px groups too wide (or 4s4 disabled): fall back to generic.
2083 return resizer_h_avx512_generic_float_pix16_sub4_ks_4_8_16;
2084 }
2085 // 16-px groups fit: use ks16 (single source group, best case).
2086 // ks16: 5500 fps (reference), generic: 2650 fps
2087 resize_prepare_coeffs_AVX512_float_H(program, env);
2088 return resize_h_planar_float_avx512_permutex_vstripe_ks16;
2089 }
2090
2091 return resizer_h_avx512_generic_float_pix16_sub4_ks_4_8_16;
2092 // other candidates were tested:
2093 // return resizer_h_avx512_generic_float_pix8_sub8_ks16;
2094 // return resizer_h_avx512_generic_float_pix16_sub16_ks8;
2095 // return resizer_h_avx512_generic_float_pix32_sub8_ks8;
2096 // return resizer_h_avx2_generic_float_pix8_sub2; // like AVX2 version
2097 // return resizer_h_avx512_generic_float_pix8_sub2; // like AVX2 version
2098 // return resizer_h_avx512_generic_float_pix8_sub4_ks8;
2099 // return resizer_h_avx512_generic_float_pix16_sub4_ks4;
2100 // return resizer_h_avx512_generic_float_pix16_sub4_ks8;
2101 // return resizer_h_avx2_generic_float;
2102 }
2103 #endif
2104 if (CPU & CPUF_AVX2) {
2105 // up to 4 coeffs it can be highly optimized with transposes, gather/permutex choice
2106 // These perform very poorly in Prefetch, so we provide alternative generic version for MT
2107 out_resampler_h_alternative_for_mt = resize_h_planar_float_avx2_permutex_vstripe_ks4; // jolly joker
2108 if (program->filter_size_real <= 4) {
2109 if (program->resize_h_planar_gather_permutex_vstripe_check(8 /*iSamplesInTheGroup*/, 8 /*permutex_index_diff_limit*/, 4 /*kernel_size*/)) {
2110 switch (program->filter_size_real) {
2111 case 1: return resize_h_planar_float_avx2_transpose_vstripe_ks4<1>; break;
2112 case 2: return resize_h_planar_float_avx2_transpose_vstripe_ks4<2>; break;
2113 case 3: return resize_h_planar_float_avx2_transpose_vstripe_ks4<3>; break;
2114 case 4: return resize_h_planar_float_avx2_transpose_vstripe_ks4<0>; break;
2115 }
2116 }
2117 return resize_h_planar_float_avx2_permutex_vstripe_ks4;
2118 }
2119 #if 0
2120 // ks8 DISABLED: slower than generic (1102 fps vs 1196 fps).
2121 // Root cause: avx2_permutex2var_ps (dual-source simulation) costs 3 port-5 ops per call
2122 // (2x permutevar8x32 + blendv). With 8 taps: 24 port-5 ops per y-iteration, making port 5
2123 // the bottleneck at ~24 cycles. The generic uses gather (ports 2+3, load units) instead,
2124 // not competing on port 5 at all. Single-source ks4 uses 1 port-5 per tap (4 total) and wins;
2125 // dual-source ks8 uses 3x that and loses. Crossover is around tap 5.
2126 // Pre-transposing coefficients is feasible but saves only outside-y-loop cost (amortized
2127 // over height scanlines = negligible) and does not touch the y-loop bottleneck.
2128 if (program->filter_size_real <= 8) {
2129 // check(8,16,8)=false: 8 output pixels' source span fits in 16 floats (two YMMs via avx2_permutex2var_ps)
2130 if (!program->resize_h_planar_gather_permutex_vstripe_check(8, 16, 8)) {
2131 out_resampler_h_alternative_for_mt = resizer_h_avx2_generic_float_pix16_sub4_ks_4_8_16;
2132 return resize_h_planar_float_avx2_permutex_vstripe_ks8;
2133 }
2134 }
2135 #endif
2136 return resizer_h_avx2_generic_float_pix16_sub4_ks_4_8_16; // new generic, like avx512 version
2137 // return resizer_h_avx2_generic_float; old generic would be named pix8_sub2_ks8
2138 }
2139 if (CPU & CPUF_SSSE3) {
2140 // up to 4 coeffs it can be highly optimized with transposes
2141 // These perform very poorly in Prefetch, so we provide alternative generic version for MT
2142 if (program->filter_size_real <= 4)
2143 out_resampler_h_alternative_for_mt = resizer_h_ssse3_generic_float; // jolly joker
2144 switch (program->filter_size_real) {
2145 case 1: return resize_h_planar_float_sse_transpose_vstripe_ks4<1>; break;
2146 case 2: return resize_h_planar_float_sse_transpose_vstripe_ks4<2>; break;
2147 case 3: return resize_h_planar_float_sse_transpose_vstripe_ks4<3>; break;
2148 case 4: return resize_h_planar_float_sse_transpose_vstripe_ks4<0>; break;
2149 default: return resizer_h_ssse3_generic_float;
2150 }
2151 }
2152 #endif
2153 return resize_h_c_planar<float, 0>;
2154 }
2155 }
2156
2157 33 FilteredResizeH::~FilteredResizeH(void)
2158 {
2159
2/4
✓ Branch 2 → 3 taken 21 times.
✗ Branch 2 → 6 not taken.
✓ Branch 3 → 4 taken 21 times.
✗ Branch 3 → 6 not taken.
21 if (resampling_program_luma) { delete resampling_program_luma; }
2160
3/4
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 10 taken 15 times.
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 10 not taken.
21 if (resampling_program_chroma) { delete resampling_program_chroma; }
2161 33 }
2162
2163 /***************************************
2164 ***** Filtered Resize - Vertical ******
2165 ***************************************/
2166
2167 22 FilteredResizeV::FilteredResizeV(PClip _child, double subrange_top, double subrange_height,
2168 int target_height, ResamplingFunction* func,
2169 bool preserve_center, int chroma_placement,
2170 22 IScriptEnvironment* env)
2171 : GenericVideoFilter(_child),
2172
2/4
✓ Branch 2 → 3 taken 22 times.
✗ Branch 2 → 51 not taken.
✓ Branch 3 → 4 taken 22 times.
✗ Branch 3 → 49 not taken.
22 resampling_program_luma(0), resampling_program_chroma(0)
2173 {
2174
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 22 times.
22 if (target_height <= 0)
2175 env->ThrowError("Resize: Height must be greater than 0.");
2176
2177
1/2
✓ Branch 7 → 8 taken 22 times.
✗ Branch 7 → 52 not taken.
22 pixelsize = vi.ComponentSize(); // AVS16
2178
1/2
✓ Branch 8 → 9 taken 22 times.
✗ Branch 8 → 52 not taken.
22 bits_per_pixel = vi.BitsPerComponent();
2179
1/2
✓ Branch 9 → 10 taken 22 times.
✗ Branch 9 → 52 not taken.
22 grey = vi.IsY();
2180
5/8
✓ Branch 10 → 11 taken 22 times.
✗ Branch 10 → 52 not taken.
✓ Branch 11 → 12 taken 21 times.
✓ Branch 11 → 14 taken 1 time.
✓ Branch 12 → 13 taken 21 times.
✗ Branch 12 → 52 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 21 times.
22 bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
2181
2182
9/10
✓ Branch 16 → 17 taken 22 times.
✗ Branch 16 → 52 not taken.
✓ Branch 17 → 18 taken 20 times.
✓ Branch 17 → 21 taken 2 times.
✓ Branch 18 → 19 taken 8 times.
✓ Branch 18 → 21 taken 12 times.
✓ Branch 19 → 20 taken 7 times.
✓ Branch 19 → 21 taken 1 time.
✓ Branch 22 → 23 taken 7 times.
✓ Branch 22 → 26 taken 15 times.
22 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
2183
1/2
✓ Branch 23 → 24 taken 7 times.
✗ Branch 23 → 52 not taken.
7 const int mask = (1 << vi.GetPlaneHeightSubsampling(PLANAR_U)) - 1;
2184
2185
2/2
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 6 times.
7 if (target_height & mask)
2186
1/2
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 52 taken 1 time.
1 env->ThrowError("Resize: Planar destination height must be a multiple of %d.", mask + 1);
2187 }
2188
2189
7/8
✓ Branch 26 → 27 taken 21 times.
✗ Branch 26 → 52 not taken.
✓ Branch 27 → 28 taken 3 times.
✓ Branch 27 → 30 taken 18 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 30 taken 1 time.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 33 taken 19 times.
21 if (vi.IsRGB() && !isRGBPfamily)
2190 2 subrange_top = vi.height - subrange_top - subrange_height; // packed RGB upside down
2191
2192 #ifdef INTEL_INTRINSICS
2193
1/2
✓ Branch 33 → 34 taken 21 times.
✗ Branch 33 → 52 not taken.
21 int cpu = env->GetCPUFlags();
2194 #else
2195 int cpu = 0;
2196 #endif
2197
2198 double center_pos_v_luma;
2199 double center_pos_v_chroma;
2200
1/2
✓ Branch 34 → 35 taken 21 times.
✗ Branch 34 → 52 not taken.
21 GetCenterShiftForResizers(center_pos_v_luma, center_pos_v_chroma, preserve_center, chroma_placement, vi, false /* for vertical */);
2201 // 3.7.4- parameter, old Avisynth behavior: 0.5, 0.5
2202
2203 // Create resampling program and pitch table
2204
1/2
✓ Branch 35 → 36 taken 21 times.
✗ Branch 35 → 52 not taken.
21 resampling_program_luma = func->GetResamplingProgram(vi.height, subrange_top, subrange_height, target_height, bits_per_pixel,
2205 center_pos_v_luma, center_pos_v_luma, // for resizing it's the same for source and dest
2206 env);
2207
1/2
✓ Branch 36 → 37 taken 21 times.
✗ Branch 36 → 52 not taken.
21 resampler_luma = GetResampler(cpu, pixelsize, bits_per_pixel, resampling_program_luma, env);
2208
2209
9/10
✓ Branch 37 → 38 taken 21 times.
✗ Branch 37 → 52 not taken.
✓ Branch 38 → 39 taken 19 times.
✓ Branch 38 → 42 taken 2 times.
✓ Branch 39 → 40 taken 7 times.
✓ Branch 39 → 42 taken 12 times.
✓ Branch 40 → 41 taken 6 times.
✓ Branch 40 → 42 taken 1 time.
✓ Branch 43 → 44 taken 6 times.
✓ Branch 43 → 48 taken 15 times.
21 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
2210
1/2
✓ Branch 44 → 45 taken 6 times.
✗ Branch 44 → 52 not taken.
6 const int shift = vi.GetPlaneHeightSubsampling(PLANAR_U);
2211 6 const int div = 1 << shift;
2212
2213 12 resampling_program_chroma = func->GetResamplingProgram(
2214
1/2
✓ Branch 45 → 46 taken 6 times.
✗ Branch 45 → 52 not taken.
6 vi.height >> shift,
2215 subrange_top / div,
2216 subrange_height / div,
2217 target_height >> shift,
2218 bits_per_pixel,
2219 center_pos_v_chroma, center_pos_v_chroma, // for resizing it's the same for source and dest
2220 env);
2221
2222
1/2
✓ Branch 46 → 47 taken 6 times.
✗ Branch 46 → 52 not taken.
6 resampler_chroma = GetResampler(cpu, pixelsize, bits_per_pixel, resampling_program_chroma, env);
2223 }
2224
2225 // Change target video info size
2226 21 vi.height = target_height;
2227 22 }
2228
2229 13 PVideoFrame __stdcall FilteredResizeV::GetFrame(int n, IScriptEnvironment* env)
2230 {
2231
1/2
✓ Branch 3 → 4 taken 13 times.
✗ Branch 3 → 93 not taken.
13 PVideoFrame src = child->GetFrame(n, env);
2232
1/2
✓ Branch 4 → 5 taken 13 times.
✗ Branch 4 → 91 not taken.
13 PVideoFrame dst = env->NewVideoFrameP(vi, &src);
2233
1/2
✓ Branch 6 → 7 taken 13 times.
✗ Branch 6 → 89 not taken.
13 int src_pitch = src->GetPitch();
2234
1/2
✓ Branch 8 → 9 taken 13 times.
✗ Branch 8 → 89 not taken.
13 int dst_pitch = dst->GetPitch();
2235
1/2
✓ Branch 10 → 11 taken 13 times.
✗ Branch 10 → 89 not taken.
13 const BYTE* srcp = src->GetReadPtr();
2236
1/2
✓ Branch 12 → 13 taken 13 times.
✗ Branch 12 → 89 not taken.
13 BYTE* dstp = dst->GetWritePtr();
2237
2238
5/8
✓ Branch 13 → 14 taken 13 times.
✗ Branch 13 → 89 not taken.
✓ Branch 14 → 15 taken 12 times.
✓ Branch 14 → 17 taken 1 time.
✓ Branch 15 → 16 taken 12 times.
✗ Branch 15 → 89 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 12 times.
13 bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
2239
2240 // Do resizing
2241
4/6
✓ Branch 19 → 20 taken 13 times.
✗ Branch 19 → 89 not taken.
✓ Branch 20 → 21 taken 11 times.
✓ Branch 20 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
✗ Branch 22 → 89 not taken.
13 int work_width = vi.IsPlanar() ? vi.width : vi.BytesFromPixels(vi.width) / pixelsize; // packed RGB: or vi.width * vi.NumComponent()
2242
1/2
✓ Branch 24 → 25 taken 13 times.
✗ Branch 24 → 89 not taken.
13 resampler_luma(dstp, srcp, dst_pitch, src_pitch, resampling_program_luma, work_width, vi.height, bits_per_pixel);
2243
2/2
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 44 taken 12 times.
13 if (isRGBPfamily)
2244 {
2245
1/2
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 89 not taken.
1 src_pitch = src->GetPitch(PLANAR_B);
2246
1/2
✓ Branch 29 → 30 taken 1 time.
✗ Branch 29 → 89 not taken.
1 dst_pitch = dst->GetPitch(PLANAR_B);
2247
1/2
✓ Branch 31 → 32 taken 1 time.
✗ Branch 31 → 89 not taken.
1 srcp = src->GetReadPtr(PLANAR_B);
2248
1/2
✓ Branch 33 → 34 taken 1 time.
✗ Branch 33 → 89 not taken.
1 dstp = dst->GetWritePtr(PLANAR_B);
2249
2250
1/2
✓ Branch 34 → 35 taken 1 time.
✗ Branch 34 → 89 not taken.
1 resampler_luma(dstp, srcp, dst_pitch, src_pitch, resampling_program_luma, work_width, vi.height, bits_per_pixel);
2251
2252
1/2
✓ Branch 36 → 37 taken 1 time.
✗ Branch 36 → 89 not taken.
1 src_pitch = src->GetPitch(PLANAR_R);
2253
1/2
✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 89 not taken.
1 dst_pitch = dst->GetPitch(PLANAR_R);
2254
1/2
✓ Branch 40 → 41 taken 1 time.
✗ Branch 40 → 89 not taken.
1 srcp = src->GetReadPtr(PLANAR_R);
2255
1/2
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 89 not taken.
1 dstp = dst->GetWritePtr(PLANAR_R);
2256
2257
1/2
✓ Branch 43 → 70 taken 1 time.
✗ Branch 43 → 89 not taken.
1 resampler_luma(dstp, srcp, dst_pitch, src_pitch, resampling_program_luma, work_width, vi.height, bits_per_pixel);
2258 }
2259
7/8
✓ Branch 44 → 45 taken 8 times.
✓ Branch 44 → 48 taken 4 times.
✓ Branch 45 → 46 taken 8 times.
✗ Branch 45 → 89 not taken.
✓ Branch 46 → 47 taken 6 times.
✓ Branch 46 → 48 taken 2 times.
✓ Branch 49 → 50 taken 6 times.
✓ Branch 49 → 70 taken 6 times.
12 else if (!grey && vi.IsPlanar()) {
2260
1/2
✓ Branch 50 → 51 taken 6 times.
✗ Branch 50 → 89 not taken.
6 int width = vi.width >> vi.GetPlaneWidthSubsampling(PLANAR_U);
2261
1/2
✓ Branch 51 → 52 taken 6 times.
✗ Branch 51 → 89 not taken.
6 int height = vi.height >> vi.GetPlaneHeightSubsampling(PLANAR_U);
2262
2263 // Plane U resizing
2264
1/2
✓ Branch 53 → 54 taken 6 times.
✗ Branch 53 → 89 not taken.
6 src_pitch = src->GetPitch(PLANAR_U);
2265
1/2
✓ Branch 55 → 56 taken 6 times.
✗ Branch 55 → 89 not taken.
6 dst_pitch = dst->GetPitch(PLANAR_U);
2266
1/2
✓ Branch 57 → 58 taken 6 times.
✗ Branch 57 → 89 not taken.
6 srcp = src->GetReadPtr(PLANAR_U);
2267
1/2
✓ Branch 59 → 60 taken 6 times.
✗ Branch 59 → 89 not taken.
6 dstp = dst->GetWritePtr(PLANAR_U);
2268
2269
1/2
✓ Branch 60 → 61 taken 6 times.
✗ Branch 60 → 89 not taken.
6 resampler_chroma(dstp, srcp, dst_pitch, src_pitch, resampling_program_chroma, width, height, bits_per_pixel);
2270
2271 // Plane V resizing
2272
1/2
✓ Branch 62 → 63 taken 6 times.
✗ Branch 62 → 89 not taken.
6 src_pitch = src->GetPitch(PLANAR_V);
2273
1/2
✓ Branch 64 → 65 taken 6 times.
✗ Branch 64 → 89 not taken.
6 dst_pitch = dst->GetPitch(PLANAR_V);
2274
1/2
✓ Branch 66 → 67 taken 6 times.
✗ Branch 66 → 89 not taken.
6 srcp = src->GetReadPtr(PLANAR_V);
2275
1/2
✓ Branch 68 → 69 taken 6 times.
✗ Branch 68 → 89 not taken.
6 dstp = dst->GetWritePtr(PLANAR_V);
2276
2277
1/2
✓ Branch 69 → 70 taken 6 times.
✗ Branch 69 → 89 not taken.
6 resampler_chroma(dstp, srcp, dst_pitch, src_pitch, resampling_program_chroma, width, height, bits_per_pixel);
2278 }
2279
2280
7/10
✓ Branch 70 → 71 taken 13 times.
✗ Branch 70 → 89 not taken.
✓ Branch 71 → 72 taken 12 times.
✓ Branch 71 → 74 taken 1 time.
✓ Branch 72 → 73 taken 12 times.
✗ Branch 72 → 89 not taken.
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 75 taken 12 times.
✓ Branch 76 → 77 taken 1 time.
✓ Branch 76 → 86 taken 12 times.
13 if (vi.IsYUVA() || vi.IsPlanarRGBA()) {
2281
1/2
✓ Branch 78 → 79 taken 1 time.
✗ Branch 78 → 89 not taken.
1 src_pitch = src->GetPitch(PLANAR_A);
2282
1/2
✓ Branch 80 → 81 taken 1 time.
✗ Branch 80 → 89 not taken.
1 dst_pitch = dst->GetPitch(PLANAR_A);
2283
1/2
✓ Branch 82 → 83 taken 1 time.
✗ Branch 82 → 89 not taken.
1 srcp = src->GetReadPtr(PLANAR_A);
2284
1/2
✓ Branch 84 → 85 taken 1 time.
✗ Branch 84 → 89 not taken.
1 dstp = dst->GetWritePtr(PLANAR_A);
2285
1/2
✓ Branch 85 → 86 taken 1 time.
✗ Branch 85 → 89 not taken.
1 resampler_luma(dstp, srcp, dst_pitch, src_pitch, resampling_program_luma, work_width, vi.height, bits_per_pixel);
2286 }
2287
2288 13 return dst;
2289 13 }
2290
2291 29 ResamplerV FilteredResizeV::GetResampler(int CPU, int pixelsize, int bits_per_pixel, ResamplingProgram* program, IScriptEnvironment* env)
2292 {
2293
2294 29 resize_prepare_coeffs(program, env, 8);
2295 // for SIMD friendliness and more: consolidate the kernel_size vs filter_size at the end.
2296 // See comments at FilteredResizeH::GetResampler
2297
2298 #ifdef INTEL_INTRINSICS_AVX512
2299 29 const bool has_AVX512_base = (CPU & CPUF_AVX512_BASE) == CPUF_AVX512_BASE;
2300 #endif
2301
2302
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 29 times.
29 if (program->filter_size == 1) {
2303 // Fast pointresize
2304 switch (pixelsize) // AVS16
2305 {
2306 case 1: return resize_v_planar_pointresize<uint8_t>;
2307 case 2: return resize_v_planar_pointresize<uint16_t>;
2308 default: // case 4:
2309 return resize_v_planar_pointresize<float>;
2310 }
2311 }
2312 else {
2313 // Other resizers
2314
2/2
✓ Branch 8 → 9 taken 26 times.
✓ Branch 8 → 16 taken 3 times.
29 if (pixelsize == 1)
2315 {
2316 #ifdef INTEL_INTRINSICS
2317 #ifdef INTEL_INTRINSICS_AVX512
2318
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 26 times.
26 if (has_AVX512_base)
2319 return resize_v_avx512_planar_uint8_t_w_sr;
2320 #endif
2321
1/2
✓ Branch 11 → 12 taken 26 times.
✗ Branch 11 → 13 not taken.
26 if (CPU & CPUF_AVX2)
2322 26 return resize_v_avx2_planar_uint8_t;
2323 if (CPU & CPUF_SSE2)
2324 return resize_v_sse2_planar;
2325 #ifdef X86_32
2326 if (CPU & CPUF_MMX)
2327 return resize_v_mmx_planar;
2328 #endif
2329 #endif
2330 // C version
2331 return resize_v_c_planar_uint8_16_t_auto_vectorized<uint8_t, true>;
2332 }
2333
1/2
✓ Branch 16 → 17 taken 3 times.
✗ Branch 16 → 32 not taken.
3 else if (pixelsize == 2)
2334 {
2335 #ifdef INTEL_INTRINSICS
2336 #ifdef INTEL_INTRINSICS_AVX512
2337
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 21 taken 3 times.
3 if (has_AVX512_base) {
2338 if (bits_per_pixel < 16)
2339 return resize_v_avx512_planar_uint16_t_w_sr<true>;
2340 else
2341 return resize_v_avx512_planar_uint16_t_w_sr<false>;
2342 }
2343 #endif
2344
1/2
✓ Branch 21 → 22 taken 3 times.
✗ Branch 21 → 25 not taken.
3 if (CPU & CPUF_AVX2) {
2345
1/2
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 3 times.
3 if (bits_per_pixel < 16)
2346 return resize_v_avx2_planar_uint16_t<true>;
2347 else
2348 3 return resize_v_avx2_planar_uint16_t<false>;
2349 }
2350 if (CPU & CPUF_SSE2) {
2351 if (bits_per_pixel < 16)
2352 return resize_v_sse2_planar_uint16_t<true>;
2353 else
2354 return resize_v_sse2_planar_uint16_t<false>;
2355 }
2356 #endif
2357 // C version
2358 if(bits_per_pixel == 16)
2359 return resize_v_c_planar_uint8_16_t_auto_vectorized<uint16_t, false>;
2360 else
2361 return resize_v_c_planar_uint8_16_t_auto_vectorized<uint16_t, true>;
2362 }
2363 else // pixelsize== 4
2364 {
2365 #ifdef INTEL_INTRINSICS
2366 #ifdef INTEL_INTRINSICS_AVX512
2367 if (has_AVX512_base) {
2368 // return resize_v_avx512_planar_float; // Old, base version, quicker than avx2 version
2369 // This one is about equal to avx2 version, but only with clang,
2370 // it seems that clang is too good and, probably unrolls the old function version
2371 // out-of-box so much better than MSVC, that it competes with the _w_sr version.
2372 // With MSVC its no-brainer to use avx512
2373 return resize_v_avx512_planar_float_w_sr;
2374 }
2375 #endif
2376 if (CPU & CPUF_AVX2) {
2377 return resize_v_avx2_planar_float_w_sr;
2378 // a memory-optimized version of resize_v_avx2_planar_float
2379 }
2380 if (CPU & CPUF_SSE2) {
2381 return resize_v_sse2_planar_float;
2382 }
2383 #endif
2384 return resize_v_c_planar_float_auto_vectorized;
2385 }
2386 }
2387 }
2388
2389 33 FilteredResizeV::~FilteredResizeV(void)
2390 {
2391
2/4
✓ Branch 2 → 3 taken 21 times.
✗ Branch 2 → 6 not taken.
✓ Branch 3 → 4 taken 21 times.
✗ Branch 3 → 6 not taken.
21 if (resampling_program_luma) { delete resampling_program_luma; }
2392
3/4
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 10 taken 15 times.
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 10 not taken.
21 if (resampling_program_chroma) { delete resampling_program_chroma; }
2393 33 }
2394
2395
2396 /**********************************************
2397 ******* Resampling Factory Methods *******
2398 **********************************************/
2399
2400 12 PClip FilteredResize::CreateResizeH(PClip clip, double subrange_left, double subrange_width, int target_width, bool force,
2401 ResamplingFunction* func, bool preserve_center, int chroma_placement, IScriptEnvironment* env)
2402 {
2403
1/2
✓ Branch 3 → 4 taken 12 times.
✗ Branch 3 → 63 not taken.
12 const VideoInfo& vi = clip->GetVideoInfo();
2404
4/8
✓ Branch 4 → 5 taken 12 times.
✗ Branch 4 → 10 not taken.
✓ Branch 5 → 6 taken 7 times.
✓ Branch 5 → 10 taken 5 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 10 taken 7 times.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 10 not taken.
12 if (!force && subrange_left == 0 && subrange_width == target_width && subrange_width == vi.width) {
2405 return clip;
2406 }
2407 /*
2408 // intentionally left here: don't use crop at special edge cases to avoid inconsistent results across params/color spaces
2409 if (subrange_left == int(subrange_left) && subrange_width == target_width
2410 && subrange_left >= 0 && subrange_left + subrange_width <= vi.width) {
2411 const int mask = ((vi.IsYUV() || vi.IsYUVA()) && !vi.IsY()) ? (1 << vi.GetPlaneWidthSubsampling(PLANAR_U)) - 1 : 0;
2412
2413 if (((int(subrange_left) | int(subrange_width)) & mask) == 0)
2414 return new Crop(int(subrange_left), 0, int(subrange_width), vi.height, 0, clip, env);
2415 }
2416 */
2417 // Convert interleaved yuv to planar yuv
2418
1/2
✓ Branch 10 → 11 taken 12 times.
✗ Branch 10 → 63 not taken.
12 PClip result = clip;
2419
2/4
✓ Branch 11 → 12 taken 12 times.
✗ Branch 11 → 61 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 21 taken 12 times.
12 if (vi.IsYUY2()) {
2420 result = new ConvertYUY2ToYV16_or_Y(result, false /*to_y*/, env);
2421 }
2422
2423
5/12
✓ Branch 21 → 22 taken 12 times.
✗ Branch 21 → 61 not taken.
✓ Branch 22 → 23 taken 12 times.
✗ Branch 22 → 51 not taken.
✓ Branch 23 → 24 taken 12 times.
✗ Branch 23 → 49 not taken.
✓ Branch 24 → 25 taken 12 times.
✗ Branch 24 → 49 not taken.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 12 times.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
12 result = new FilteredResizeH(result, subrange_left, subrange_width, target_width, func, preserve_center, chroma_placement, env);
2424
2425
2/4
✓ Branch 28 → 29 taken 12 times.
✗ Branch 28 → 61 not taken.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 38 taken 12 times.
12 if (vi.IsYUY2()) {
2426 result = new ConvertYV16ToYUY2(result, env);
2427 }
2428
2429
1/2
✓ Branch 38 → 39 taken 12 times.
✗ Branch 38 → 61 not taken.
12 return result;
2430 12 }
2431
2432
2433 12 PClip FilteredResize::CreateResizeV(PClip clip, double subrange_top, double subrange_height, int target_height, bool force,
2434 ResamplingFunction* func, bool preserve_center, int chroma_placement, IScriptEnvironment* env)
2435 {
2436 12 const VideoInfo& vi = clip->GetVideoInfo();
2437
4/8
✓ Branch 4 → 5 taken 12 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 7 times.
✓ Branch 5 → 9 taken 5 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 7 times.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
12 if (!force && subrange_top == 0 && subrange_height == target_height && subrange_height == vi.height) {
2438 return clip;
2439 }
2440 /*
2441 // intentionally left here: don't use crop at special edge cases to avoid inconsistent results across params/color spaces
2442 if (subrange_top == int(subrange_top) && subrange_height == target_height
2443 && subrange_top >= 0 && subrange_top + subrange_height <= vi.height) {
2444 const int mask = ((vi.IsYUV() || vi.IsYUVA()) && !vi.IsY()) ? (1 << vi.GetPlaneHeightSubsampling(PLANAR_U)) - 1 : 0;
2445
2446 if (((int(subrange_top) | int(subrange_height)) & mask) == 0)
2447 return new Crop(0, int(subrange_top), vi.width, int(subrange_height), 0, clip, env);
2448 }
2449 */
2450
4/10
✓ Branch 10 → 11 taken 12 times.
✗ Branch 10 → 20 not taken.
✓ Branch 11 → 12 taken 12 times.
✗ Branch 11 → 18 not taken.
✓ Branch 12 → 13 taken 12 times.
✗ Branch 12 → 18 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 12 times.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
12 return new FilteredResizeV(clip, subrange_top, subrange_height, target_height, func, preserve_center, chroma_placement, env);
2451 }
2452
2453
2454 12 PClip FilteredResize::CreateResize(PClip clip, int target_width, int target_height, const AVSValue* args, int force,
2455 ResamplingFunction* f,
2456 bool preserve_center, const char* placement_name, const int forced_chroma_placement,
2457 IScriptEnvironment* env)
2458 {
2459 // args 0-1-2-3: left-top-width-height
2460
1/2
✓ Branch 3 → 4 taken 12 times.
✗ Branch 3 → 91 not taken.
12 VideoInfo vi = clip->GetVideoInfo();
2461
2/4
✓ Branch 4 → 5 taken 12 times.
✗ Branch 4 → 91 not taken.
✓ Branch 5 → 6 taken 12 times.
✗ Branch 5 → 91 not taken.
12 const double subrange_left = args[0].AsFloat(0), subrange_top = args[1].AsFloat(0);
2462
2463
2/4
✓ Branch 6 → 7 taken 12 times.
✗ Branch 6 → 91 not taken.
✓ Branch 7 → 8 taken 12 times.
✗ Branch 7 → 91 not taken.
12 double subrange_width = args[2].AsDblDef(vi.width), subrange_height = args[3].AsDblDef(vi.height);
2464 // Crop style syntax
2465
2/2
✓ Branch 8 → 9 taken 4 times.
✓ Branch 8 → 10 taken 8 times.
12 if (subrange_width <= 0.0) subrange_width = vi.width - subrange_left + subrange_width;
2466
2/2
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 12 taken 8 times.
12 if (subrange_height <= 0.0) subrange_height = vi.height - subrange_top + subrange_height;
2467
2468
1/2
✓ Branch 12 → 13 taken 12 times.
✗ Branch 12 → 91 not taken.
12 PClip result;
2469 // ensure that the intermediate area is maximal
2470
2471 12 const double area_FirstH = subrange_height * target_width;
2472 12 const double area_FirstV = subrange_width * target_height;
2473
2474 // "minimal area" logic is not necessarily faster because H and V resizers are not the same speed.
2475 // so we keep the traditional max area logic, which is for quality
2476
2477 // use forced_chroma_placement >= 0 and placement_name == nullptr together
2478 12 int chroma_placement = forced_chroma_placement >= 0 ? forced_chroma_placement : ChromaLocation_e::AVS_CHROMA_UNUSED;
2479
2/2
✓ Branch 13 → 14 taken 8 times.
✓ Branch 13 → 29 taken 4 times.
12 if (placement_name) {
2480 // no format-oriented defaults
2481
7/14
✓ Branch 14 → 15 taken 8 times.
✗ Branch 14 → 89 not taken.
✓ Branch 15 → 16 taken 8 times.
✗ Branch 15 → 20 not taken.
✓ Branch 16 → 17 taken 8 times.
✗ Branch 16 → 89 not taken.
✓ Branch 17 → 18 taken 8 times.
✗ Branch 17 → 20 not taken.
✓ Branch 18 → 19 taken 8 times.
✗ Branch 18 → 89 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 8 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 29 taken 8 times.
8 if (vi.IsYV411() || vi.Is420() || vi.Is422()) {
2482 // placement explicite parameter like in ConvertToXXX or Text
2483 // input frame properties, if "auto"
2484 // When called from ConvertToXXX, chroma is not involved.
2485 auto frame0 = clip->GetFrame(0, env);
2486 const AVSMap* props = env->getFramePropsRO(frame0);
2487 chromaloc_parse_merge_with_props(vi, placement_name, props, /* ref*/chroma_placement, ChromaLocation_e::AVS_CHROMA_UNUSED /*default*/, env);
2488 }
2489
2490 }
2491
2492 // 0 - return unchanged if no resize needed
2493 // 1 - force H
2494 // 2 - force V
2495 // 3 - force H and V
2496
2/4
✓ Branch 29 → 30 taken 12 times.
✗ Branch 29 → 31 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 12 times.
12 const bool force_H = force == 1 || force == 3;
2497
2/4
✓ Branch 33 → 34 taken 12 times.
✗ Branch 33 → 35 not taken.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 12 times.
12 const bool force_V = force == 2 || force == 3;
2498 #ifdef DTL2D
2499 if (force == 3) // not very good manual forcing of special 2pass mode, better to nake selection if both H and V resizs required, currently for test only
2500 result = new FilteredResize_2p(clip,
2501 subrange_left, subrange_width, target_width,
2502 subrange_top, subrange_height, target_height,
2503 f, preserve_center, chroma_placement, env);
2504 else
2505 #endif
2506 {
2507
1/2
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 49 taken 12 times.
12 if (area_FirstH < area_FirstV)
2508 {
2509 result = CreateResizeV(clip, subrange_top, subrange_height, target_height, force_V, f, preserve_center, chroma_placement, env);
2510 result = CreateResizeH(result, subrange_left, subrange_width, target_width, force_H, f, preserve_center, chroma_placement, env);
2511 }
2512 else
2513 {
2514
3/6
✓ Branch 49 → 50 taken 12 times.
✗ Branch 49 → 81 not taken.
✓ Branch 50 → 51 taken 12 times.
✗ Branch 50 → 79 not taken.
✓ Branch 51 → 52 taken 12 times.
✗ Branch 51 → 77 not taken.
12 result = CreateResizeH(clip, subrange_left, subrange_width, target_width, force_H, f, preserve_center, chroma_placement, env);
2515
3/6
✓ Branch 54 → 55 taken 12 times.
✗ Branch 54 → 87 not taken.
✓ Branch 55 → 56 taken 12 times.
✗ Branch 55 → 85 not taken.
✓ Branch 56 → 57 taken 12 times.
✗ Branch 56 → 83 not taken.
12 result = CreateResizeV(result, subrange_top, subrange_height, target_height, force_V, f, preserve_center, chroma_placement, env);
2516 }
2517 }
2518 12 return result;
2519 }
2520
2521 4 AVSValue __cdecl FilteredResize::Create_PointResize(AVSValue args, void*, IScriptEnvironment* env)
2522 {
2523 4 auto f = PointFilter();
2524
2/4
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 29 not taken.
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 29 not taken.
4 const int force = args[7].AsInt(0);
2525
2526
2/4
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 29 not taken.
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 29 not taken.
4 bool preserve_center = args[8].AsBool(true); // [keep_center] default Avisynth
2527
2/4
✓ Branch 6 → 7 taken 4 times.
✗ Branch 6 → 29 not taken.
✓ Branch 7 → 8 taken 4 times.
✗ Branch 7 → 29 not taken.
4 const char* placement_name = args[9].AsString("auto"); // [placement]s
2528 4 const int forced_chroma_placement = -1; // no force, used internally
2529
2530
9/18
✓ Branch 8 → 9 taken 4 times.
✗ Branch 8 → 28 not taken.
✓ Branch 9 → 10 taken 4 times.
✗ Branch 9 → 28 not taken.
✓ Branch 10 → 11 taken 4 times.
✗ Branch 10 → 28 not taken.
✓ Branch 11 → 12 taken 4 times.
✗ Branch 11 → 28 not taken.
✓ Branch 12 → 13 taken 4 times.
✗ Branch 12 → 28 not taken.
✓ Branch 13 → 14 taken 4 times.
✗ Branch 13 → 27 not taken.
✓ Branch 14 → 15 taken 4 times.
✗ Branch 14 → 27 not taken.
✓ Branch 15 → 16 taken 4 times.
✗ Branch 15 → 25 not taken.
✓ Branch 16 → 17 taken 4 times.
✗ Branch 16 → 23 not taken.
8 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2531 4 }
2532
2533
2534 AVSValue __cdecl FilteredResize::Create_BilinearResize(AVSValue args, void*, IScriptEnvironment* env)
2535 {
2536 auto f = TriangleFilter();
2537 const int force = args[7].AsInt(0);
2538
2539 bool preserve_center = args[8].AsBool(true); // [keep_center] default Avisynth
2540 const char* placement_name = args[9].AsString("auto"); // [placement]s
2541 const int forced_chroma_placement = -1; // no force, used internally
2542
2543 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2544 }
2545
2546
2547 AVSValue __cdecl FilteredResize::Create_BicubicResize(AVSValue args, void*, IScriptEnvironment* env)
2548 {
2549 auto f = MitchellNetravaliFilter(args[3].AsDblDef(1. / 3.), args[4].AsDblDef(1. / 3.));
2550 const int force = args[9].AsInt(0);
2551
2552 bool preserve_center = args[10].AsBool(true); // [keep_center] default Avisynth
2553 const char* placement_name = args[11].AsString("auto"); // [placement]s
2554 const int forced_chroma_placement = -1; // no force, used internally
2555
2556 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[5], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2557 }
2558
2559 AVSValue __cdecl FilteredResize::Create_LanczosResize(AVSValue args, void*, IScriptEnvironment* env)
2560 {
2561 auto f = LanczosFilter(args[7].AsInt(3));
2562 const int force = args[8].AsInt(0);
2563
2564 bool preserve_center = args[9].AsBool(true); // [keep_center] default Avisynth
2565 const char* placement_name = args[10].AsString("auto"); // [placement]s
2566 const int forced_chroma_placement = -1; // no force, used internally
2567
2568 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2569 }
2570
2571 AVSValue __cdecl FilteredResize::Create_Lanczos4Resize(AVSValue args, void*, IScriptEnvironment* env)
2572 {
2573 auto f = LanczosFilter(4);
2574 const int force = args[7].AsInt(0);
2575
2576 bool preserve_center = args[8].AsBool(true); // [keep_center] default Avisynth
2577 const char* placement_name = args[9].AsString("auto"); // [placement]s
2578 const int forced_chroma_placement = -1; // no force, used internally
2579
2580 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2581 }
2582
2583 AVSValue __cdecl FilteredResize::Create_BlackmanResize(AVSValue args, void*, IScriptEnvironment* env)
2584 {
2585 auto f = BlackmanFilter(args[7].AsInt(4));
2586 const int force = args[8].AsInt(0);
2587
2588 bool preserve_center = args[8].AsBool(true); // [keep_center] default Avisynth
2589 const char* placement_name = args[9].AsString("auto"); // [placement]s
2590 const int forced_chroma_placement = -1; // no force, used internally
2591
2592 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2593 }
2594
2595 AVSValue __cdecl FilteredResize::Create_Spline16Resize(AVSValue args, void*, IScriptEnvironment* env)
2596 {
2597 auto f = Spline16Filter();
2598 const int force = args[7].AsInt(0);
2599
2600 bool preserve_center = args[8].AsBool(true); // [keep_center] default Avisynth
2601 const char* placement_name = args[9].AsString("auto"); // [placement]s
2602 const int forced_chroma_placement = -1; // no force, used internally
2603
2604 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2605 }
2606
2607 AVSValue __cdecl FilteredResize::Create_Spline36Resize(AVSValue args, void*, IScriptEnvironment* env)
2608 {
2609 auto f = Spline36Filter();
2610 const int force = args[7].AsInt(0);
2611
2612 bool preserve_center = args[8].AsBool(true); // [keep_center] default Avisynth
2613 const char* placement_name = args[9].AsString("auto"); // [placement]s
2614 const int forced_chroma_placement = -1; // no force, used internally
2615
2616 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2617 }
2618
2619 AVSValue __cdecl FilteredResize::Create_Spline64Resize(AVSValue args, void*, IScriptEnvironment* env)
2620 {
2621 auto f = Spline64Filter();
2622 const int force = args[7].AsInt(0);
2623
2624 bool preserve_center = args[8].AsBool(true); // [keep_center] default Avisynth
2625 const char* placement_name = args[9].AsString("auto"); // [placement]s
2626 const int forced_chroma_placement = -1; // no force, used internally
2627
2628 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2629 }
2630
2631 AVSValue __cdecl FilteredResize::Create_GaussianResize(AVSValue args, void*, IScriptEnvironment* env)
2632 {
2633 auto f = GaussianFilter(args[7].AsFloat(30.0f), args[8].AsFloat(2.0f), args[9].AsFloat(4.0f)); // defaults at two more places
2634 const int force = args[10].AsInt(0);
2635
2636 bool preserve_center = args[11].AsBool(true); // [keep_center] default Avisynth
2637 const char* placement_name = args[12].AsString("auto"); // [placement]s
2638 const int forced_chroma_placement = -1; // no force, used internally
2639
2640 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2641 }
2642
2643 AVSValue __cdecl FilteredResize::Create_SincResize(AVSValue args, void*, IScriptEnvironment* env)
2644 {
2645 auto f = SincFilter(args[7].AsInt(4));
2646 const int force = args[8].AsInt(0);
2647
2648 bool preserve_center = args[9].AsBool(true); // [keep_center] default Avisynth
2649 const char * placement_name = args[10].AsString("auto"); // [placement]s
2650 const int forced_chroma_placement = -1; // no force, used internally
2651
2652 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2653 }
2654
2655 // like GaussianFilter(); optional P
2656 AVSValue __cdecl FilteredResize::Create_SinPowerResize(AVSValue args, void*, IScriptEnvironment* env)
2657 {
2658 auto f = SinPowerFilter(args[7].AsFloat(2.5f));
2659 const int force = args[8].AsInt(0);
2660
2661 bool preserve_center = args[9].AsBool(true); // [keep_center] default Avisynth
2662 const char* placement_name = args[10].AsString("auto"); // [placement]s
2663 const int forced_chroma_placement = -1; // no force, used internally
2664
2665 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2666 }
2667
2668 // like SincFilter or LanczosFilter: optional Taps
2669 AVSValue __cdecl FilteredResize::Create_SincLin2Resize(AVSValue args, void*, IScriptEnvironment* env)
2670 {
2671 auto f = SincLin2Filter(args[7].AsInt(15));
2672 const int force = args[8].AsInt(0);
2673
2674 bool preserve_center = args[9].AsBool(true); // [keep_center] default Avisynth
2675 const char* placement_name = args[10].AsString("auto"); // [placement]s
2676 const int forced_chroma_placement = -1; // no force, used internally
2677
2678 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2679 }
2680
2681 // like bicubic, plus 's'upport: optional B and C and S
2682 AVSValue __cdecl FilteredResize::Create_UserDefined2Resize(AVSValue args, void*, IScriptEnvironment* env)
2683 {
2684 auto f = UserDefined2Filter(args[3].AsFloat(121.0f), args[4].AsFloat(19.0f), args[5].AsFloat(2.3f));
2685 const int force = args[10].AsInt(0);
2686
2687 bool preserve_center = args[11].AsBool(true); // [keep_center] default Avisynth
2688 const char* placement_name = args[12].AsString("auto"); // [placement]s
2689 const int forced_chroma_placement = -1; // no force, used internally
2690
2691 return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[6], force, &f, preserve_center, placement_name, forced_chroma_placement, env);
2692 }
2693
2694 #ifdef DTL2D
2695
2696 /***************************************
2697 ***** Filtered Resize - 2p ******
2698 ***************************************/
2699
2700 FilteredResize_2p::FilteredResize_2p(PClip _child,
2701 double subrange_left, double subrange_width, int target_width,
2702 double subrange_top, double subrange_height, int target_height,
2703 ResamplingFunction* func, bool preserve_center, int chroma_placement, IScriptEnvironment* env)
2704 : GenericVideoFilter(_child),
2705 resampling_program_luma_h(0), resampling_program_chroma_h(0),
2706 resampling_program_luma_v(0), resampling_program_chroma_v(0)
2707 {
2708 if (target_height <= 0)
2709 env->ThrowError("Resize: Height must be greater than 0.");
2710
2711 if (target_width <= 0)
2712 env->ThrowError("Resize: Width must be greater than 0.");
2713
2714 // set class globals
2715 src_width = vi.width;
2716 src_height = vi.height;
2717 dst_width = target_width;
2718 dst_height = target_height;
2719
2720 pixelsize = vi.ComponentSize(); // AVS16
2721 bits_per_pixel = vi.BitsPerComponent();
2722 grey = vi.IsY();
2723 bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
2724
2725 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
2726 const int mask = (1 << vi.GetPlaneHeightSubsampling(PLANAR_U)) - 1;
2727
2728 if (target_height & mask)
2729 env->ThrowError("Resize: Planar destination height must be a multiple of %d.", mask + 1);
2730 }
2731
2732 if (vi.IsRGB() && !isRGBPfamily)
2733 subrange_top = vi.height - subrange_top - subrange_height; // packed RGB upside down
2734
2735 #ifdef INTEL_INTRINSICS
2736 int cpu = env->GetCPUFlags();
2737 #else
2738 int cpu = 0;
2739 #endif
2740
2741 double center_pos_v_luma;
2742 double center_pos_v_chroma;
2743 GetCenterShiftForResizers(center_pos_v_luma, center_pos_v_chroma, preserve_center, chroma_placement, vi, false /* for vertical */);
2744
2745 double center_pos_h_luma;
2746 double center_pos_h_chroma;
2747 GetCenterShiftForResizers(center_pos_h_luma, center_pos_h_chroma, preserve_center, chroma_placement, vi, true /* for horizontal */);
2748 // 3.7.4- parameter, old Avisynth behavior: 0.5, 0.5
2749
2750 // Create resampling program and pitch table for H
2751 resampling_program_luma_h = func->GetResamplingProgram(vi.width, subrange_left, subrange_width, target_width, bits_per_pixel,
2752 center_pos_h_luma, center_pos_h_luma, // for resizing it's the same for source and dest
2753 env);
2754 resampler_luma_h = GetResamplerH(cpu, pixelsize, bits_per_pixel, resampling_program_luma_h, env);
2755
2756 // Create resampling program and pitch table for V
2757 resampling_program_luma_v = func->GetResamplingProgram(vi.height, subrange_top, subrange_height, target_height, bits_per_pixel,
2758 center_pos_v_luma, center_pos_v_luma, // for resizing it's the same for source and dest
2759 env);
2760 resampler_luma_v = GetResamplerV(cpu, pixelsize, bits_per_pixel, resampling_program_luma_v, env);
2761
2762
2763 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
2764 const int shift = vi.GetPlaneHeightSubsampling(PLANAR_U);
2765 const int div = 1 << shift;
2766
2767 resampling_program_chroma_v = func->GetResamplingProgram(
2768 vi.height >> shift,
2769 subrange_top / div,
2770 subrange_height / div,
2771 target_height >> shift,
2772 bits_per_pixel,
2773 center_pos_v_chroma, center_pos_v_chroma, // for resizing it's the same for source and dest
2774 env);
2775
2776 resampler_chroma_v = GetResamplerV(cpu, pixelsize, bits_per_pixel, resampling_program_chroma_v, env);
2777 }
2778
2779 if (vi.IsPlanar() && !grey && !isRGBPfamily) {
2780 const int shift = vi.GetPlaneWidthSubsampling(PLANAR_U);
2781 const int div = 1 << shift;
2782
2783 resampling_program_chroma_h = func->GetResamplingProgram(
2784 vi.width >> shift,
2785 subrange_left / div,
2786 subrange_width / div,
2787 target_width >> shift,
2788 bits_per_pixel,
2789 center_pos_h_chroma, center_pos_h_chroma, // horizontal
2790 env);
2791
2792 resampler_chroma_h = GetResamplerH(cpu, pixelsize, bits_per_pixel, resampling_program_chroma_h, env);
2793 }
2794
2795 // Change target video info size
2796 vi.height = target_height;
2797 vi.width = target_width;
2798 }
2799
2800 #if 0 // expected worse in performance - left for performance tests
2801 PVideoFrame __stdcall FilteredResize_2p::GetFrame(int n, IScriptEnvironment* env) // use env->Allocate() to get temp buf from other allocated memory - it is NOT returned to the memory pool for the NewVideoFrameP for the downstream filter to write to ?
2802 {
2803 PVideoFrame src = child->GetFrame(n, env);
2804 PVideoFrame dst = env->NewVideoFrameP(vi, &src);
2805 int src_pitch = src->GetPitch();
2806 int dst_pitch = dst->GetPitch();
2807 const BYTE* srcp = src->GetReadPtr();
2808 BYTE* dstp = dst->GetWritePtr(); // for first (largest ?) plane or for single ?
2809
2810 bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
2811
2812 BYTE* temp_1 = static_cast<BYTE*>(env->Allocate(dst_pitch * dst_height, FRAME_ALIGN, AVS_POOLED_ALLOC));
2813 if (!temp_1 ) {
2814 env->Free(temp_1);
2815 env->ThrowError("Could not reserve temp memory in a resampler_2p.");
2816 }
2817
2818 // Do resizing, single plane by plane
2819 resampler_luma_h(temp_1, srcp, dst_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2820 int work_height = vi.IsPlanar() ? vi.width : vi.BytesFromPixels(vi.width) / pixelsize; // packed RGB: or vi.width * vi.NumComponent()
2821 resampler_luma_v(dstp, temp_1, dst_pitch, dst_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2822
2823 if (isRGBPfamily)
2824 {
2825 src_pitch = src->GetPitch(PLANAR_B);
2826 dst_pitch = dst->GetPitch(PLANAR_B);
2827 srcp = src->GetReadPtr(PLANAR_B);
2828 dstp = dst->GetWritePtr(PLANAR_B);
2829
2830 resampler_luma_h(temp_1, srcp, dst_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2831 int work_height = vi.IsPlanar() ? vi.width : vi.BytesFromPixels(vi.width) / pixelsize; // packed RGB: or vi.width * vi.NumComponent()
2832 resampler_luma_v(dstp, temp_1, dst_pitch, dst_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2833
2834 src_pitch = src->GetPitch(PLANAR_R);
2835 dst_pitch = dst->GetPitch(PLANAR_R);
2836 srcp = src->GetReadPtr(PLANAR_R);
2837 dstp = dst->GetWritePtr(PLANAR_R);
2838
2839 resampler_luma_h(temp_1, srcp, dst_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2840 resampler_luma_v(dstp, temp_1, dst_pitch, dst_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2841
2842 }
2843 else if (!grey && vi.IsPlanar()) {
2844 int width = vi.width >> vi.GetPlaneWidthSubsampling(PLANAR_U);
2845 int height = vi.height >> vi.GetPlaneHeightSubsampling(PLANAR_U);
2846
2847 // Plane U resizing
2848 src_pitch = src->GetPitch(PLANAR_U);
2849 dst_pitch = dst->GetPitch(PLANAR_U);
2850 srcp = src->GetReadPtr(PLANAR_U);
2851 dstp = dst->GetWritePtr(PLANAR_U);
2852
2853 resampler_chroma_h(temp_1, srcp, dst_pitch, src_pitch, resampling_program_chroma_h, width, src_height >> vi.GetPlaneHeightSubsampling(PLANAR_U), bits_per_pixel);
2854 resampler_chroma_v(dstp, temp_1, dst_pitch, dst_pitch, resampling_program_chroma_v, width, height, bits_per_pixel);
2855
2856 // Plane V resizing
2857 src_pitch = src->GetPitch(PLANAR_V);
2858 dst_pitch = dst->GetPitch(PLANAR_V);
2859 srcp = src->GetReadPtr(PLANAR_V);
2860 dstp = dst->GetWritePtr(PLANAR_V);
2861
2862 resampler_chroma_h(temp_1, srcp, dst_pitch, src_pitch, resampling_program_chroma_h, width, src_height >> vi.GetPlaneHeightSubsampling(PLANAR_U), bits_per_pixel);
2863 resampler_chroma_v(dstp, temp_1, dst_pitch, dst_pitch, resampling_program_chroma_v, width, height, bits_per_pixel);
2864
2865 }
2866
2867 if (vi.IsYUVA() || vi.IsPlanarRGBA()) {
2868 src_pitch = src->GetPitch(PLANAR_A);
2869 dst_pitch = dst->GetPitch(PLANAR_A);
2870 srcp = src->GetReadPtr(PLANAR_A);
2871 dstp = dst->GetWritePtr(PLANAR_A);
2872
2873 resampler_luma_h(temp_1, srcp, dst_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2874 int work_height = vi.IsPlanar() ? vi.width : vi.BytesFromPixels(vi.width) / pixelsize; // packed RGB: or vi.width * vi.NumComponent()
2875 resampler_luma_v(dstp, temp_1, dst_pitch, dst_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2876 }
2877
2878 env->Free(temp_1);
2879
2880 return dst;
2881 }
2882 #endif
2883
2884 PVideoFrame __stdcall FilteredResize_2p::GetFrame(int n, IScriptEnvironment* env) // use NewVideoFrame as temp buf to return it in the vfb pool after exit this filter
2885 {
2886 PVideoFrame src = child->GetFrame(n, env);
2887 PVideoFrame dst = env->NewVideoFrameP(vi, &src);
2888
2889 PVideoFrame tmp = env->NewVideoFrame(vi); // no need frame properties copy, use as temporal buffer only and its refcount will be zeroed at function exit with object auto-release/destructor (PVideoFrame::~PVideoFrame() )
2890 /*
2891 Here we need to ask ScriptEnvironment to look for output format of downstream filter ? So it is not trans-in-place filter we can request frame buffer larger and left
2892 it unused after exiting this function. Only in this case there is a big probability the env->NewVideoFrameP(vi, &src); for downstream filter call will return this same virtual address buffer
2893 to the downstream filter and it can be (at least partially) overwritten saving from useless downloading from CPU cache. It is new TODO idea for modification of ScriptEnvironment vfb memory management.
2894 After this will be implemented - we can use such method of requesting temp buffer (frame) to use in 2pass resize.
2895 If this is last filter in a chain - simply request lowest possible sized frame.
2896
2897 Update 30.05.2025: The expected transfer of tmp buf address to downstream fiter dst frame sometime happens - but how frequently it happens in real scripts running - need to be discovered.
2898
2899 As env->Allocate/Free buffers are definitely worse (only good if downstream filter will request same temp buf for write) - this temp method expected to be faster (as first expectations).
2900 */
2901
2902 int src_pitch = src->GetPitch();
2903 int dst_pitch = dst->GetPitch();
2904 const BYTE* srcp = src->GetReadPtr();
2905 BYTE* dstp = dst->GetWritePtr(); // for first (largest ?) plane or for single ?
2906
2907 bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
2908
2909 const BYTE* tmp_srcp = tmp->GetReadPtr();
2910 BYTE* tmp_dstp = tmp->GetWritePtr(); // for first (largest ?) plane or for single ?
2911 int tmp_pitch = tmp->GetPitch();
2912
2913 // Do resizing, single plane by plane
2914 resampler_luma_h(tmp_dstp, srcp, tmp_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2915 int work_height = vi.IsPlanar() ? vi.width : vi.BytesFromPixels(vi.width) / pixelsize; // packed RGB: or vi.width * vi.NumComponent()
2916 resampler_luma_v(dstp, tmp_srcp, dst_pitch, tmp_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2917
2918
2919 if (isRGBPfamily)
2920 {
2921 src_pitch = src->GetPitch(PLANAR_B);
2922 dst_pitch = dst->GetPitch(PLANAR_B);
2923 srcp = src->GetReadPtr(PLANAR_B);
2924 dstp = dst->GetWritePtr(PLANAR_B);
2925
2926 resampler_luma_h(tmp_dstp, srcp, tmp_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2927 int work_height = vi.IsPlanar() ? vi.width : vi.BytesFromPixels(vi.width) / pixelsize; // packed RGB: or vi.width * vi.NumComponent()
2928 resampler_luma_v(dstp, tmp_srcp, dst_pitch, tmp_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2929
2930 src_pitch = src->GetPitch(PLANAR_R);
2931 dst_pitch = dst->GetPitch(PLANAR_R);
2932 srcp = src->GetReadPtr(PLANAR_R);
2933 dstp = dst->GetWritePtr(PLANAR_R);
2934
2935 resampler_luma_h(tmp_dstp, srcp, tmp_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2936 resampler_luma_v(dstp, tmp_srcp, dst_pitch, tmp_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2937
2938 }
2939 else if (!grey && vi.IsPlanar()) {
2940 int width = vi.width >> vi.GetPlaneWidthSubsampling(PLANAR_U);
2941 int height = vi.height >> vi.GetPlaneHeightSubsampling(PLANAR_U);
2942
2943 // Plane U resizing
2944 src_pitch = src->GetPitch(PLANAR_U);
2945 dst_pitch = dst->GetPitch(PLANAR_U);
2946 srcp = src->GetReadPtr(PLANAR_U);
2947 dstp = dst->GetWritePtr(PLANAR_U);
2948
2949 resampler_chroma_h(tmp_dstp, srcp, tmp_pitch, src_pitch, resampling_program_chroma_h, width, src_height >> vi.GetPlaneHeightSubsampling(PLANAR_U), bits_per_pixel);
2950 resampler_chroma_v(dstp, tmp_dstp, dst_pitch, tmp_pitch, resampling_program_chroma_v, width, height, bits_per_pixel);
2951
2952 // Plane V resizing
2953 src_pitch = src->GetPitch(PLANAR_V);
2954 dst_pitch = dst->GetPitch(PLANAR_V);
2955 srcp = src->GetReadPtr(PLANAR_V);
2956 dstp = dst->GetWritePtr(PLANAR_V);
2957
2958 resampler_chroma_h(tmp_dstp, srcp, tmp_pitch, src_pitch, resampling_program_chroma_h, width, src_height >> vi.GetPlaneHeightSubsampling(PLANAR_V), bits_per_pixel);
2959 resampler_chroma_v(dstp, tmp_dstp, dst_pitch, tmp_pitch, resampling_program_chroma_v, width, height, bits_per_pixel);
2960
2961 }
2962
2963 if (vi.IsYUVA() || vi.IsPlanarRGBA()) {
2964 src_pitch = src->GetPitch(PLANAR_A);
2965 dst_pitch = dst->GetPitch(PLANAR_A);
2966 srcp = src->GetReadPtr(PLANAR_A);
2967 dstp = dst->GetWritePtr(PLANAR_A);
2968
2969 resampler_luma_h(tmp_dstp, srcp, tmp_pitch, src_pitch, resampling_program_luma_h, dst_width, src_height, bits_per_pixel);
2970 int work_height = vi.IsPlanar() ? vi.width : vi.BytesFromPixels(vi.width) / pixelsize; // packed RGB: or vi.width * vi.NumComponent()
2971 resampler_luma_v(dstp, tmp_dstp, dst_pitch, tmp_pitch, resampling_program_luma_v, work_height, vi.height, bits_per_pixel);
2972 }
2973
2974 return dst;
2975 }
2976
2977
2978 ResamplerV FilteredResize_2p::GetResamplerV(int CPU, int pixelsize, int bits_per_pixel, ResamplingProgram* program, IScriptEnvironment* env) // may be somehow call same method from FilteredResizeV class ?
2979 {
2980
2981 resize_prepare_coeffs(program, env, 8);
2982 // for SIMD friendliness and more: consolidate the kernel_size vs filter_size at the end.
2983 // See comments at FilteredResizeH::GetResampler
2984
2985 if (program->filter_size == 1) {
2986 // Fast pointresize
2987 switch (pixelsize) // AVS16
2988 {
2989 case 1: return resize_v_planar_pointresize<uint8_t>;
2990 case 2: return resize_v_planar_pointresize<uint16_t>;
2991 default: // case 4:
2992 return resize_v_planar_pointresize<float>;
2993 }
2994 }
2995 else {
2996 // Other resizers
2997 if (pixelsize == 1)
2998 {
2999 #ifdef INTEL_INTRINSICS
3000 #ifdef INTEL_INTRINSICS_AVX512
3001 if (CPU & CPUF_AVX512F)
3002 return resize_v_avx512_planar_uint8_t_w_sr;
3003 #endif
3004 if (CPU & CPUF_AVX2)
3005 return resize_v_avx2_planar_uint8_t;
3006 if (CPU & CPUF_SSE2)
3007 return resize_v_sse2_planar;
3008 #ifdef X86_32
3009 if (CPU & CPUF_MMX)
3010 return resize_v_mmx_planar;
3011 #endif
3012 #endif
3013 // C version
3014 return resize_v_c_planar_uint8_16_t_auto_vectorized<uint8_t, true>;
3015 }
3016 else if (pixelsize == 2)
3017 {
3018 #ifdef INTEL_INTRINSICS
3019 #ifdef INTEL_INTRINSICS_AVX512
3020 if (CPU & CPUF_AVX512F)
3021 if (bits_per_pixel < 16)
3022 return resize_v_avx512_planar_uint16_t_w_sr<true>;
3023 else
3024 return resize_v_avx512_planar_uint16_t_w_sr<false>;
3025 #endif
3026 if (CPU & CPUF_AVX2) {
3027 if (bits_per_pixel < 16)
3028 return resize_v_avx2_planar_uint16_t<true>;
3029 else
3030 return resize_v_avx2_planar_uint16_t<false>;
3031 }
3032 if (CPU & CPUF_SSE2) {
3033 if (bits_per_pixel < 16)
3034 return resize_v_sse2_planar_uint16_t<true>;
3035 else
3036 return resize_v_sse2_planar_uint16_t<false>;
3037 }
3038 #endif
3039 // C version
3040 if (bits_per_pixel == 16)
3041 return resize_v_c_planar_uint8_16_t_auto_vectorized<uint16_t, false>;
3042 else
3043 return resize_v_c_planar_uint8_16_t_auto_vectorized<uint16_t, true>;
3044 }
3045 else // pixelsize== 4
3046 {
3047 #ifdef INTEL_INTRINSICS
3048 #ifdef INTEL_INTRINSICS_AVX512
3049 if (CPU & CPUF_AVX512F) {
3050 // return resize_v_avx512_planar_float;
3051 return resize_v_avx512_planar_float_w_sr;
3052 }
3053 #endif
3054 if (CPU & CPUF_AVX2) {
3055 return resize_v_avx2_planar_float;
3056 }
3057 if (CPU & CPUF_SSE2) {
3058 return resize_v_sse2_planar_float;
3059 }
3060 #endif
3061 return resize_v_c_planar_float_auto_vectorized;
3062 }
3063 }
3064 }
3065
3066 ResamplerH FilteredResize_2p::GetResamplerH(int CPU, int pixelsize, int bits_per_pixel, ResamplingProgram* program, IScriptEnvironment* env) // may be somehow call same method from FilteredResizeH class ?
3067 {
3068 int simd_coeff_count_padding = 8;
3069
3070 // Both 8-bit and 16-bit SSSE3 and AVX2 horizontal resizers benefit from processing 16 pixels per cycle.
3071 // Floats also use 32 bytes, but since 32/sizeof(float) = 8, processing 16 pixels is unnecessary.
3072 // Even in C, the code is optimized to be vector-friendly.
3073 if (pixelsize == 1 || pixelsize == 2)
3074 simd_coeff_count_padding = 16;
3075
3076 // Not only does it prepare and pad for SIMD/vector code, but it also corrects, reorders, and equalizes coefficients
3077 // at the right and bottom ends, since we may have variable kernel sizes due to boundary conditions.
3078 resize_prepare_coeffs(program, env, simd_coeff_count_padding);
3079
3080 if (pixelsize == 1)
3081 {
3082 #ifdef INTEL_INTRINSICS
3083 if (CPU & CPUF_AVX2) {
3084 return resizer_h_avx2_generic_uint8_t;
3085 }
3086 if (CPU & CPUF_SSSE3) {
3087 return resizer_h_ssse3_generic_uint8_16<uint8_t, true>;
3088 }
3089 #endif
3090 return resizer_h_c_generic_uint8_16_vectorized<uint8_t, true>;
3091 //return resize_h_c_planar<uint8_t, 1>;
3092 }
3093 else if (pixelsize == 2) {
3094 #ifdef INTEL_INTRINSICS
3095 if (CPU & CPUF_AVX2) {
3096 if (bits_per_pixel < 16)
3097 return resizer_h_avx2_generic_uint16_t<true>;
3098 else
3099 return resizer_h_avx2_generic_uint16_t<false>;
3100 }
3101 if (CPU & CPUF_SSSE3) {
3102 if (bits_per_pixel < 16)
3103 return resizer_h_ssse3_generic_uint8_16<uint16_t, true>;
3104 else
3105 return resizer_h_ssse3_generic_uint8_16<uint16_t, false>;
3106 }
3107 #endif
3108 if (bits_per_pixel == 16)
3109 return resizer_h_c_generic_uint8_16_vectorized<uint16_t, false>;
3110 // return resize_h_c_planar<uint16_t, 0>;
3111 else
3112 return resizer_h_c_generic_uint8_16_vectorized<uint16_t, true>;
3113 // return resize_h_c_planar<uint16_t, 1>;
3114 }
3115 else { //if (pixelsize == 4)
3116 #ifdef INTEL_INTRINSICS
3117 #ifdef INTEL_INTRINSICS_AVX512
3118 if ((CPU & CPUF_AVX512F) && program->filter_size_real <= 4) {
3119 //return resize_h_planar_float_avx2_permutex_vstripe_ks4;
3120 switch (program->filter_size_real) {
3121 /* case 1: return resize_h_planar_float_avx512_transpose_vstripe_ks4<1>; break;
3122 case 2: return resize_h_planar_float_avx512_transpose_vstripe_ks4<2>; break;
3123 case 3: return resize_h_planar_float_avx512_transpose_vstripe_ks4<3>; break;
3124 case 4: return resize_h_planar_float_avx512_transpose_vstripe_ks4<0>; break;*/
3125 case 1: return resize_h_planar_float_avx512_gather_permutex_vstripe_ks4_2w<1>; break;
3126 case 2: return resize_h_planar_float_avx512_gather_permutex_vstripe_ks4_2w<2>; break;
3127 case 3: return resize_h_planar_float_avx512_gather_permutex_vstripe_ks4_2w<3>; break;
3128 case 4: return resize_h_planar_float_avx512_gather_permutex_vstripe_ks4_2w<0>; break;
3129 }
3130 }
3131 #endif
3132 if (CPU & CPUF_AVX2) {
3133 //return resize_h_planar_float_avx2_permutex_vstripe_ks4;
3134
3135 switch (program->filter_size_real) {
3136 /* case 1: return resize_h_planar_float_avx_transpose_vstripe_ks4<1>; break;
3137 case 2: return resize_h_planar_float_avx_transpose_vstripe_ks4<2>; break;
3138 case 3: return resize_h_planar_float_avx_transpose_vstripe_ks4<3>; break;
3139 case 4: return resize_h_planar_float_avx_transpose_vstripe_ks4<0>; break;*/
3140 case 1: return resize_h_planar_float_avx2_gather_permutex_vstripe_ks4<1>; break;
3141 case 2: return resize_h_planar_float_avx2_gather_permutex_vstripe_ks4<2>; break;
3142 case 3: return resize_h_planar_float_avx2_gather_permutex_vstripe_ks4<3>; break;
3143 case 4: return resize_h_planar_float_avx2_gather_permutex_vstripe_ks4<0>; break;
3144 default: return resizer_h_avx2_generic_float;
3145 }
3146
3147 }
3148 if (CPU & CPUF_SSSE3) {
3149 // return resizer_h_ssse3_generic_float;
3150 switch (program->filter_size_real) {
3151 case 1: return resize_h_planar_float_sse_transpose_vstripe_ks4<1>; break;
3152 case 2: return resize_h_planar_float_sse_transpose_vstripe_ks4<2>; break;
3153 case 3: return resize_h_planar_float_sse_transpose_vstripe_ks4<3>; break;
3154 case 4: return resize_h_planar_float_sse_transpose_vstripe_ks4<0>; break;
3155 default: return resizer_h_ssse3_generic_float;
3156 }
3157 }
3158 #endif
3159 return resize_h_c_planar<float, 0>;
3160 }
3161 }
3162
3163
3164 FilteredResize_2p::~FilteredResize_2p(void)
3165 {
3166 if (resampling_program_luma_h) { delete resampling_program_luma_h; }
3167 if (resampling_program_chroma_h) { delete resampling_program_chroma_h; }
3168
3169 if (resampling_program_luma_v) { delete resampling_program_luma_v; }
3170 if (resampling_program_chroma_v) { delete resampling_program_chroma_v; }
3171
3172
3173 }
3174 #endif
3175