/* * i965_vpp_avs.c - Adaptive Video Scaler (AVS) block * * Copyright (C) 2014 Intel Corporation * Author: Gwenole Beauchesne * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "sysdeps.h" #include #include #include "i965_vpp_avs.h" typedef void (*AVSGenCoeffsFunc)(float *coeffs, int num_coeffs, int phase, int num_phases, float f); /* Initializes all coefficients to zero */ static void avs_init_coeffs(float *coeffs, int num_coeffs) { #if defined(__STDC_IEC_559__) && (__STDC_IEC_559__ > 0) memset(coeffs, 0, num_coeffs * sizeof(*coeffs)); #else int i; for (i = 0; i < num_coeffs; i++) coeffs[i] = 0.0f; #endif } /* Computes the sinc(x) function */ static float avs_sinc(float x) { if (x == 0.0f) return 1.0f; return sin(x * M_PI) / (x * M_PI); } /* Convolution kernel for linear interpolation */ static float avs_kernel_linear(float x) { const float abs_x = fabsf(x); return abs_x < 1.0f ? 1 - abs_x : 0.0f; } /* Convolution kernel for Lanczos-based interpolation */ static float avs_kernel_lanczos(float x, float a) { const float abs_x = fabsf(x); return abs_x < a ? avs_sinc(x) * avs_sinc(x / a) : 0.0f; } /* Truncates floating-point value towards an epsilon factor */ static inline float avs_trunc_coeff(float x, float epsilon) { return rintf(x / epsilon) * epsilon; } /* Normalize coefficients for one sample/direction */ static void avs_normalize_coeffs_1(float *coeffs, int num_coeffs, float epsilon) { float s, sum = 0.0; int i, c, r, r1; for (i = 0; i < num_coeffs; i++) sum += coeffs[i]; if (sum < epsilon) return; s = 0.0; for (i = 0; i < num_coeffs; i++) s += (coeffs[i] = avs_trunc_coeff(coeffs[i] / sum, epsilon)); /* Distribute the remaining bits, while allocating more to the center */ c = num_coeffs / 2; c = c - (coeffs[c - 1] > coeffs[c]); r = (1.0f - s) / epsilon; r1 = r / 4; if (coeffs[c + 1] == 0.0f) coeffs[c] += r * epsilon; else { coeffs[c] += (r - 2 * r1) * epsilon; coeffs[c - 1] += r1 * epsilon; coeffs[c + 1] += r1 * epsilon; } } /* Normalize all coefficients so that their sum yields 1.0f */ static void avs_normalize_coeffs(AVSCoeffs *coeffs, const AVSConfig *config) { avs_normalize_coeffs_1(coeffs->y_k_h, config->num_luma_coeffs, config->coeff_epsilon); avs_normalize_coeffs_1(coeffs->y_k_v, config->num_luma_coeffs, config->coeff_epsilon); avs_normalize_coeffs_1(coeffs->uv_k_h, config->num_chroma_coeffs, config->coeff_epsilon); avs_normalize_coeffs_1(coeffs->uv_k_v, config->num_chroma_coeffs, config->coeff_epsilon); } /* Validate coefficients for one sample/direction */ static bool avs_validate_coeffs_1(float *coeffs, int num_coeffs, const float *min_coeffs, const float *max_coeffs) { int i; for (i = 0; i < num_coeffs; i++) { if (coeffs[i] < min_coeffs[i] || coeffs[i] > max_coeffs[i]) return false; } return true; } /* Validate coefficients wrt. the supplied range in config */ static bool avs_validate_coeffs(AVSCoeffs *coeffs, const AVSConfig *config) { const AVSCoeffs * const min_coeffs = &config->coeff_range.lower_bound; const AVSCoeffs * const max_coeffs = &config->coeff_range.upper_bound; return avs_validate_coeffs_1(coeffs->y_k_h, config->num_luma_coeffs, min_coeffs->y_k_h, max_coeffs->y_k_h) && avs_validate_coeffs_1(coeffs->y_k_v, config->num_luma_coeffs, min_coeffs->y_k_v, max_coeffs->y_k_v) && avs_validate_coeffs_1(coeffs->uv_k_h, config->num_chroma_coeffs, min_coeffs->uv_k_h, max_coeffs->uv_k_h) && avs_validate_coeffs_1(coeffs->uv_k_v, config->num_chroma_coeffs, min_coeffs->uv_k_v, max_coeffs->uv_k_v); } /* Generate coefficients for default quality (bilinear) */ static void avs_gen_coeffs_linear(float *coeffs, int num_coeffs, int phase, int num_phases, float f) { const int c = num_coeffs / 2 - 1; const float p = (float)phase / (num_phases * 2); avs_init_coeffs(coeffs, num_coeffs); coeffs[c] = avs_kernel_linear(p); coeffs[c + 1] = avs_kernel_linear(p - 1); } /* Generate coefficients for high quality (lanczos) */ static void avs_gen_coeffs_lanczos(float *coeffs, int num_coeffs, int phase, int num_phases, float f) { const int c = num_coeffs / 2 - 1; const int l = num_coeffs > 4 ? 3 : 2; const float p = (float)phase / (num_phases * 2); int i; if (f > 1.0f) f = 1.0f; for (i = 0; i < num_coeffs; i++) coeffs[i] = avs_kernel_lanczos((i - (c + p)) * f, l); } /* Generate coefficients with the supplied scaler */ static bool avs_gen_coeffs(AVSState *avs, float sx, float sy, AVSGenCoeffsFunc gen_coeffs) { const AVSConfig * const config = avs->config; int i; for (i = 0; i <= config->num_phases; i++) { AVSCoeffs * const coeffs = &avs->coeffs[i]; gen_coeffs(coeffs->y_k_h, config->num_luma_coeffs, i, config->num_phases, sx); gen_coeffs(coeffs->uv_k_h, config->num_chroma_coeffs, i, config->num_phases, sx); gen_coeffs(coeffs->y_k_v, config->num_luma_coeffs, i, config->num_phases, sy); gen_coeffs(coeffs->uv_k_v, config->num_chroma_coeffs, i, config->num_phases, sy); avs_normalize_coeffs(coeffs, config); if (!avs_validate_coeffs(coeffs, config)) return false; } return true; } /* Initializes AVS state with the supplied configuration */ void avs_init_state(AVSState *avs, const AVSConfig *config) { avs->config = config; avs->flags = 0; avs->scale_x = 0.0f; avs->scale_y = 0.0f; } /* Checks whether the AVS scaling parameters changed */ static inline bool avs_params_changed(AVSState *avs, float sx, float sy, uint32_t flags) { if (avs->flags != flags) return true; if (flags >= VA_FILTER_SCALING_HQ) { if (avs->scale_x != sx || avs->scale_y != sy) return true; } else { if (avs->scale_x == 0.0f || avs->scale_y == 0.0f) return true; } return false; } /* Updates AVS coefficients for the supplied factors and quality level */ bool avs_update_coefficients(AVSState *avs, float sx, float sy, uint32_t flags) { AVSGenCoeffsFunc gen_coeffs; flags &= VA_FILTER_SCALING_MASK; if (!avs_params_changed(avs, sx, sy, flags)) return true; switch (flags) { case VA_FILTER_SCALING_HQ: gen_coeffs = avs_gen_coeffs_lanczos; break; default: gen_coeffs = avs_gen_coeffs_linear; break; } if (!avs_gen_coeffs(avs, sx, sy, gen_coeffs)) { assert(0 && "invalid set of coefficients generated"); return false; } avs->flags = flags; avs->scale_x = sx; avs->scale_y = sy; return true; }