xref: /aosp_15_r20/external/libaom/aom_dsp/flow_estimation/x86/corner_match_sse4.c (revision 77c1e3ccc04c968bd2bc212e87364f250e820521)
1 /*
2  * Copyright (c) 2018, Alliance for Open Media. All rights reserved.
3  *
4  * This source code is subject to the terms of the BSD 2 Clause License and
5  * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6  * was not distributed with this source code in the LICENSE file, you can
7  * obtain it at www.aomedia.org/license/software. If the Alliance for Open
8  * Media Patent License 1.0 was not distributed with this source code in the
9  * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
10  */
11 
12 #include <stdlib.h>
13 #include <memory.h>
14 #include <math.h>
15 #include <assert.h>
16 
17 #include <smmintrin.h>
18 
19 #include "config/aom_dsp_rtcd.h"
20 
21 #include "aom_ports/mem.h"
22 #include "aom_dsp/flow_estimation/corner_match.h"
23 
24 DECLARE_ALIGNED(16, static const uint16_t, ones_array[8]) = { 1, 1, 1, 1,
25                                                               1, 1, 1, 1 };
26 
27 #if MATCH_SZ != 16
28 #error "Need to apply pixel mask in corner_match_sse4.c if MATCH_SZ != 16"
29 #endif
30 
31 /* Compute mean and standard deviation of pixels in a window of size
32    MATCH_SZ by MATCH_SZ centered at (x, y).
33    Store results into *mean and *one_over_stddev
34 
35    Note: The output of this function is scaled by MATCH_SZ, as in
36    *mean = MATCH_SZ * <true mean> and
37    *one_over_stddev = 1 / (MATCH_SZ * <true stddev>)
38 
39    Combined with the fact that we return 1/stddev rather than the standard
40    deviation itself, this allows us to completely avoid divisions in
41    aom_compute_correlation, which is much hotter than this function is.
42 
43    Returns true if this feature point is usable, false otherwise.
44 */
aom_compute_mean_stddev_sse4_1(const unsigned char * frame,int stride,int x,int y,double * mean,double * one_over_stddev)45 bool aom_compute_mean_stddev_sse4_1(const unsigned char *frame, int stride,
46                                     int x, int y, double *mean,
47                                     double *one_over_stddev) {
48   // 8 16-bit partial sums of pixels
49   // Each lane sums at most 2*MATCH_SZ pixels, which can have values up to 255,
50   // and is therefore at most 2*MATCH_SZ*255, which is > 2^8 but < 2^16.
51   // Thus this value is safe to store in 16 bits.
52   __m128i sum_vec = _mm_setzero_si128();
53 
54   // 8 32-bit partial sums of squares
55   __m128i sumsq_vec_l = _mm_setzero_si128();
56   __m128i sumsq_vec_r = _mm_setzero_si128();
57 
58   frame += (y - MATCH_SZ_BY2) * stride + (x - MATCH_SZ_BY2);
59 
60   for (int i = 0; i < MATCH_SZ; ++i) {
61     const __m128i v = _mm_loadu_si128((__m128i *)frame);
62     const __m128i v_l = _mm_cvtepu8_epi16(v);
63     const __m128i v_r = _mm_cvtepu8_epi16(_mm_srli_si128(v, 8));
64 
65     sum_vec = _mm_add_epi16(sum_vec, _mm_add_epi16(v_l, v_r));
66     sumsq_vec_l = _mm_add_epi32(sumsq_vec_l, _mm_madd_epi16(v_l, v_l));
67     sumsq_vec_r = _mm_add_epi32(sumsq_vec_r, _mm_madd_epi16(v_r, v_r));
68 
69     frame += stride;
70   }
71 
72   // Reduce sum_vec and sumsq_vec into single values
73   // Start by reducing each vector to 4x32-bit values, hadd() to perform four
74   // additions, then perform the last two additions in scalar code.
75   const __m128i ones = _mm_load_si128((__m128i *)ones_array);
76   const __m128i partial_sum = _mm_madd_epi16(sum_vec, ones);
77   const __m128i partial_sumsq = _mm_add_epi32(sumsq_vec_l, sumsq_vec_r);
78   const __m128i tmp = _mm_hadd_epi32(partial_sum, partial_sumsq);
79   const int sum = _mm_extract_epi32(tmp, 0) + _mm_extract_epi32(tmp, 1);
80   const int sumsq = _mm_extract_epi32(tmp, 2) + _mm_extract_epi32(tmp, 3);
81 
82   *mean = (double)sum / MATCH_SZ;
83   const double variance = sumsq - (*mean) * (*mean);
84   if (variance < MIN_FEATURE_VARIANCE) {
85     *one_over_stddev = 0.0;
86     return false;
87   }
88   *one_over_stddev = 1.0 / sqrt(variance);
89   return true;
90 }
91 
92 /* Compute corr(frame1, frame2) over a window of size MATCH_SZ by MATCH_SZ.
93    To save on computation, the mean and (1 divided by the) standard deviation
94    of the window in each frame are precomputed and passed into this function
95    as arguments.
96 */
aom_compute_correlation_sse4_1(const unsigned char * frame1,int stride1,int x1,int y1,double mean1,double one_over_stddev1,const unsigned char * frame2,int stride2,int x2,int y2,double mean2,double one_over_stddev2)97 double aom_compute_correlation_sse4_1(const unsigned char *frame1, int stride1,
98                                       int x1, int y1, double mean1,
99                                       double one_over_stddev1,
100                                       const unsigned char *frame2, int stride2,
101                                       int x2, int y2, double mean2,
102                                       double one_over_stddev2) {
103   // 8 32-bit partial sums of products
104   __m128i cross_vec_l = _mm_setzero_si128();
105   __m128i cross_vec_r = _mm_setzero_si128();
106 
107   frame1 += (y1 - MATCH_SZ_BY2) * stride1 + (x1 - MATCH_SZ_BY2);
108   frame2 += (y2 - MATCH_SZ_BY2) * stride2 + (x2 - MATCH_SZ_BY2);
109 
110   for (int i = 0; i < MATCH_SZ; ++i) {
111     const __m128i v1 = _mm_loadu_si128((__m128i *)frame1);
112     const __m128i v2 = _mm_loadu_si128((__m128i *)frame2);
113 
114     const __m128i v1_l = _mm_cvtepu8_epi16(v1);
115     const __m128i v1_r = _mm_cvtepu8_epi16(_mm_srli_si128(v1, 8));
116     const __m128i v2_l = _mm_cvtepu8_epi16(v2);
117     const __m128i v2_r = _mm_cvtepu8_epi16(_mm_srli_si128(v2, 8));
118 
119     cross_vec_l = _mm_add_epi32(cross_vec_l, _mm_madd_epi16(v1_l, v2_l));
120     cross_vec_r = _mm_add_epi32(cross_vec_r, _mm_madd_epi16(v1_r, v2_r));
121 
122     frame1 += stride1;
123     frame2 += stride2;
124   }
125 
126   // Sum cross_vec into a single value
127   const __m128i tmp = _mm_add_epi32(cross_vec_l, cross_vec_r);
128   const int cross = _mm_extract_epi32(tmp, 0) + _mm_extract_epi32(tmp, 1) +
129                     _mm_extract_epi32(tmp, 2) + _mm_extract_epi32(tmp, 3);
130 
131   // Note: In theory, the calculations here "should" be
132   //   covariance = cross / N^2 - mean1 * mean2
133   //   correlation = covariance / (stddev1 * stddev2).
134   //
135   // However, because of the scaling in aom_compute_mean_stddev, the
136   // lines below actually calculate
137   //   covariance * N^2 = cross - (mean1 * N) * (mean2 * N)
138   //   correlation = (covariance * N^2) / ((stddev1 * N) * (stddev2 * N))
139   //
140   // ie. we have removed the need for a division, and still end up with the
141   // correct unscaled correlation (ie, in the range [-1, +1])
142   const double covariance = cross - mean1 * mean2;
143   const double correlation = covariance * (one_over_stddev1 * one_over_stddev2);
144   return correlation;
145 }
146