blob: 536645c793aa07f6db22b093d16a82c060dc7dd3 [file]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// Custom-CSP critical functions
//
// Author: Skal (pascal.massimino@gmail.com)
#include <cassert>
#include <cstdint>
#include "src/dsp/dsp.h"
#include "src/dsp/math.h"
#include "src/wp2/format_constants.h"
namespace {
constexpr uint32_t kMtxShift = 12u;
//------------------------------------------------------------------------------
// Converts a row of 'width' pixels from the 'y,u,v' planes into the
// 'dst0,dst1,dst2' planes using an 'offset' then a 'mtx' multiplication.
void YuvToCustom_C(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t offset[3], const int16_t mtx[9], int16_t* dst0,
int16_t* dst1, int16_t* dst2, uint32_t width) {
for (uint32_t x = 0; x < width; ++x) {
// TODO(yguyon): No-overflow guaranteed only if custom ccsp is kYuvMaxPrec
int32_t ccsp[3];
WP2::Multiply(y[x] + offset[0], u[x] + offset[1], v[x] + offset[2], mtx,
&ccsp[0], &ccsp[1], &ccsp[2]);
dst0[x] = (int16_t)WP2::RightShiftRound(ccsp[0], kMtxShift);
dst1[x] = (int16_t)WP2::RightShiftRound(ccsp[1], kMtxShift);
dst2[x] = (int16_t)WP2::RightShiftRound(ccsp[2], kMtxShift);
}
}
//------------------------------------------------------------------------------
// Converts the 0-centered 'y,u,v,a' values into the alpha-premultiplied 'argb'
// channels using the matrix 'im' and the offset 'avg'.
// 'kNumRgbBits' can be either 8 or 10. Alpha is always 8-bit.
template <int kNumRgbBits, typename TypeOut>
void AyuvToArgb(int16_t y, int16_t u, int16_t v, int32_t a,
const int16_t avg[3], const int16_t im[9],
TypeOut* const argb) {
static_assert(sizeof(TypeOut) * 8 >= kNumRgbBits, "TypeOut is too narrow");
a = WP2::Clamp<int32_t>(a, 0, WP2::kAlphaMax);
argb[0] = a;
if (a == 0) {
argb[1] = argb[2] = argb[3] = 0; // Alpha-premultiplied so 0 everywhere.
} else {
// For 10b, the maximum value is 1023 for RGB but 255 for A. Shifting alpha
// by 2 would limit to 1020, hence the dithering to keep the full range.
if (kNumRgbBits == 10) a = (a << 2) | (a >> 6);
int32_t rgb[3];
WP2::Multiply<int32_t>(y, u, v, im, &rgb[0], &rgb[1], &rgb[2]);
for (uint32_t i : {0, 1, 2}) {
rgb[i] = WP2::RightShiftRound(rgb[i], kMtxShift - (kNumRgbBits - 8));
argb[1 + i] = WP2::Clamp<int32_t>(rgb[i] + avg[i], 0, a);
}
}
}
// {Y, U, V, alpha} planes to {alpha, premultiplied R,G,B} channels
void AyuvToArgb32_C(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t* a, const int16_t avg[3],
const int16_t mtx[9], void* argb, uint32_t width) {
uint8_t* const argb32 = (uint8_t*)argb;
for (uint32_t x = 0; x < width; ++x) {
AyuvToArgb</*kNumRgbBits=*/8>(y[x], u[x], v[x], a[x], avg, mtx,
argb32 + 4 * x);
}
}
void AyuvToArgb38_C(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t* a, const int16_t avg[3],
const int16_t mtx[9], void* argb, uint32_t width) {
uint16_t* const argb38 = (uint16_t*)argb;
for (uint32_t x = 0; x < width; ++x) {
AyuvToArgb</*kNumRgbBits=*/10>(y[x], u[x], v[x], a[x], avg, mtx,
argb38 + 4 * x);
}
}
// {Y, U, V, alpha} planes to {alpha, unmultiplied R,G,B} channels
void AyuvToARGB32_C(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t* a, const int16_t avg[3],
const int16_t mtx[9], void* argb, uint32_t width) {
uint8_t* const argb32 = (uint8_t*)argb;
for (uint32_t x = 0; x < width; ++x) {
// Convert.
AyuvToArgb</*kNumRgbBits=*/8>(y[x], u[x], v[x], a[x], avg, mtx,
argb32 + 4 * x);
// Now demultiply.
if (argb32[4 * x + 0] > 0 && argb32[4 * x + 0] < WP2::kAlphaMax) {
const uint32_t m = WP2::kAlphaDiv[argb32[4 * x + 0]];
for (int i : {1, 2, 3}) {
argb32[4 * x + i] = WP2::DivByAlphaDiv(argb32[4 * x + i], m);
}
}
}
}
// {Y, U, V, alpha} planes to {alpha set to 255, unmultiplied R,G,B} channels
void AyuvToXRGB32_C(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t* a, const int16_t avg[3],
const int16_t mtx[9], void* argb, uint32_t width) {
uint8_t* const argb32 = (uint8_t*)argb;
AyuvToARGB32_C(y, u, v, a, avg, mtx, argb32, width);
for (uint32_t x = 0; x < width; ++x) {
argb32[4 * x] = WP2::kAlphaMax;
}
}
// {Y, U, V} planes to {alpha set to 255, R,G,B} channels
void YuvToArgb32_C(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t avg[3], const int16_t mtx[9], void* argb,
uint32_t width) {
uint8_t* const argb32 = (uint8_t*)argb;
for (uint32_t x = 0; x < width; ++x) {
AyuvToArgb</*kNumRgbBits=*/8>(y[x], u[x], v[x], WP2::kAlphaMax, avg, mtx,
argb32 + 4 * x);
}
}
void YuvToArgb38_C(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t avg[3], const int16_t mtx[9], void* argb,
uint32_t width) {
uint16_t* const argb38 = (uint16_t*)argb;
for (uint32_t x = 0; x < width; ++x) {
AyuvToArgb</*kNumRgbBits=*/10>(y[x], u[x], v[x], WP2::kAlphaMax, avg, mtx,
argb38 + 4 * x);
}
}
//------------------------------------------------------------------------------
// SSE4.1 implementation
#if defined(WP2_USE_SSE)
//------------------------------------------------------------------------------
// some useful macros used extensively below
// load the matrix elements mtx[] into variables mtx00, ... mtx21
#define MAKE32b(A, B) ((uint32_t)((A) & 0xffff) | ((uint32_t)(B) << 16))
#define LOAD_MTX_ELEMENTS(MTX) \
constexpr uint32_t kRnd = 1u << kMtxShift >> 1; \
const __m128i mtx00 = _mm_set1_epi32(MAKE32b(MTX[0], MTX[1])); \
const __m128i mtx01 = _mm_set1_epi32(MAKE32b(MTX[2], kRnd)); \
const __m128i mtx10 = _mm_set1_epi32(MAKE32b(MTX[3], MTX[4])); \
const __m128i mtx11 = _mm_set1_epi32(MAKE32b(MTX[5], kRnd)); \
const __m128i mtx20 = _mm_set1_epi32(MAKE32b(MTX[6], MTX[7])); \
const __m128i mtx21 = _mm_set1_epi32(MAKE32b(MTX[8], kRnd)); \
const __m128i kCst = _mm_set1_epi16(1)
// load the y[x],u[x],v[x] samples in variables Y0, U0, V0
#define LOAD_YUV(Y0, U0, V0) \
const __m128i Y0 = _mm_loadu_si128((const __m128i*)(y + x)); \
const __m128i U0 = _mm_loadu_si128((const __m128i*)(u + x)); \
const __m128i V0 = _mm_loadu_si128((const __m128i*)(v + x))
// unpack Y/U/V to variables 16b-pairs Y|U and V|K
#define UNPACK_YUV(Y, U, V) \
const __m128i YU_lo = _mm_unpacklo_epi16(Y, U); \
const __m128i YU_hi = _mm_unpackhi_epi16(Y, U); \
const __m128i VK_lo = _mm_unpacklo_epi16(V, kCst); \
const __m128i VK_hi = _mm_unpackhi_epi16(V, kCst)
#define MULT_ROW(MTX0, MTX1, OUT) \
__m128i OUT; \
do { \
const __m128i a0 = _mm_madd_epi16(YU_lo, MTX0); \
const __m128i a1 = _mm_madd_epi16(YU_hi, MTX0); \
const __m128i b0 = _mm_madd_epi16(VK_lo, MTX1); \
const __m128i b1 = _mm_madd_epi16(VK_hi, MTX1); \
const __m128i c0 = _mm_add_epi32(a0, b0); \
const __m128i c1 = _mm_add_epi32(a1, b1); \
const __m128i d0 = _mm_srai_epi32(c0, kMtxShift); \
const __m128i d1 = _mm_srai_epi32(c1, kMtxShift); \
OUT = _mm_packs_epi32(d0, d1); \
} while (false)
#define PACK_RGB(A, R, G, B, DST) \
do { \
const __m128i out_rb = _mm_packus_epi16(R, B); \
const __m128i out_ag = _mm_packus_epi16(A, G); \
const __m128i out_ar = _mm_unpacklo_epi8(out_ag, out_rb); \
const __m128i out_gb = _mm_unpackhi_epi8(out_ag, out_rb); \
const __m128i argb0 = _mm_unpacklo_epi16(out_ar, out_gb); \
const __m128i argb1 = _mm_unpackhi_epi16(out_ar, out_gb); \
_mm_storeu_si128((__m128i*)(DST + 0), argb0); \
_mm_storeu_si128((__m128i*)(DST + 16), argb1); \
} while (false)
//------------------------------------------------------------------------------
void YuvToCustom_SSE(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t offset[3], const int16_t mtx[9],
int16_t* dst0, int16_t* dst1, int16_t* dst2,
uint32_t width) {
const __m128i Off0 = _mm_set1_epi16(offset[0]);
const __m128i Off1 = _mm_set1_epi16(offset[1]);
const __m128i Off2 = _mm_set1_epi16(offset[2]);
LOAD_MTX_ELEMENTS(mtx);
uint32_t x = 0;
for (; x + 8 <= width; x += 8) {
// load source
LOAD_YUV(Y0, U0, V0);
// add offset
const __m128i Y1 = _mm_adds_epi16(Y0, Off0);
const __m128i U1 = _mm_adds_epi16(U0, Off1);
const __m128i V1 = _mm_adds_epi16(V0, Off2);
// unpack to YU + VK
UNPACK_YUV(Y1, U1, V1);
// multiply with mtx
MULT_ROW(mtx00, mtx01, out0);
MULT_ROW(mtx10, mtx11, out1);
MULT_ROW(mtx20, mtx21, out2);
// and store
_mm_storeu_si128((__m128i*)(dst0 + x), out0);
_mm_storeu_si128((__m128i*)(dst1 + x), out1);
_mm_storeu_si128((__m128i*)(dst2 + x), out2);
}
if (x < width) { // left-over
YuvToCustom_C(y + x, u + x, v + x, offset, mtx, dst0 + x, dst1 + x,
dst2 + x, width - x);
}
}
//------------------------------------------------------------------------------
// {YUV, alpha} to {alpha, premultiplied RGB}
void AyuvToArgb32_SSE(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t* a, const int16_t avg[3],
const int16_t mtx[9], void* argb, uint32_t width) {
uint8_t* const argb32 = (uint8_t*)argb;
const __m128i Avg0 = _mm_set1_epi16(avg[0]);
const __m128i Avg1 = _mm_set1_epi16(avg[1]);
const __m128i Avg2 = _mm_set1_epi16(avg[2]);
LOAD_MTX_ELEMENTS(mtx);
uint32_t x = 0;
for (; x + 8 <= width; x += 8) {
// load source
LOAD_YUV(Y0, U0, V0);
UNPACK_YUV(Y0, U0, V0);
// multiply with mtx and add average
MULT_ROW(mtx00, mtx01, R0);
MULT_ROW(mtx10, mtx11, G0);
MULT_ROW(mtx20, mtx21, B0);
const __m128i A1 = _mm_loadu_si128((const __m128i*)(a + x));
const __m128i R1 = _mm_min_epi16(A1, _mm_adds_epi16(Avg0, R0));
const __m128i G1 = _mm_min_epi16(A1, _mm_adds_epi16(Avg1, G0));
const __m128i B1 = _mm_min_epi16(A1, _mm_adds_epi16(Avg2, B0));
// repack to 8b, with alpha
PACK_RGB(A1, R1, G1, B1, argb32 + 4 * x);
}
if (x < width) { // left-over
AyuvToArgb32_C(y + x, u + x, v + x, a + x, avg, mtx, argb32 + 4 * x,
width - x);
}
}
// {YUV, alpha} to {alpha, unmultiplied RGB}
void AyuvToARGB32_SSE(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t* a, const int16_t avg[3],
const int16_t mtx[9], void* argb, uint32_t width) {
uint8_t* const argb32 = (uint8_t*)argb;
const __m128i Avg0 = _mm_set1_epi16(avg[0]);
const __m128i Avg1 = _mm_set1_epi16(avg[1]);
const __m128i Avg2 = _mm_set1_epi16(avg[2]);
LOAD_MTX_ELEMENTS(mtx);
uint32_t x = 0;
for (; x + 8 <= width; x += 8) {
LOAD_YUV(Y0, U0, V0);
UNPACK_YUV(Y0, U0, V0);
MULT_ROW(mtx00, mtx01, R0);
MULT_ROW(mtx10, mtx11, G0);
MULT_ROW(mtx20, mtx21, B0);
const __m128i A1 = _mm_loadu_si128((const __m128i*)(a + x));
const __m128i R1 = _mm_min_epi16(A1, _mm_adds_epi16(Avg0, R0));
const __m128i G1 = _mm_min_epi16(A1, _mm_adds_epi16(Avg1, G0));
const __m128i B1 = _mm_min_epi16(A1, _mm_adds_epi16(Avg2, B0));
uint8_t tmp[4 * 8];
PACK_RGB(A1, R1, G1, B1, tmp);
for (uint32_t i = 0; i < 8; ++i) {
const uint8_t A = tmp[4 * i + 0];
if (A == 255) {
argb32[4 * (x + i) + 0] = tmp[4 * i + 0];
argb32[4 * (x + i) + 1] = tmp[4 * i + 1];
argb32[4 * (x + i) + 2] = tmp[4 * i + 2];
argb32[4 * (x + i) + 3] = tmp[4 * i + 3];
} else {
const uint32_t M = WP2::kAlphaDiv[A];
argb32[4 * (x + i) + 0] = A;
argb32[4 * (x + i) + 1] = WP2::DivByAlphaDiv(tmp[4 * i + 1], M);
argb32[4 * (x + i) + 2] = WP2::DivByAlphaDiv(tmp[4 * i + 2], M);
argb32[4 * (x + i) + 3] = WP2::DivByAlphaDiv(tmp[4 * i + 3], M);
}
}
}
if (x < width) { // left-over
AyuvToARGB32_C(y + x, u + x, v + x, a + x, avg, mtx, argb32 + 4 * x,
width - x);
}
}
// {YUV, alpha} to {alpha set to 255, unmultiplied RGB}
void AyuvToXRGB32_SSE(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t* a, const int16_t avg[3],
const int16_t mtx[9], void* argb, uint32_t width) {
AyuvToARGB32_SSE(y, u, v, a, avg, mtx, argb, width);
uint8_t* const argb32 = (uint8_t*)argb;
for (uint32_t x = 0; x < width; ++x) {
argb32[4 * x] = WP2::kAlphaMax;
}
}
// {YUV} to {alpha set to 255, RGB}
void YuvToArgb32_SSE(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t avg[3], const int16_t mtx[9], void* argb,
uint32_t width) {
uint8_t* const argb32 = (uint8_t*)argb;
const __m128i Avg0 = _mm_set1_epi16(avg[0]);
const __m128i Avg1 = _mm_set1_epi16(avg[1]);
const __m128i Avg2 = _mm_set1_epi16(avg[2]);
LOAD_MTX_ELEMENTS(mtx);
const __m128i alpha = _mm_set1_epi16(255u);
uint32_t x = 0;
for (; x + 8 <= width; x += 8) {
// load source
LOAD_YUV(Y0, U0, V0);
UNPACK_YUV(Y0, U0, V0);
// multiply with mtx and add average
MULT_ROW(mtx00, mtx01, R0);
MULT_ROW(mtx10, mtx11, G0);
MULT_ROW(mtx20, mtx21, B0);
const __m128i R1 = _mm_adds_epi16(Avg0, R0);
const __m128i G1 = _mm_adds_epi16(Avg1, G0);
const __m128i B1 = _mm_adds_epi16(Avg2, B0);
// repack to 8b, with alpha
PACK_RGB(alpha, R1, G1, B1, argb32 + 4 * x);
}
if (x < width) { // left-over
YuvToArgb32_C(y + x, u + x, v + x, avg, mtx, argb32 + 4 * x, width - x);
}
}
//------------------------------------------------------------------------------
#undef LOAD_MTX_ELEMENTS
#undef LOAD_YUV
#undef UNPACK_YUV
#undef MULT_ROW
#undef PACK_RGB
WP2_TSAN_IGNORE_FUNCTION void WP2CSPConverterInitSSE() {
WP2YuvToCustom = YuvToCustom_SSE;
WP2AyuvToArgb32 = AyuvToArgb32_SSE;
WP2AyuvToARGB32 = AyuvToARGB32_SSE;
WP2AyuvToXRGB32 = AyuvToXRGB32_SSE;
WP2YuvToArgb32 = YuvToArgb32_SSE;
}
#endif // WP2_USE_SSE
//------------------------------------------------------------------------------
// NEON implementation (TODO)
//------------------------------------------------------------------------------
} // namespace
void (*WP2YuvToCustom)(const int16_t* y, const int16_t* u, const int16_t* v,
const int16_t offset[3], const int16_t mtx[9],
int16_t* dst0, int16_t* dst1, int16_t* dst2,
uint32_t width) = nullptr;
WP2AyuvToArgbFunc WP2AyuvToArgb32;
WP2AyuvToArgbFunc WP2AyuvToARGB32;
WP2AyuvToArgbFunc WP2AyuvToXRGB32;
WP2YuvToArgbFunc WP2YuvToArgb32;
WP2AyuvToArgbFunc WP2AyuvToArgb38;
WP2YuvToArgbFunc WP2YuvToArgb38;
static volatile WP2CPUInfo csp_converter_last_cpuinfo_used =
(WP2CPUInfo)&csp_converter_last_cpuinfo_used;
WP2_TSAN_IGNORE_FUNCTION void WP2CSPConverterInit() {
if (csp_converter_last_cpuinfo_used == WP2GetCPUInfo) return;
WP2YuvToCustom = YuvToCustom_C;
WP2AyuvToArgb32 = AyuvToArgb32_C;
WP2AyuvToARGB32 = AyuvToARGB32_C;
WP2AyuvToXRGB32 = AyuvToXRGB32_C;
WP2YuvToArgb32 = YuvToArgb32_C;
WP2AyuvToArgb38 = AyuvToArgb38_C;
WP2YuvToArgb38 = YuvToArgb38_C;
if (WP2GetCPUInfo != nullptr) {
#if defined(WP2_USE_SSE)
if (WP2GetCPUInfo(kSSE)) WP2CSPConverterInitSSE();
#endif
}
csp_converter_last_cpuinfo_used = WP2GetCPUInfo;
}
//------------------------------------------------------------------------------