FreeRDP
Loading...
Searching...
No Matches
prim_YUV_neon.c
1
23#include <freerdp/config.h>
24
25#include <winpr/sysinfo.h>
26#include <winpr/crt.h>
27#include <freerdp/types.h>
28#include <freerdp/primitives.h>
29
30#include "prim_internal.h"
31#include "prim_YUV.h"
32
33#if defined(NEON_INTRINSICS_ENABLED)
34#include <arm_neon.h>
35
36static primitives_t* generic = nullptr;
37
38static inline uint8x8_t neon_YUV2R_single(uint16x8_t C, int16x8_t D, int16x8_t E)
39{
40 /* R = (256 * Y + 403 * (V - 128)) >> 8 */
41 const int32x4_t Ch = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(C)));
42 const int32x4_t e403h = vmull_n_s16(vget_high_s16(E), 403);
43 const int32x4_t cehm = vaddq_s32(Ch, e403h);
44 const int32x4_t ceh = vshrq_n_s32(cehm, 8);
45
46 const int32x4_t Cl = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(C)));
47 const int32x4_t e403l = vmull_n_s16(vget_low_s16(E), 403);
48 const int32x4_t celm = vaddq_s32(Cl, e403l);
49 const int32x4_t cel = vshrq_n_s32(celm, 8);
50 const int16x8_t ce = vcombine_s16(vqmovn_s32(cel), vqmovn_s32(ceh));
51 return vqmovun_s16(ce);
52}
53
54static inline uint8x8x2_t neon_YUV2R(uint16x8x2_t C, int16x8x2_t D, int16x8x2_t E)
55{
56 uint8x8x2_t res = { { neon_YUV2R_single(C.val[0], D.val[0], E.val[0]),
57 neon_YUV2R_single(C.val[1], D.val[1], E.val[1]) } };
58 return res;
59}
60
61static inline uint8x8_t neon_YUV2G_single(uint16x8_t C, int16x8_t D, int16x8_t E)
62{
63 /* G = (256L * Y - 48 * (U - 128) - 120 * (V - 128)) >> 8 */
64 const int16x8_t d48 = vmulq_n_s16(D, 48);
65 const int16x8_t e120 = vmulq_n_s16(E, 120);
66 const int32x4_t deh = vaddl_s16(vget_high_s16(d48), vget_high_s16(e120));
67 const int32x4_t Ch = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(C)));
68 const int32x4_t cdeh32m = vsubq_s32(Ch, deh);
69 const int32x4_t cdeh32 = vshrq_n_s32(cdeh32m, 8);
70 const int16x4_t cdeh = vqmovn_s32(cdeh32);
71
72 const int32x4_t del = vaddl_s16(vget_low_s16(d48), vget_low_s16(e120));
73 const int32x4_t Cl = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(C)));
74 const int32x4_t cdel32m = vsubq_s32(Cl, del);
75 const int32x4_t cdel32 = vshrq_n_s32(cdel32m, 8);
76 const int16x4_t cdel = vqmovn_s32(cdel32);
77 const int16x8_t cde = vcombine_s16(cdel, cdeh);
78 return vqmovun_s16(cde);
79}
80
81static inline uint8x8x2_t neon_YUV2G(uint16x8x2_t C, int16x8x2_t D, int16x8x2_t E)
82{
83 uint8x8x2_t res = { { neon_YUV2G_single(C.val[0], D.val[0], E.val[0]),
84 neon_YUV2G_single(C.val[1], D.val[1], E.val[1]) } };
85 return res;
86}
87
88static inline uint8x8_t neon_YUV2B_single(uint16x8_t C, int16x8_t D, int16x8_t E)
89{
90 /* B = (256L * Y + 475 * (U - 128)) >> 8*/
91 const int32x4_t Ch = vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(C)));
92 const int32x4_t d475h = vmull_n_s16(vget_high_s16(D), 475);
93 const int32x4_t cdhm = vaddq_s32(Ch, d475h);
94 const int32x4_t cdh = vshrq_n_s32(cdhm, 8);
95
96 const int32x4_t Cl = vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(C)));
97 const int32x4_t d475l = vmull_n_s16(vget_low_s16(D), 475);
98 const int32x4_t cdlm = vaddq_s32(Cl, d475l);
99 const int32x4_t cdl = vshrq_n_s32(cdlm, 8);
100 const int16x8_t cd = vcombine_s16(vqmovn_s32(cdl), vqmovn_s32(cdh));
101 return vqmovun_s16(cd);
102}
103
104static inline uint8x8x2_t neon_YUV2B(uint16x8x2_t C, int16x8x2_t D, int16x8x2_t E)
105{
106 uint8x8x2_t res = { { neon_YUV2B_single(C.val[0], D.val[0], E.val[0]),
107 neon_YUV2B_single(C.val[1], D.val[1], E.val[1]) } };
108 return res;
109}
110
111static inline void neon_store_bgrx(BYTE* WINPR_RESTRICT pRGB, uint8x8_t r, uint8x8_t g, uint8x8_t b,
112 uint8_t rPos, uint8_t gPos, uint8_t bPos, uint8_t aPos)
113{
114 uint8x8x4_t bgrx = vld4_u8(pRGB);
115 bgrx.val[rPos] = r;
116 bgrx.val[gPos] = g;
117 bgrx.val[bPos] = b;
118 vst4_u8(pRGB, bgrx);
119}
120
121static inline void neon_YuvToRgbPixel(BYTE* pRGB, uint8x8x2_t Y, int16x8x2_t D, int16x8x2_t E,
122 const uint8_t rPos, const uint8_t gPos, const uint8_t bPos,
123 const uint8_t aPos)
124{
125 /* Y * 256 == Y << 8 */
126 const uint16x8x2_t C = { { vshlq_n_u16(vmovl_u8(Y.val[0]), 8),
127 vshlq_n_u16(vmovl_u8(Y.val[1]), 8) } };
128
129 const uint8x8x2_t r = neon_YUV2R(C, D, E);
130 const uint8x8x2_t g = neon_YUV2G(C, D, E);
131 const uint8x8x2_t b = neon_YUV2B(C, D, E);
132
133 neon_store_bgrx(pRGB, r.val[0], g.val[0], b.val[0], rPos, gPos, bPos, aPos);
134 neon_store_bgrx(pRGB + sizeof(uint8x8x4_t), r.val[1], g.val[1], b.val[1], rPos, gPos, bPos,
135 aPos);
136}
137
138static inline int16x8x2_t loadUV(const BYTE* WINPR_RESTRICT pV, size_t x)
139{
140 const uint8x8_t Vraw = vld1_u8(&pV[x / 2]);
141 const int16x8_t V = vreinterpretq_s16_u16(vmovl_u8(Vraw));
142 const int16x8_t c128 = vdupq_n_s16(128);
143 const int16x8_t E = vsubq_s16(V, c128);
144 return vzipq_s16(E, E);
145}
146
147static inline void neon_write_pixel(BYTE* pRGB, BYTE Y, BYTE U, BYTE V, const uint8_t rPos,
148 const uint8_t gPos, const uint8_t bPos, const uint8_t aPos)
149{
150 const BYTE r = YUV2R(Y, U, V);
151 const BYTE g = YUV2G(Y, U, V);
152 const BYTE b = YUV2B(Y, U, V);
153
154 pRGB[rPos] = r;
155 pRGB[gPos] = g;
156 pRGB[bPos] = b;
157}
158
159static inline void neon_YUV420ToX_DOUBLE_ROW(const BYTE* WINPR_RESTRICT pY[2],
160 const BYTE* WINPR_RESTRICT pU,
161 const BYTE* WINPR_RESTRICT pV,
162 BYTE* WINPR_RESTRICT pRGB[2], size_t width,
163 const uint8_t rPos, const uint8_t gPos,
164 const uint8_t bPos, const uint8_t aPos)
165{
166 UINT32 x = 0;
167
168 for (; x < width - width % 16; x += 16)
169 {
170 const uint8x16_t Y0raw = vld1q_u8(&pY[0][x]);
171 const uint8x8x2_t Y0 = { { vget_low_u8(Y0raw), vget_high_u8(Y0raw) } };
172 const int16x8x2_t D = loadUV(pU, x);
173 const int16x8x2_t E = loadUV(pV, x);
174 neon_YuvToRgbPixel(&pRGB[0][4ULL * x], Y0, D, E, rPos, gPos, bPos, aPos);
175
176 const uint8x16_t Y1raw = vld1q_u8(&pY[1][x]);
177 const uint8x8x2_t Y1 = { { vget_low_u8(Y1raw), vget_high_u8(Y1raw) } };
178 neon_YuvToRgbPixel(&pRGB[1][4ULL * x], Y1, D, E, rPos, gPos, bPos, aPos);
179 }
180
181 for (; x < width - width % 2; x += 2)
182 {
183 const BYTE U = pU[x / 2];
184 const BYTE V = pV[x / 2];
185
186 neon_write_pixel(&pRGB[0][4 * x], pY[0][x], U, V, rPos, gPos, bPos, aPos);
187 neon_write_pixel(&pRGB[0][4 * (1ULL + x)], pY[0][1ULL + x], U, V, rPos, gPos, bPos, aPos);
188 neon_write_pixel(&pRGB[1][4 * x], pY[1][x], U, V, rPos, gPos, bPos, aPos);
189 neon_write_pixel(&pRGB[1][4 * (1ULL + x)], pY[1][1ULL + x], U, V, rPos, gPos, bPos, aPos);
190 }
191
192 for (; x < width; x++)
193 {
194 const BYTE U = pU[x / 2];
195 const BYTE V = pV[x / 2];
196
197 neon_write_pixel(&pRGB[0][4 * x], pY[0][x], U, V, rPos, gPos, bPos, aPos);
198 neon_write_pixel(&pRGB[1][4 * x], pY[1][x], U, V, rPos, gPos, bPos, aPos);
199 }
200}
201
202static inline void neon_YUV420ToX_SINGLE_ROW(const BYTE* WINPR_RESTRICT pY,
203 const BYTE* WINPR_RESTRICT pU,
204 const BYTE* WINPR_RESTRICT pV,
205 BYTE* WINPR_RESTRICT pRGB, size_t width,
206 const uint8_t rPos, const uint8_t gPos,
207 const uint8_t bPos, const uint8_t aPos)
208{
209 UINT32 x = 0;
210
211 for (; x < width - width % 16; x += 16)
212 {
213 const uint8x16_t Y0raw = vld1q_u8(&pY[x]);
214 const uint8x8x2_t Y0 = { { vget_low_u8(Y0raw), vget_high_u8(Y0raw) } };
215 const int16x8x2_t D = loadUV(pU, x);
216 const int16x8x2_t E = loadUV(pV, x);
217 neon_YuvToRgbPixel(&pRGB[4ULL * x], Y0, D, E, rPos, gPos, bPos, aPos);
218 }
219
220 for (; x < width - width % 2; x += 2)
221 {
222 const BYTE U = pU[x / 2];
223 const BYTE V = pV[x / 2];
224
225 neon_write_pixel(&pRGB[4 * x], pY[x], U, V, rPos, gPos, bPos, aPos);
226 neon_write_pixel(&pRGB[4 * (1ULL + x)], pY[1ULL + x], U, V, rPos, gPos, bPos, aPos);
227 }
228 for (; x < width; x++)
229 {
230 const BYTE U = pU[x / 2];
231 const BYTE V = pV[x / 2];
232
233 neon_write_pixel(&pRGB[4 * x], pY[x], U, V, rPos, gPos, bPos, aPos);
234 }
235}
236
237static inline pstatus_t neon_YUV420ToX(const BYTE* WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3],
238 BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
239 const prim_size_t* WINPR_RESTRICT roi, const uint8_t rPos,
240 const uint8_t gPos, const uint8_t bPos, const uint8_t aPos)
241{
242 const UINT32 nWidth = roi->width;
243 const UINT32 nHeight = roi->height;
244
245 WINPR_ASSERT(nHeight > 0);
246 UINT32 y = 0;
247 for (; y < (nHeight - 1); y += 2)
248 {
249 const uint8_t* pY[2] = { pSrc[0] + y * srcStep[0], pSrc[0] + (1ULL + y) * srcStep[0] };
250 const uint8_t* pU = pSrc[1] + (y / 2) * srcStep[1];
251 const uint8_t* pV = pSrc[2] + (y / 2) * srcStep[2];
252 uint8_t* pRGB[2] = { pDst + y * dstStep, pDst + (1ULL + y) * dstStep };
253
254 neon_YUV420ToX_DOUBLE_ROW(pY, pU, pV, pRGB, nWidth, rPos, gPos, bPos, aPos);
255 }
256 for (; y < nHeight; y++)
257 {
258 const uint8_t* pY = pSrc[0] + y * srcStep[0];
259 const uint8_t* pU = pSrc[1] + (y / 2) * srcStep[1];
260 const uint8_t* pV = pSrc[2] + (y / 2) * srcStep[2];
261 uint8_t* pRGB = pDst + y * dstStep;
262
263 neon_YUV420ToX_SINGLE_ROW(pY, pU, pV, pRGB, nWidth, rPos, gPos, bPos, aPos);
264 }
265 return PRIMITIVES_SUCCESS;
266}
267
268static pstatus_t neon_YUV420ToRGB_8u_P3AC4R(const BYTE* WINPR_RESTRICT pSrc[3],
269 const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
270 UINT32 dstStep, UINT32 DstFormat,
271 const prim_size_t* WINPR_RESTRICT roi)
272{
273 switch (DstFormat)
274 {
275 case PIXEL_FORMAT_BGRA32:
276 case PIXEL_FORMAT_BGRX32:
277 return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0, 3);
278
279 case PIXEL_FORMAT_RGBA32:
280 case PIXEL_FORMAT_RGBX32:
281 return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2, 3);
282
283 case PIXEL_FORMAT_ARGB32:
284 case PIXEL_FORMAT_XRGB32:
285 return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3, 0);
286
287 case PIXEL_FORMAT_ABGR32:
288 case PIXEL_FORMAT_XBGR32:
289 return neon_YUV420ToX(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1, 0);
290
291 default:
292 return generic->YUV420ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
293 }
294}
295
296static inline int16x8_t loadUVreg(uint8x8_t Vraw)
297{
298 const int16x8_t V = vreinterpretq_s16_u16(vmovl_u8(Vraw));
299 const int16x8_t c128 = vdupq_n_s16(128);
300 const int16x8_t E = vsubq_s16(V, c128);
301 return E;
302}
303
304static inline int16x8x2_t loadUV444(uint8x16_t Vld)
305{
306 const uint8x8x2_t V = { { vget_low_u8(Vld), vget_high_u8(Vld) } };
307 const int16x8x2_t res = { {
308 loadUVreg(V.val[0]),
309 loadUVreg(V.val[1]),
310 } };
311 return res;
312}
313
314static inline void avgUV(BYTE U[2][2])
315{
316 const BYTE u00 = U[0][0];
317 const INT16 umul = (INT16)u00 << 2;
318 const INT16 sum = (INT16)U[0][1] + U[1][0] + U[1][1];
319 const INT16 wavg = umul - sum;
320 const BYTE val = CONDITIONAL_CLIP(wavg, u00);
321 U[0][0] = val;
322}
323
324static inline void neon_avgUV(uint8x16_t pU[2])
325{
326 /* put even and odd values into different registers.
327 * U 0/0 is in lower half */
328 const uint8x16x2_t usplit = vuzpq_u8(pU[0], pU[1]);
329 const uint8x16_t ueven = usplit.val[0];
330 const uint8x16_t uodd = usplit.val[1];
331
332 const uint8x8_t u00 = vget_low_u8(ueven);
333 const uint8x8_t u01 = vget_low_u8(uodd);
334 const uint8x8_t u10 = vget_high_u8(ueven);
335 const uint8x8_t u11 = vget_high_u8(uodd);
336
337 /* Create sum of U01 + U10 + U11 */
338 const uint16x8_t uoddsum = vaddl_u8(u01, u10);
339 const uint16x8_t usum = vaddq_u16(uoddsum, vmovl_u8(u11));
340
341 /* U00 * 4 */
342 const uint16x8_t umul = vshll_n_u8(u00, 2);
343
344 /* U00 - (U01 + U10 + U11) */
345 const int16x8_t wavg = vsubq_s16(vreinterpretq_s16_u16(umul), vreinterpretq_s16_u16(usum));
346 const uint8x8_t avg = vqmovun_s16(wavg);
347
348 /* abs(u00 - avg) */
349 const uint8x8_t absdiff = vabd_u8(avg, u00);
350
351 /* (diff < 30) ? u00 : avg */
352 const uint8x8_t mask = vclt_u8(absdiff, vdup_n_u8(30));
353
354 /* out1 = u00 & mask */
355 const uint8x8_t out1 = vand_u8(u00, mask);
356
357 /* invmask = ~mask */
358 const uint8x8_t notmask = vmvn_u8(mask);
359
360 /* out2 = avg & invmask */
361 const uint8x8_t out2 = vand_u8(avg, notmask);
362
363 /* out = out1 | out2 */
364 const uint8x8_t out = vorr_u8(out1, out2);
365
366 const uint8x8x2_t ua = vzip_u8(out, u01);
367 const uint8x16_t u = vcombine_u8(ua.val[0], ua.val[1]);
368 pU[0] = u;
369}
370
371static inline pstatus_t neon_YUV444ToX_SINGLE_ROW(const BYTE* WINPR_RESTRICT pY,
372 const BYTE* WINPR_RESTRICT pU,
373 const BYTE* WINPR_RESTRICT pV,
374 BYTE* WINPR_RESTRICT pRGB, size_t width,
375 const uint8_t rPos, const uint8_t gPos,
376 const uint8_t bPos, const uint8_t aPos)
377{
378 size_t x = 0;
379
380 for (; x < width - width % 16; x += 16)
381 {
382 uint8x16_t U = vld1q_u8(&pU[x]);
383 uint8x16_t V = vld1q_u8(&pV[x]);
384 const uint8x16_t Y0raw = vld1q_u8(&pY[x]);
385 const uint8x8x2_t Y0 = { { vget_low_u8(Y0raw), vget_high_u8(Y0raw) } };
386 const int16x8x2_t D0 = loadUV444(U);
387 const int16x8x2_t E0 = loadUV444(V);
388 neon_YuvToRgbPixel(&pRGB[4ULL * x], Y0, D0, E0, rPos, gPos, bPos, aPos);
389 }
390
391 for (; x < width; x += 2)
392 {
393 BYTE* rgb = &pRGB[x * 4];
394
395 for (size_t j = 0; j < 2; j++)
396 {
397 const BYTE y = pY[x + j];
398 const BYTE u = pU[x + j];
399 const BYTE v = pV[x + j];
400
401 neon_write_pixel(&rgb[4 * (j)], y, u, v, rPos, gPos, bPos, aPos);
402 }
403 }
404
405 return PRIMITIVES_SUCCESS;
406}
407
408static inline pstatus_t neon_YUV444ToX_DOUBLE_ROW(const BYTE* WINPR_RESTRICT pY[2],
409 const BYTE* WINPR_RESTRICT pU[2],
410 const BYTE* WINPR_RESTRICT pV[2],
411 BYTE* WINPR_RESTRICT pRGB[2], size_t width,
412 const uint8_t rPos, const uint8_t gPos,
413 const uint8_t bPos, const uint8_t aPos)
414{
415 size_t x = 0;
416
417 for (; x < width - width % 16; x += 16)
418 {
419 uint8x16_t U[2] = { vld1q_u8(&pU[0][x]), vld1q_u8(&pU[1][x]) };
420 neon_avgUV(U);
421
422 uint8x16_t V[2] = { vld1q_u8(&pV[0][x]), vld1q_u8(&pV[1][x]) };
423 neon_avgUV(V);
424
425 const uint8x16_t Y0raw = vld1q_u8(&pY[0][x]);
426 const uint8x8x2_t Y0 = { { vget_low_u8(Y0raw), vget_high_u8(Y0raw) } };
427 const int16x8x2_t D0 = loadUV444(U[0]);
428 const int16x8x2_t E0 = loadUV444(V[0]);
429 neon_YuvToRgbPixel(&pRGB[0][4ULL * x], Y0, D0, E0, rPos, gPos, bPos, aPos);
430
431 const uint8x16_t Y1raw = vld1q_u8(&pY[1][x]);
432 const uint8x8x2_t Y1 = { { vget_low_u8(Y1raw), vget_high_u8(Y1raw) } };
433 const int16x8x2_t D1 = loadUV444(U[1]);
434 const int16x8x2_t E1 = loadUV444(V[1]);
435 neon_YuvToRgbPixel(&pRGB[1][4ULL * x], Y1, D1, E1, rPos, gPos, bPos, aPos);
436 }
437
438 for (; x < width; x += 2)
439 {
440 BYTE* rgb[2] = { &pRGB[0][x * 4], &pRGB[1][x * 4] };
441 BYTE U[2][2] = { { pU[0][x], pU[0][x + 1] }, { pU[1][x], pU[1][x + 1] } };
442 avgUV(U);
443
444 BYTE V[2][2] = { { pV[0][x], pV[0][x + 1] }, { pV[1][x], pV[1][x + 1] } };
445 avgUV(V);
446
447 for (size_t i = 0; i < 2; i++)
448 {
449 for (size_t j = 0; j < 2; j++)
450 {
451 const BYTE y = pY[i][x + j];
452 const BYTE u = U[i][j];
453 const BYTE v = V[i][j];
454
455 neon_write_pixel(&rgb[i][4 * (j)], y, u, v, rPos, gPos, bPos, aPos);
456 }
457 }
458 }
459
460 return PRIMITIVES_SUCCESS;
461}
462
463static inline pstatus_t neon_YUV444ToX(const BYTE* WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3],
464 BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
465 const prim_size_t* WINPR_RESTRICT roi, const uint8_t rPos,
466 const uint8_t gPos, const uint8_t bPos, const uint8_t aPos)
467{
468 WINPR_ASSERT(roi);
469 const UINT32 nWidth = roi->width;
470 const UINT32 nHeight = roi->height;
471
472 size_t y = 0;
473 for (; y < nHeight - nHeight % 2; y += 2)
474 {
475 const uint8_t* WINPR_RESTRICT pY[2] = { pSrc[0] + y * srcStep[0],
476 pSrc[0] + (y + 1) * srcStep[0] };
477 const uint8_t* WINPR_RESTRICT pU[2] = { pSrc[1] + y * srcStep[1],
478 pSrc[1] + (y + 1) * srcStep[1] };
479 const uint8_t* WINPR_RESTRICT pV[2] = { pSrc[2] + y * srcStep[2],
480 pSrc[2] + (y + 1) * srcStep[2] };
481
482 uint8_t* WINPR_RESTRICT pRGB[2] = { &pDst[y * dstStep], &pDst[(y + 1) * dstStep] };
483
484 const pstatus_t rc =
485 neon_YUV444ToX_DOUBLE_ROW(pY, pU, pV, pRGB, nWidth, rPos, gPos, bPos, aPos);
486 if (rc != PRIMITIVES_SUCCESS)
487 return rc;
488 }
489 for (; y < nHeight; y++)
490 {
491 const uint8_t* WINPR_RESTRICT pY = pSrc[0] + y * srcStep[0];
492 const uint8_t* WINPR_RESTRICT pU = pSrc[1] + y * srcStep[1];
493 const uint8_t* WINPR_RESTRICT pV = pSrc[2] + y * srcStep[2];
494 uint8_t* WINPR_RESTRICT pRGB = &pDst[y * dstStep];
495
496 const pstatus_t rc =
497 neon_YUV444ToX_SINGLE_ROW(pY, pU, pV, pRGB, nWidth, rPos, gPos, bPos, aPos);
498 if (rc != PRIMITIVES_SUCCESS)
499 return rc;
500 }
501
502 return PRIMITIVES_SUCCESS;
503}
504
505static pstatus_t neon_YUV444ToRGB_8u_P3AC4R(const BYTE* WINPR_RESTRICT pSrc[3],
506 const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDst,
507 UINT32 dstStep, UINT32 DstFormat,
508 const prim_size_t* WINPR_RESTRICT roi)
509{
510 switch (DstFormat)
511 {
512 case PIXEL_FORMAT_BGRA32:
513 case PIXEL_FORMAT_BGRX32:
514 return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0, 3);
515
516 case PIXEL_FORMAT_RGBA32:
517 case PIXEL_FORMAT_RGBX32:
518 return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2, 3);
519
520 case PIXEL_FORMAT_ARGB32:
521 case PIXEL_FORMAT_XRGB32:
522 return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3, 0);
523
524 case PIXEL_FORMAT_ABGR32:
525 case PIXEL_FORMAT_XBGR32:
526 return neon_YUV444ToX(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1, 0);
527
528 default:
529 return generic->YUV444ToRGB_8u_P3AC4R(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
530 }
531}
532
533static inline void neon_I444ToX_ROW(const BYTE* WINPR_RESTRICT pY, const BYTE* WINPR_RESTRICT pU,
534 const BYTE* WINPR_RESTRICT pV, BYTE* WINPR_RESTRICT pRGB,
535 size_t width, const uint8_t rPos, const uint8_t gPos,
536 const uint8_t bPos, const uint8_t aPos)
537{
538 size_t x = 0;
539
540 for (; x < width - width % 16; x += 16)
541 {
542 const uint8x16_t Yraw = vld1q_u8(&pY[x]);
543 const uint8x8x2_t Y = { { vget_low_u8(Yraw), vget_high_u8(Yraw) } };
544 const int16x8x2_t D = loadUV444(vld1q_u8(&pU[x]));
545 const int16x8x2_t E = loadUV444(vld1q_u8(&pV[x]));
546 neon_YuvToRgbPixel(&pRGB[4ULL * x], Y, D, E, rPos, gPos, bPos, aPos);
547 }
548
549 for (; x < width; x++)
550 neon_write_pixel(&pRGB[4ULL * x], pY[x], pU[x], pV[x], rPos, gPos, bPos, aPos);
551}
552
553static inline pstatus_t neon_I444ToX(const BYTE* WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3],
554 BYTE* WINPR_RESTRICT pDst, UINT32 dstStep,
555 const prim_size_t* WINPR_RESTRICT roi, const uint8_t rPos,
556 const uint8_t gPos, const uint8_t bPos, const uint8_t aPos)
557{
558 WINPR_ASSERT(roi);
559
560 for (size_t y = 0; y < roi->height; y++)
561 {
562 const uint8_t* WINPR_RESTRICT pY = pSrc[0] + y * srcStep[0];
563 const uint8_t* WINPR_RESTRICT pU = pSrc[1] + y * srcStep[1];
564 const uint8_t* WINPR_RESTRICT pV = pSrc[2] + y * srcStep[2];
565 uint8_t* WINPR_RESTRICT pRGB = &pDst[y * dstStep];
566
567 neon_I444ToX_ROW(pY, pU, pV, pRGB, roi->width, rPos, gPos, bPos, aPos);
568 }
569
570 return PRIMITIVES_SUCCESS;
571}
572
573static pstatus_t neon_I444ToRGB_8u(const BYTE* WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3],
574 BYTE* WINPR_RESTRICT pDst, UINT32 dstStep, UINT32 DstFormat,
575 const prim_size_t* WINPR_RESTRICT roi)
576{
577 switch (DstFormat)
578 {
579 case PIXEL_FORMAT_BGRA32:
580 case PIXEL_FORMAT_BGRX32:
581 return neon_I444ToX(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0, 3);
582
583 case PIXEL_FORMAT_RGBA32:
584 case PIXEL_FORMAT_RGBX32:
585 return neon_I444ToX(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2, 3);
586
587 case PIXEL_FORMAT_ARGB32:
588 case PIXEL_FORMAT_XRGB32:
589 return neon_I444ToX(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3, 0);
590
591 case PIXEL_FORMAT_ABGR32:
592 case PIXEL_FORMAT_XBGR32:
593 return neon_I444ToX(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1, 0);
594
595 default:
596 return generic->I444ToRGB_8u(pSrc, srcStep, pDst, dstStep, DstFormat, roi);
597 }
598}
599
600/* RGBToYUV420_8u_P3AC4R: Y for every pixel, U and V from the average R, G and B of
601 * each 2x2 block, with the exact integer math of the generic routine (RGB2Y,
602 * RGB2U, RGB2V; the average is the sum of four >> 2), so the result matches it
603 * byte for byte. 16 bit lanes are wide enough for every intermediate value. */
604
605/* the low byte of 16 bit lanes: (54 R + 183 G + 18 B) >> 8, at most 65025 */
606static inline uint8x8_t neon_YUV420_Y8(uint16x8_t r, uint16x8_t g, uint16x8_t b)
607{
608 uint16x8_t t = vmulq_n_u16(r, 54);
609 t = vmlaq_n_u16(t, g, 183);
610 t = vmlaq_n_u16(t, b, 18);
611 return vshrn_n_u16(t, 8);
612}
613
614static inline uint8x16_t neon_YUV420_Y(uint8x16_t r, uint8x16_t g, uint8x16_t b)
615{
616 const uint8x8_t lo = neon_YUV420_Y8(vmovl_u8(vget_low_u8(r)), vmovl_u8(vget_low_u8(g)),
617 vmovl_u8(vget_low_u8(b)));
618 const uint8x8_t hi = neon_YUV420_Y8(vmovl_u8(vget_high_u8(r)), vmovl_u8(vget_high_u8(g)),
619 vmovl_u8(vget_high_u8(b)));
620 return vcombine_u8(lo, hi);
621}
622
623/* U and V of eight 2x2 blocks given as their average R, G and B in 16 bit lanes:
624 * ((128 B - (29 R + 99 G)) >> 8) + 128 and ((128 R - (116 G + 12 B)) >> 8) + 128,
625 * both terms at most 32640 */
626static inline void neon_YUV420_UV(uint16x8_t r, uint16x8_t g, uint16x8_t b,
627 uint8x8_t* WINPR_RESTRICT u, uint8x8_t* WINPR_RESTRICT v)
628{
629 const int16x8_t c128 = vdupq_n_s16(128);
630 const int16x8_t rs = vreinterpretq_s16_u16(r);
631 const int16x8_t gs = vreinterpretq_s16_u16(g);
632 const int16x8_t bs = vreinterpretq_s16_u16(b);
633
634 int16x8_t s = vmlaq_n_s16(vmulq_n_s16(rs, 29), gs, 99);
635 s = vsubq_s16(vshlq_n_s16(bs, 7), s);
636 *u = vqmovun_s16(vaddq_s16(vshrq_n_s16(s, 8), c128));
637
638 s = vmlaq_n_s16(vmulq_n_s16(gs, 116), bs, 12);
639 s = vsubq_s16(vshlq_n_s16(rs, 7), s);
640 *v = vqmovun_s16(vaddq_s16(vshrq_n_s16(s, 8), c128));
641}
642
643/* one of the four byte planes of vld4q_u8, picked with constant indices: indexing
644 * px.val[] with a variable makes the compiler spill all four to the stack */
645static inline uint8x16_t neon_YUV420_plane(const uint8x16x4_t px, const size_t off)
646{
647 switch (off)
648 {
649 case 0:
650 return px.val[0];
651 case 1:
652 return px.val[1];
653 case 2:
654 return px.val[2];
655 default:
656 return px.val[3];
657 }
658}
659
660static inline pstatus_t neon_RGBToYUV420_32bpp(const BYTE* WINPR_RESTRICT pSrc, UINT32 SrcFormat,
661 UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
662 const UINT32 dstStep[3],
663 const prim_size_t* WINPR_RESTRICT roi,
664 const size_t rOff, const size_t bOff)
665{
666 const size_t gOff = 1;
667 /* the vector loop takes 16x2 pixel blocks; the generic routine does the
668 * columns right of them and an odd last row, so the edges keep its exact
669 * behaviour */
670 const size_t width = roi->width - roi->width % 16;
671 const size_t height = roi->height - roi->height % 2;
672
673 for (size_t row = 0; row < height; row += 2)
674 {
675 const BYTE* src0 = &pSrc[row * srcStep];
676 const BYTE* src1 = &pSrc[(row + 1) * srcStep];
677 BYTE* y0 = &pDst[0][row * dstStep[0]];
678 BYTE* y1 = &pDst[0][(row + 1) * dstStep[0]];
679 BYTE* ud = &pDst[1][(row / 2) * dstStep[1]];
680 BYTE* vd = &pDst[2][(row / 2) * dstStep[2]];
681
682 for (size_t x = 0; x < width; x += 16)
683 {
684 const uint8x16x4_t p0 = vld4q_u8(&src0[4 * x]);
685 const uint8x16x4_t p1 = vld4q_u8(&src1[4 * x]);
686 const uint8x16_t r0 = neon_YUV420_plane(p0, rOff);
687 const uint8x16_t g0 = neon_YUV420_plane(p0, gOff);
688 const uint8x16_t b0 = neon_YUV420_plane(p0, bOff);
689 const uint8x16_t r1 = neon_YUV420_plane(p1, rOff);
690 const uint8x16_t g1 = neon_YUV420_plane(p1, gOff);
691 const uint8x16_t b1 = neon_YUV420_plane(p1, bOff);
692
693 vst1q_u8(&y0[x], neon_YUV420_Y(r0, g0, b0));
694 vst1q_u8(&y1[x], neon_YUV420_Y(r1, g1, b1));
695
696 /* the sum of each 2x2 block: pairs in a row, then the two rows */
697 const uint16x8_t r = vshrq_n_u16(vpadalq_u8(vpaddlq_u8(r0), r1), 2);
698 const uint16x8_t g = vshrq_n_u16(vpadalq_u8(vpaddlq_u8(g0), g1), 2);
699 const uint16x8_t b = vshrq_n_u16(vpadalq_u8(vpaddlq_u8(b0), b1), 2);
700 uint8x8_t u = vdup_n_u8(0);
701 uint8x8_t v = vdup_n_u8(0);
702 neon_YUV420_UV(r, g, b, &u, &v);
703 vst1_u8(&ud[x / 2], u);
704 vst1_u8(&vd[x / 2], v);
705 }
706 }
707
708 /* the columns right of the vector blocks, every row */
709 if (width < roi->width)
710 {
711 const BYTE* src = &pSrc[4 * width];
712 BYTE* dst[3] = { &pDst[0][width], &pDst[1][width / 2], &pDst[2][width / 2] };
713 const prim_size_t rest = { roi->width - (UINT32)width, roi->height };
714 const pstatus_t rc =
715 generic->RGBToYUV420_8u_P3AC4R(src, SrcFormat, srcStep, dst, dstStep, &rest);
716 if (rc != PRIMITIVES_SUCCESS)
717 return rc;
718 }
719
720 /* an odd last row, left of those columns */
721 if ((height < roi->height) && (width > 0))
722 {
723 const BYTE* src = &pSrc[height * srcStep];
724 BYTE* dst[3] = { &pDst[0][height * dstStep[0]], &pDst[1][(height / 2) * dstStep[1]],
725 &pDst[2][(height / 2) * dstStep[2]] };
726 const prim_size_t rest = { (UINT32)width, 1 };
727 return generic->RGBToYUV420_8u_P3AC4R(src, SrcFormat, srcStep, dst, dstStep, &rest);
728 }
729
730 return PRIMITIVES_SUCCESS;
731}
732
733static pstatus_t neon_RGBToYUV420_8u_P3AC4R(const BYTE* WINPR_RESTRICT pSrc, UINT32 SrcFormat,
734 UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
735 const UINT32 dstStep[3],
736 const prim_size_t* WINPR_RESTRICT roi)
737{
738 /* FreeRDPReadColor reads 32 bit pixels most significant byte first: the
739 * first channel of the format name is the first byte in memory. The other
740 * formats average differently in the generic routine, so they stay there. */
741 switch (SrcFormat)
742 {
743 case PIXEL_FORMAT_BGRA32:
744 case PIXEL_FORMAT_BGRX32:
745 return neon_RGBToYUV420_32bpp(pSrc, SrcFormat, srcStep, pDst, dstStep, roi, 2, 0);
746 case PIXEL_FORMAT_RGBA32:
747 case PIXEL_FORMAT_RGBX32:
748 return neon_RGBToYUV420_32bpp(pSrc, SrcFormat, srcStep, pDst, dstStep, roi, 0, 2);
749 default:
750 return generic->RGBToYUV420_8u_P3AC4R(pSrc, SrcFormat, srcStep, pDst, dstStep, roi);
751 }
752}
753
754/* RGBToAVC444YUV and RGBToAVC444YUVv2 for BGRX/BGRA: Y, U and V of every pixel with
755 * the exact integer math of the generic routines, then laid out in the main and
756 * auxiliary views as they do, so the result matches them byte for byte. The
757 * columns right of the last 16 pixel block go to the generic
758 * general_RGBToAVC444YUV*_BGRX_DOUBLE_ROW, an odd last row too: they keep the
759 * generic edge behaviour. */
760
761/* Y, U and V of sixteen BGRX pixels */
762static inline void neon_AVC444_row16(const BYTE* WINPR_RESTRICT src, uint8x16_t* WINPR_RESTRICT y,
763 uint8x16_t* WINPR_RESTRICT u, uint8x16_t* WINPR_RESTRICT v)
764{
765 const uint8x16x4_t px = vld4q_u8(src);
766 const uint8x16_t r = px.val[2];
767 const uint8x16_t g = px.val[1];
768 const uint8x16_t b = px.val[0];
769 uint8x8_t ul = vdup_n_u8(0);
770 uint8x8_t vl = vdup_n_u8(0);
771 uint8x8_t uh = vdup_n_u8(0);
772 uint8x8_t vh = vdup_n_u8(0);
773
774 *y = neon_YUV420_Y(r, g, b);
775 neon_YUV420_UV(vmovl_u8(vget_low_u8(r)), vmovl_u8(vget_low_u8(g)), vmovl_u8(vget_low_u8(b)),
776 &ul, &vl);
777 neon_YUV420_UV(vmovl_u8(vget_high_u8(r)), vmovl_u8(vget_high_u8(g)), vmovl_u8(vget_high_u8(b)),
778 &uh, &vh);
779 *u = vcombine_u8(ul, uh);
780 *v = vcombine_u8(vl, vh);
781}
782
783/* (a + b + c + d) / 4 of each 2x2 block of two rows of samples */
784static inline uint8x8_t neon_AVC444_avg(uint8x16_t even, uint8x16_t odd)
785{
786 return vmovn_u16(vshrq_n_u16(vpadalq_u8(vpaddlq_u8(even), odd), 2));
787}
788
789/* the samples of the even or of the odd columns */
790static inline uint8x8_t neon_AVC444_even(uint8x16_t a)
791{
792 return vget_low_u8(vuzpq_u8(a, a).val[0]);
793}
794
795static inline uint8x8_t neon_AVC444_odd(uint8x16_t a)
796{
797 return vget_low_u8(vuzpq_u8(a, a).val[1]);
798}
799
800/* store the first four bytes of a */
801static inline void neon_AVC444_store4(BYTE* WINPR_RESTRICT dst, uint8x8_t a)
802{
803 BYTE tmp[8];
804 vst1_u8(tmp, a);
805 memcpy(dst, tmp, 4);
806}
807
808static pstatus_t neon_RGBToAVC444YUV_BGRX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
809 BYTE* WINPR_RESTRICT pDst1[3], const UINT32 dst1Step[3],
810 BYTE* WINPR_RESTRICT pDst2[3], const UINT32 dst2Step[3],
811 const prim_size_t* WINPR_RESTRICT roi)
812{
813 const size_t width = roi->width - roi->width % 16;
814
815 size_t y = 0;
816 for (; y < roi->height - roi->height % 2; y += 2)
817 {
818 const BYTE* srcEven = &pSrc[y * srcStep];
819 const BYTE* srcOdd = &pSrc[(y + 1) * srcStep];
820 const size_t i = y >> 1;
821 const size_t n = (i & (uint32_t)~7) + i;
822 BYTE* b1Even = &pDst1[0][y * dst1Step[0]];
823 BYTE* b1Odd = b1Even + dst1Step[0];
824 BYTE* b2 = &pDst1[1][(y / 2) * dst1Step[1]];
825 BYTE* b3 = &pDst1[2][(y / 2) * dst1Step[2]];
826 BYTE* b4 = &pDst2[0][dst2Step[0] * n];
827 BYTE* b5 = b4 + 8ULL * dst2Step[0];
828 BYTE* b6 = &pDst2[1][(y / 2) * dst2Step[1]];
829 BYTE* b7 = &pDst2[2][(y / 2) * dst2Step[2]];
830
831 for (size_t x = 0; x < width; x += 16)
832 {
833 uint8x16_t ye = vdupq_n_u8(0);
834 uint8x16_t ue = vdupq_n_u8(0);
835 uint8x16_t ve = vdupq_n_u8(0);
836 uint8x16_t yo = vdupq_n_u8(0);
837 uint8x16_t uo = vdupq_n_u8(0);
838 uint8x16_t vo = vdupq_n_u8(0);
839 neon_AVC444_row16(&srcEven[4 * x], &ye, &ue, &ve);
840 neon_AVC444_row16(&srcOdd[4 * x], &yo, &uo, &vo);
841
842 /* main view: Y, and U and V averaged over each 2x2 block */
843 vst1q_u8(&b1Even[x], ye);
844 vst1q_u8(&b1Odd[x], yo);
845 vst1_u8(&b2[x / 2], neon_AVC444_avg(ue, uo));
846 vst1_u8(&b3[x / 2], neon_AVC444_avg(ve, vo));
847 /* auxiliary view: U and V of the odd row, and of the odd columns of
848 * the even row */
849 vst1q_u8(&b4[x], uo);
850 vst1q_u8(&b5[x], vo);
851 vst1_u8(&b6[x / 2], neon_AVC444_odd(ue));
852 vst1_u8(&b7[x / 2], neon_AVC444_odd(ve));
853 }
854
855 general_RGBToAVC444YUV_BGRX_DOUBLE_ROW(
856 width, srcEven, srcOdd, &b1Even[width], &b1Odd[width], &b2[width / 2], &b3[width / 2],
857 &b4[width], &b5[width], &b6[width / 2], &b7[width / 2], roi->width);
858 }
859
860 for (; y < roi->height; y++)
861 {
862 const BYTE* srcEven = &pSrc[y * srcStep];
863 BYTE* b1Even = &pDst1[0][y * dst1Step[0]];
864 BYTE* b2 = &pDst1[1][(y / 2) * dst1Step[1]];
865 BYTE* b3 = &pDst1[2][(y / 2) * dst1Step[2]];
866 BYTE* b6 = &pDst2[1][(y / 2) * dst2Step[1]];
867 BYTE* b7 = &pDst2[2][(y / 2) * dst2Step[2]];
868 general_RGBToAVC444YUV_BGRX_DOUBLE_ROW(0, srcEven, nullptr, b1Even, nullptr, b2, b3,
869 nullptr, nullptr, b6, b7, roi->width);
870 }
871
872 return PRIMITIVES_SUCCESS;
873}
874
875static pstatus_t neon_RGBToAVC444YUV(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
876 UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[3],
877 const UINT32 dst1Step[3], BYTE* WINPR_RESTRICT pDst2[3],
878 const UINT32 dst2Step[3],
879 const prim_size_t* WINPR_RESTRICT roi)
880{
881 switch (srcFormat)
882 {
883 case PIXEL_FORMAT_BGRA32:
884 case PIXEL_FORMAT_BGRX32:
885 return neon_RGBToAVC444YUV_BGRX(pSrc, srcStep, pDst1, dst1Step, pDst2, dst2Step, roi);
886 default:
887 return generic->RGBToAVC444YUV(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
888 dst2Step, roi);
889 }
890}
891
892static pstatus_t neon_RGBToAVC444YUVv2_BGRX(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
893 BYTE* WINPR_RESTRICT pDst1[3], const UINT32 dst1Step[3],
894 BYTE* WINPR_RESTRICT pDst2[3], const UINT32 dst2Step[3],
895 const prim_size_t* WINPR_RESTRICT roi)
896{
897 if (roi->height < 1 || roi->width < 1)
898 return !PRIMITIVES_SUCCESS;
899
900 const size_t width = roi->width - roi->width % 16;
901
902 size_t y = 0;
903 for (; y < roi->height - roi->height % 2; y += 2)
904 {
905 const BYTE* srcEven = &pSrc[y * srcStep];
906 const BYTE* srcOdd = srcEven + srcStep;
907 BYTE* yLumaEven = &pDst1[0][y * dst1Step[0]];
908 BYTE* yLumaOdd = yLumaEven + dst1Step[0];
909 BYTE* uLuma = &pDst1[1][(y / 2) * dst1Step[1]];
910 BYTE* vLuma = &pDst1[2][(y / 2) * dst1Step[2]];
911 BYTE* yEvenChroma1 = &pDst2[0][y * dst2Step[0]];
912 BYTE* yEvenChroma2 = yEvenChroma1 + roi->width / 2;
913 BYTE* yOddChroma1 = yEvenChroma1 + dst2Step[0];
914 BYTE* yOddChroma2 = yEvenChroma2 + dst2Step[0];
915 BYTE* uChroma1 = &pDst2[1][(y / 2) * dst2Step[1]];
916 BYTE* vChroma1 = &pDst2[2][(y / 2) * dst2Step[2]];
917 BYTE* uChroma2 = uChroma1 + roi->width / 4;
918 BYTE* vChroma2 = vChroma1 + roi->width / 4;
919
920 for (size_t x = 0; x < width; x += 16)
921 {
922 uint8x16_t ye = vdupq_n_u8(0);
923 uint8x16_t ue = vdupq_n_u8(0);
924 uint8x16_t ve = vdupq_n_u8(0);
925 uint8x16_t yo = vdupq_n_u8(0);
926 uint8x16_t uo = vdupq_n_u8(0);
927 uint8x16_t vo = vdupq_n_u8(0);
928 neon_AVC444_row16(&srcEven[4 * x], &ye, &ue, &ve);
929 neon_AVC444_row16(&srcOdd[4 * x], &yo, &uo, &vo);
930
931 /* main view: Y, and U and V averaged over each 2x2 block */
932 vst1q_u8(&yLumaEven[x], ye);
933 vst1q_u8(&yLumaOdd[x], yo);
934 vst1_u8(&uLuma[x / 2], neon_AVC444_avg(ue, uo));
935 vst1_u8(&vLuma[x / 2], neon_AVC444_avg(ve, vo));
936
937 /* auxiliary view, Y plane: U and V of the odd columns, both rows */
938 vst1_u8(&yEvenChroma1[x / 2], neon_AVC444_odd(ue));
939 vst1_u8(&yEvenChroma2[x / 2], neon_AVC444_odd(ve));
940 vst1_u8(&yOddChroma1[x / 2], neon_AVC444_odd(uo));
941 vst1_u8(&yOddChroma2[x / 2], neon_AVC444_odd(vo));
942
943 /* auxiliary view, U and V planes: U and V of the even columns of the
944 * odd row; columns 4k go to the U plane, columns 4k+2 to the V plane */
945 const uint8x8_t uc = neon_AVC444_even(uo);
946 const uint8x8_t vc = neon_AVC444_even(vo);
947 const uint8x8x2_t us = vuzp_u8(uc, uc);
948 const uint8x8x2_t vs = vuzp_u8(vc, vc);
949 neon_AVC444_store4(&uChroma1[x / 4], us.val[0]);
950 neon_AVC444_store4(&uChroma2[x / 4], vs.val[0]);
951 neon_AVC444_store4(&vChroma1[x / 4], us.val[1]);
952 neon_AVC444_store4(&vChroma2[x / 4], vs.val[1]);
953 }
954
955 general_RGBToAVC444YUVv2_BGRX_DOUBLE_ROW(
956 width, srcEven, srcOdd, &yLumaEven[width], &yLumaOdd[width], &uLuma[width / 2],
957 &vLuma[width / 2], &yEvenChroma1[width / 2], &yEvenChroma2[width / 2],
958 &yOddChroma1[width / 2], &yOddChroma2[width / 2], &uChroma1[width / 4],
959 &uChroma2[width / 4], &vChroma1[width / 4], &vChroma2[width / 4], roi->width);
960 }
961
962 for (; y < roi->height; y++)
963 {
964 const BYTE* srcEven = &pSrc[y * srcStep];
965 BYTE* yLumaEven = &pDst1[0][y * dst1Step[0]];
966 BYTE* uLuma = &pDst1[1][(y / 2) * dst1Step[1]];
967 BYTE* vLuma = &pDst1[2][(y / 2) * dst1Step[2]];
968 BYTE* yEvenChroma1 = &pDst2[0][y * dst2Step[0]];
969 BYTE* yEvenChroma2 = yEvenChroma1 + roi->width / 2;
970 general_RGBToAVC444YUVv2_BGRX_DOUBLE_ROW(
971 0, srcEven, nullptr, yLumaEven, nullptr, uLuma, vLuma, yEvenChroma1, yEvenChroma2,
972 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, roi->width);
973 }
974
975 return PRIMITIVES_SUCCESS;
976}
977
978static pstatus_t neon_RGBToAVC444YUVv2(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcFormat,
979 UINT32 srcStep, BYTE* WINPR_RESTRICT pDst1[3],
980 const UINT32 dst1Step[3], BYTE* WINPR_RESTRICT pDst2[3],
981 const UINT32 dst2Step[3],
982 const prim_size_t* WINPR_RESTRICT roi)
983{
984 switch (srcFormat)
985 {
986 case PIXEL_FORMAT_BGRA32:
987 case PIXEL_FORMAT_BGRX32:
988 return neon_RGBToAVC444YUVv2_BGRX(pSrc, srcStep, pDst1, dst1Step, pDst2, dst2Step, roi);
989 default:
990 return generic->RGBToAVC444YUVv2(pSrc, srcFormat, srcStep, pDst1, dst1Step, pDst2,
991 dst2Step, roi);
992 }
993}
994
995static pstatus_t neon_LumaToYUV444(const BYTE* WINPR_RESTRICT pSrcRaw[3], const UINT32 srcStep[3],
996 BYTE* WINPR_RESTRICT pDstRaw[3], const UINT32 dstStep[3],
997 const RECTANGLE_16* WINPR_RESTRICT roi)
998{
999 const UINT32 nWidth = roi->right - roi->left;
1000 const UINT32 nHeight = roi->bottom - roi->top;
1001 const UINT32 halfWidth = (nWidth + 1) / 2;
1002 const UINT32 halfHeight = (nHeight + 1) / 2;
1003 const UINT32 evenY = 0;
1004 const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
1005 pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
1006 pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
1007 BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
1008 pDstRaw[1] + roi->top * dstStep[1] + roi->left,
1009 pDstRaw[2] + roi->top * dstStep[2] + roi->left };
1010
1011 /* Y data is already here... */
1012 /* B1 */
1013 for (UINT32 y = 0; y < nHeight; y++)
1014 {
1015 const BYTE* Ym = pSrc[0] + srcStep[0] * y;
1016 BYTE* pY = pDst[0] + dstStep[0] * y;
1017 memcpy(pY, Ym, nWidth);
1018 }
1019
1020 /* The first half of U, V are already here part of this frame. */
1021 /* B2 and B3 */
1022 for (UINT32 y = 0; y < halfHeight; y++)
1023 {
1024 const UINT32 val2y = (2 * y + evenY);
1025 const BYTE* Um = pSrc[1] + srcStep[1] * y;
1026 const BYTE* Vm = pSrc[2] + srcStep[2] * y;
1027 BYTE* pU = pDst[1] + dstStep[1] * val2y;
1028 BYTE* pV = pDst[2] + dstStep[2] * val2y;
1029 BYTE* pU1 = pU + dstStep[1];
1030 BYTE* pV1 = pV + dstStep[2];
1031
1032 UINT32 x = 0;
1033 for (; x + 16 < halfWidth; x += 16)
1034 {
1035 {
1036 const uint8x16_t u = vld1q_u8(Um);
1037 uint8x16x2_t u2x;
1038 u2x.val[0] = u;
1039 u2x.val[1] = u;
1040 vst2q_u8(pU, u2x);
1041 vst2q_u8(pU1, u2x);
1042 Um += 16;
1043 pU += 32;
1044 pU1 += 32;
1045 }
1046 {
1047 const uint8x16_t v = vld1q_u8(Vm);
1048 uint8x16x2_t v2x;
1049 v2x.val[0] = v;
1050 v2x.val[1] = v;
1051 vst2q_u8(pV, v2x);
1052 vst2q_u8(pV1, v2x);
1053 Vm += 16;
1054 pV += 32;
1055 pV1 += 32;
1056 }
1057 }
1058
1059 for (; x < halfWidth; x++)
1060 {
1061 const BYTE u = *Um++;
1062 const BYTE v = *Vm++;
1063 *pU++ = u;
1064 *pU++ = u;
1065 *pU1++ = u;
1066 *pU1++ = u;
1067 *pV++ = v;
1068 *pV++ = v;
1069 *pV1++ = v;
1070 *pV1++ = v;
1071 }
1072 }
1073
1074 return PRIMITIVES_SUCCESS;
1075}
1076
1077static pstatus_t neon_ChromaV1ToYUV444(const BYTE* WINPR_RESTRICT pSrcRaw[3],
1078 const UINT32 srcStep[3], BYTE* WINPR_RESTRICT pDstRaw[3],
1079 const UINT32 dstStep[3],
1080 const RECTANGLE_16* WINPR_RESTRICT roi)
1081{
1082 const UINT32 mod = 16;
1083 UINT32 uY = 0;
1084 UINT32 vY = 0;
1085 const UINT32 nWidth = roi->right - roi->left;
1086 const UINT32 nHeight = roi->bottom - roi->top;
1087 const UINT32 halfWidth = (nWidth) / 2;
1088 const UINT32 halfHeight = (nHeight) / 2;
1089 const UINT32 oddY = 1;
1090 const UINT32 evenY = 0;
1091 const UINT32 oddX = 1;
1092 /* The auxiliary frame is aligned to multiples of 16x16.
1093 * We need the padded height for B4 and B5 conversion. */
1094 const UINT32 padHeight = nHeight + 16 - nHeight % 16;
1095 const UINT32 halfPad = halfWidth % 16;
1096 const BYTE* pSrc[3] = { pSrcRaw[0] + roi->top * srcStep[0] + roi->left,
1097 pSrcRaw[1] + roi->top / 2 * srcStep[1] + roi->left / 2,
1098 pSrcRaw[2] + roi->top / 2 * srcStep[2] + roi->left / 2 };
1099 BYTE* pDst[3] = { pDstRaw[0] + roi->top * dstStep[0] + roi->left,
1100 pDstRaw[1] + roi->top * dstStep[1] + roi->left,
1101 pDstRaw[2] + roi->top * dstStep[2] + roi->left };
1102
1103 /* The second half of U and V is a bit more tricky... */
1104 /* B4 and B5 */
1105 for (UINT32 y = 0; y < padHeight; y++)
1106 {
1107 const BYTE* Ya = pSrc[0] + srcStep[0] * y;
1108 BYTE* pX;
1109
1110 if ((y) % mod < (mod + 1) / 2)
1111 {
1112 const UINT32 pos = (2 * uY++ + oddY);
1113
1114 if (pos >= nHeight)
1115 continue;
1116
1117 pX = pDst[1] + dstStep[1] * pos;
1118 }
1119 else
1120 {
1121 const UINT32 pos = (2 * vY++ + oddY);
1122
1123 if (pos >= nHeight)
1124 continue;
1125
1126 pX = pDst[2] + dstStep[2] * pos;
1127 }
1128
1129 if (y < nHeight)
1130 memcpy(pX, Ya, nWidth);
1131 }
1132
1133 /* B6 and B7 */
1134 for (UINT32 y = 0; y < halfHeight; y++)
1135 {
1136 const UINT32 val2y = (y * 2 + evenY);
1137 const BYTE* Ua = pSrc[1] + srcStep[1] * y;
1138 const BYTE* Va = pSrc[2] + srcStep[2] * y;
1139 BYTE* pU = pDst[1] + dstStep[1] * val2y;
1140 BYTE* pV = pDst[2] + dstStep[2] * val2y;
1141
1142 UINT32 x = 0;
1143 for (; x < halfWidth - halfPad; x += 16)
1144 {
1145 {
1146 uint8x16x2_t u = vld2q_u8(&pU[2 * x]);
1147 u.val[1] = vld1q_u8(&Ua[x]);
1148 vst2q_u8(&pU[2 * x], u);
1149 }
1150 {
1151 uint8x16x2_t v = vld2q_u8(&pV[2 * x]);
1152 v.val[1] = vld1q_u8(&Va[x]);
1153 vst2q_u8(&pV[2 * x], v);
1154 }
1155 }
1156
1157 for (; x < halfWidth; x++)
1158 {
1159 const UINT32 val2x1 = (x * 2 + oddX);
1160 pU[val2x1] = Ua[x];
1161 pV[val2x1] = Va[x];
1162 }
1163 }
1164
1165 return PRIMITIVES_SUCCESS;
1166}
1167
1168static pstatus_t neon_ChromaV2ToYUV444(const BYTE* WINPR_RESTRICT pSrc[3], const UINT32 srcStep[3],
1169 UINT32 nTotalWidth, UINT32 nTotalHeight,
1170 BYTE* WINPR_RESTRICT pDst[3], const UINT32 dstStep[3],
1171 const RECTANGLE_16* WINPR_RESTRICT roi)
1172{
1173 const UINT32 nWidth = roi->right - roi->left;
1174 const UINT32 nHeight = roi->bottom - roi->top;
1175 const UINT32 halfWidth = (nWidth + 1) / 2;
1176 const UINT32 halfPad = halfWidth % 16;
1177 const UINT32 halfHeight = (nHeight + 1) / 2;
1178 const UINT32 quaterWidth = (nWidth + 3) / 4;
1179 const UINT32 quaterPad = quaterWidth % 16;
1180
1181 /* B4 and B5: odd UV values for width/2, height */
1182 for (UINT32 y = 0; y < nHeight; y++)
1183 {
1184 const UINT32 yTop = y + roi->top;
1185 const BYTE* pYaU = pSrc[0] + srcStep[0] * yTop + roi->left / 2;
1186 const BYTE* pYaV = pYaU + nTotalWidth / 2;
1187 BYTE* pU = pDst[1] + dstStep[1] * yTop + roi->left;
1188 BYTE* pV = pDst[2] + dstStep[2] * yTop + roi->left;
1189
1190 UINT32 x = 0;
1191 for (; x < halfWidth - halfPad; x += 16)
1192 {
1193 {
1194 uint8x16x2_t u = vld2q_u8(&pU[2 * x]);
1195 u.val[1] = vld1q_u8(&pYaU[x]);
1196 vst2q_u8(&pU[2 * x], u);
1197 }
1198 {
1199 uint8x16x2_t v = vld2q_u8(&pV[2 * x]);
1200 v.val[1] = vld1q_u8(&pYaV[x]);
1201 vst2q_u8(&pV[2 * x], v);
1202 }
1203 }
1204
1205 for (; x < halfWidth; x++)
1206 {
1207 const UINT32 odd = 2 * x + 1;
1208 pU[odd] = pYaU[x];
1209 pV[odd] = pYaV[x];
1210 }
1211 }
1212
1213 /* B6 - B9 */
1214 for (UINT32 y = 0; y < halfHeight; y++)
1215 {
1216 const BYTE* pUaU = pSrc[1] + srcStep[1] * (y + roi->top / 2) + roi->left / 4;
1217 const BYTE* pUaV = pUaU + nTotalWidth / 4;
1218 const BYTE* pVaU = pSrc[2] + srcStep[2] * (y + roi->top / 2) + roi->left / 4;
1219 const BYTE* pVaV = pVaU + nTotalWidth / 4;
1220 BYTE* pU = pDst[1] + dstStep[1] * (2 * y + 1 + roi->top) + roi->left;
1221 BYTE* pV = pDst[2] + dstStep[2] * (2 * y + 1 + roi->top) + roi->left;
1222
1223 UINT32 x = 0;
1224 for (; x < quaterWidth - quaterPad; x += 16)
1225 {
1226 {
1227 uint8x16x4_t u = vld4q_u8(&pU[4 * x]);
1228 u.val[0] = vld1q_u8(&pUaU[x]);
1229 u.val[2] = vld1q_u8(&pVaU[x]);
1230 vst4q_u8(&pU[4 * x], u);
1231 }
1232 {
1233 uint8x16x4_t v = vld4q_u8(&pV[4 * x]);
1234 v.val[0] = vld1q_u8(&pUaV[x]);
1235 v.val[2] = vld1q_u8(&pVaV[x]);
1236 vst4q_u8(&pV[4 * x], v);
1237 }
1238 }
1239
1240 for (; x < quaterWidth; x++)
1241 {
1242 pU[4 * x + 0] = pUaU[x];
1243 pV[4 * x + 0] = pUaV[x];
1244 pU[4 * x + 2] = pVaU[x];
1245 pV[4 * x + 2] = pVaV[x];
1246 }
1247 }
1248
1249 return PRIMITIVES_SUCCESS;
1250}
1251
1252static pstatus_t neon_YUV420CombineToYUV444(avc444_frame_type type,
1253 const BYTE* WINPR_RESTRICT pSrc[3],
1254 const UINT32 srcStep[3], UINT32 nWidth, UINT32 nHeight,
1255 BYTE* WINPR_RESTRICT pDst[3], const UINT32 dstStep[3],
1256 const RECTANGLE_16* WINPR_RESTRICT roi)
1257{
1258 if (!pSrc || !pSrc[0] || !pSrc[1] || !pSrc[2])
1259 return -1;
1260
1261 if (!pDst || !pDst[0] || !pDst[1] || !pDst[2])
1262 return -1;
1263
1264 if (!roi)
1265 return -1;
1266
1267 switch (type)
1268 {
1269 case AVC444_LUMA:
1270 return neon_LumaToYUV444(pSrc, srcStep, pDst, dstStep, roi);
1271
1272 case AVC444_CHROMAv1:
1273 return neon_ChromaV1ToYUV444(pSrc, srcStep, pDst, dstStep, roi);
1274
1275 case AVC444_CHROMAv2:
1276 return neon_ChromaV2ToYUV444(pSrc, srcStep, nWidth, nHeight, pDst, dstStep, roi);
1277
1278 default:
1279 return -1;
1280 }
1281}
1282
1283/* RGBToI444_8u: one Y, U and V sample per pixel, with the exact integer math of
1284 * RGB2Y/RGB2U/RGB2V (16 bit lanes are wide enough for every intermediate value),
1285 * so the result matches the generic routine byte for byte. */
1286
1287/* Y, U and V of eight pixels given as 16 bit R, G and B lanes */
1288static inline void neon_I444_YUV(uint16x8_t r, uint16x8_t g, uint16x8_t b,
1289 uint8x8_t* WINPR_RESTRICT y, uint8x8_t* WINPR_RESTRICT u,
1290 uint8x8_t* WINPR_RESTRICT v)
1291{
1292 const int16x8_t c128 = vdupq_n_s16(128);
1293
1294 /* (54 R + 183 G + 18 B) >> 8: at most 65025, exact as unsigned 16 bit */
1295 uint16x8_t t = vmulq_n_u16(r, 54);
1296 t = vmlaq_n_u16(t, g, 183);
1297 t = vmlaq_n_u16(t, b, 18);
1298 *y = vshrn_n_u16(t, 8);
1299
1300 const int16x8_t rs = vreinterpretq_s16_u16(r);
1301 const int16x8_t gs = vreinterpretq_s16_u16(g);
1302 const int16x8_t bs = vreinterpretq_s16_u16(b);
1303
1304 /* ((128 B - (29 R + 99 G)) >> 8) + 128: both terms are at most 32640 */
1305 int16x8_t s = vmlaq_n_s16(vmulq_n_s16(rs, 29), gs, 99);
1306 s = vsubq_s16(vshlq_n_s16(bs, 7), s);
1307 *u = vqmovun_s16(vaddq_s16(vshrq_n_s16(s, 8), c128));
1308
1309 /* ((128 R - (116 G + 12 B)) >> 8) + 128 */
1310 s = vmlaq_n_s16(vmulq_n_s16(gs, 116), bs, 12);
1311 s = vsubq_s16(vshlq_n_s16(rs, 7), s);
1312 *v = vqmovun_s16(vaddq_s16(vshrq_n_s16(s, 8), c128));
1313}
1314
1315/* one of the four byte planes of vld4q_u8, picked with constant indices: indexing
1316 * px.val[] with a variable makes the compiler spill all four to the stack */
1317static inline uint8x16_t neon_I444_plane(const uint8x16x4_t px, const size_t off)
1318{
1319 switch (off)
1320 {
1321 case 0:
1322 return px.val[0];
1323 case 1:
1324 return px.val[1];
1325 case 2:
1326 return px.val[2];
1327 default:
1328 return px.val[3];
1329 }
1330}
1331
1332static inline pstatus_t neon_RGBToI444_32bpp(const BYTE* WINPR_RESTRICT pSrc, UINT32 srcStep,
1333 BYTE* WINPR_RESTRICT pDst[3], const UINT32 dstStep[3],
1334 const prim_size_t* WINPR_RESTRICT roi,
1335 const size_t rOff, const size_t gOff,
1336 const size_t bOff)
1337{
1338 const size_t width = roi->width;
1339
1340 for (size_t row = 0; row < roi->height; row++)
1341 {
1342 const BYTE* src = &pSrc[row * srcStep];
1343 BYTE* yd = &pDst[0][row * dstStep[0]];
1344 BYTE* ud = &pDst[1][row * dstStep[1]];
1345 BYTE* vd = &pDst[2][row * dstStep[2]];
1346
1347 size_t x = 0;
1348 for (; x + 16 <= width; x += 16)
1349 {
1350 /* sixteen pixels, split into one register per byte of the pixel */
1351 const uint8x16x4_t px = vld4q_u8(&src[4 * x]);
1352 const uint8x16_t R = neon_I444_plane(px, rOff);
1353 const uint8x16_t G = neon_I444_plane(px, gOff);
1354 const uint8x16_t B = neon_I444_plane(px, bOff);
1355
1356 uint8x8_t y0 = vdup_n_u8(0);
1357 uint8x8_t u0 = vdup_n_u8(0);
1358 uint8x8_t v0 = vdup_n_u8(0);
1359 uint8x8_t y1 = vdup_n_u8(0);
1360 uint8x8_t u1 = vdup_n_u8(0);
1361 uint8x8_t v1 = vdup_n_u8(0);
1362 neon_I444_YUV(vmovl_u8(vget_low_u8(R)), vmovl_u8(vget_low_u8(G)),
1363 vmovl_u8(vget_low_u8(B)), &y0, &u0, &v0);
1364 neon_I444_YUV(vmovl_u8(vget_high_u8(R)), vmovl_u8(vget_high_u8(G)),
1365 vmovl_u8(vget_high_u8(B)), &y1, &u1, &v1);
1366 vst1q_u8(&yd[x], vcombine_u8(y0, y1));
1367 vst1q_u8(&ud[x], vcombine_u8(u0, u1));
1368 vst1q_u8(&vd[x], vcombine_u8(v0, v1));
1369 }
1370
1371 for (; x < width; x++)
1372 {
1373 const BYTE* p = &src[4 * x];
1374 const BYTE r = p[rOff];
1375 const BYTE g = p[gOff];
1376 const BYTE b = p[bOff];
1377 yd[x] = RGB2Y(r, g, b);
1378 ud[x] = RGB2U(r, g, b);
1379 vd[x] = RGB2V(r, g, b);
1380 }
1381 }
1382
1383 return PRIMITIVES_SUCCESS;
1384}
1385
1386static pstatus_t neon_RGBToI444_8u(const BYTE* WINPR_RESTRICT pSrc, UINT32 SrcFormat,
1387 const UINT32 srcStep, BYTE* WINPR_RESTRICT pDst[3],
1388 const UINT32 dstStep[3], const prim_size_t* WINPR_RESTRICT roi)
1389{
1390 /* byte offsets of R, G and B in memory, as in general_RGBToI444_8u */
1391 switch (SrcFormat)
1392 {
1393 case PIXEL_FORMAT_BGRA32:
1394 case PIXEL_FORMAT_BGRX32:
1395 return neon_RGBToI444_32bpp(pSrc, srcStep, pDst, dstStep, roi, 2, 1, 0);
1396 case PIXEL_FORMAT_RGBA32:
1397 case PIXEL_FORMAT_RGBX32:
1398 return neon_RGBToI444_32bpp(pSrc, srcStep, pDst, dstStep, roi, 0, 1, 2);
1399 case PIXEL_FORMAT_ARGB32:
1400 case PIXEL_FORMAT_XRGB32:
1401 return neon_RGBToI444_32bpp(pSrc, srcStep, pDst, dstStep, roi, 1, 2, 3);
1402 case PIXEL_FORMAT_ABGR32:
1403 case PIXEL_FORMAT_XBGR32:
1404 return neon_RGBToI444_32bpp(pSrc, srcStep, pDst, dstStep, roi, 3, 2, 1);
1405 default:
1406 return generic->RGBToI444_8u(pSrc, SrcFormat, srcStep, pDst, dstStep, roi);
1407 }
1408}
1409#endif
1410
1411void primitives_init_YUV_neon_int(primitives_t* WINPR_RESTRICT prims)
1412{
1413#if defined(NEON_INTRINSICS_ENABLED)
1414 generic = primitives_get_generic();
1415 WLog_VRB(PRIM_TAG, "NEON optimizations");
1416 prims->RGBToYUV420_8u_P3AC4R = neon_RGBToYUV420_8u_P3AC4R;
1417 prims->RGBToAVC444YUV = neon_RGBToAVC444YUV;
1418 prims->RGBToAVC444YUVv2 = neon_RGBToAVC444YUVv2;
1419 prims->YUV420ToRGB_8u_P3AC4R = neon_YUV420ToRGB_8u_P3AC4R;
1420 prims->YUV444ToRGB_8u_P3AC4R = neon_YUV444ToRGB_8u_P3AC4R;
1421 prims->I444ToRGB_8u = neon_I444ToRGB_8u;
1422 prims->RGBToI444_8u = neon_RGBToI444_8u;
1423 prims->YUV420CombineToYUV444 = neon_YUV420CombineToYUV444;
1424#else
1425 WLog_VRB(PRIM_TAG, "undefined WITH_SIMD or neon intrinsics not available");
1426 WINPR_UNUSED(prims);
1427#endif
1428}