aboutsummaryrefslogtreecommitdiff
path: root/jsimd_arm_neon.S
blob: 04fd9b20d5d0fea669435db6da49355a06eeb51d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
/*
 * ARM NEON optimizations for libjpeg-turbo
 *
 * Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 * Contact: Alexander Bokovoy <alexander.bokovoy@nokia.com>
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */
/* Copyright (c) 2011,  NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of the NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */



#if defined(__linux__) && defined(__ELF__)
.section .note.GNU-stack,"",%progbits /* mark stack as non-executable */
#endif

.text
.fpu neon
.arch armv7a
.object_arch armv7a
.arm


#define RESPECT_STRICT_ALIGNMENT 1

/*****************************************************************************/

/* Supplementary macro for setting function attributes */
.macro asm_function fname
    .global \fname
#ifdef __ELF__
    .hidden \fname
    .type \fname, %function
#endif
\fname:
.endm

/* Transpose a block of 4x4 coefficients in four 64-bit registers */
.macro transpose_4x4 x0, x1, x2, x3
    vtrn.16 \x0, \x1
    vtrn.16 \x2, \x3
    vtrn.32 \x0, \x2
    vtrn.32 \x1, \x3
.endm

/*****************************************************************************/

/*
 * jsimd_idct_ifast_neon
 *
 * This function contains a fast, not so accurate integer implementation of
 * the inverse DCT (Discrete Cosine Transform). It uses the same calculations
 * and produces exactly the same output as IJG's original 'jpeg_idct_fast'
 * function from jidctfst.c
 *
 * TODO: a bit better instructions scheduling is needed.
 */

#define XFIX_1_082392200 d0[0]
#define XFIX_1_414213562 d0[1]
#define XFIX_1_847759065 d0[2]
#define XFIX_2_613125930 d0[3]

.balign 16
jsimd_idct_ifast_neon_consts:
    .short (277 * 128 - 256 * 128) /* XFIX_1_082392200 */
    .short (362 * 128 - 256 * 128) /* XFIX_1_414213562 */
    .short (473 * 128 - 256 * 128) /* XFIX_1_847759065 */
    .short (669 * 128 - 512 * 128) /* XFIX_2_613125930 */

/* 1-D IDCT helper macro */

.macro idct_helper  x0, x1, x2, x3, x4, x5, x6, x7, \
                    t10, t11, t12, t13, t14

    vsub.s16        \t10, \x0, \x4
    vadd.s16        \x4,  \x0, \x4
    vswp.s16        \t10, \x0
    vsub.s16        \t11, \x2, \x6
    vadd.s16        \x6,  \x2, \x6
    vswp.s16        \t11, \x2
    vsub.s16        \t10, \x3, \x5
    vadd.s16        \x5,  \x3, \x5
    vswp.s16        \t10, \x3
    vsub.s16        \t11, \x1, \x7
    vadd.s16        \x7,  \x1, \x7
    vswp.s16        \t11, \x1

    vqdmulh.s16     \t13, \x2,  d0[1]
    vadd.s16        \t12, \x3,  \x3
    vadd.s16        \x2,  \x2,  \t13
    vqdmulh.s16     \t13, \x3,  d0[3]
    vsub.s16        \t10,  \x1, \x3
    vadd.s16        \t12, \t12, \t13
    vqdmulh.s16     \t13, \t10, d0[2]
    vsub.s16        \t11, \x7,  \x5
    vadd.s16        \t10, \t10, \t13
    vqdmulh.s16     \t13, \t11, d0[1]
    vadd.s16        \t11, \t11, \t13

    vqdmulh.s16     \t13, \x1,  d0[0]
    vsub.s16        \x2,  \x6,  \x2
    vsub.s16        \t14, \x0,  \x2
    vadd.s16        \x2,  \x0,  \x2
    vadd.s16        \x0,  \x4,  \x6
    vsub.s16        \x4,  \x4,  \x6
    vadd.s16        \x1,  \x1,  \t13
    vadd.s16        \t13, \x7,  \x5
    vsub.s16        \t12, \t13, \t12
    vsub.s16        \t12, \t12, \t10
    vadd.s16        \t11, \t12, \t11
    vsub.s16        \t10, \x1,  \t10
    vadd.s16        \t10, \t10, \t11

    vsub.s16        \x7,  \x0,  \t13
    vadd.s16        \x0,  \x0,  \t13
    vadd.s16        \x6,  \t14, \t12
    vsub.s16        \x1,  \t14, \t12
    vsub.s16        \x5,  \x2,  \t11
    vadd.s16        \x2,  \x2,  \t11
    vsub.s16        \x3,  \x4,  \t10
    vadd.s16        \x4,  \x4,  \t10
.endm

asm_function jsimd_idct_ifast_neon

    DCT_TABLE       .req r0
    COEF_BLOCK      .req r1
    OUTPUT_BUF      .req r2
    OUTPUT_COL      .req r3
    TMP             .req ip

    vpush           {d8-d15}

    /* Load constants */
    adr             TMP, jsimd_idct_ifast_neon_consts
    vld1.16         {d0}, [TMP, :64]

    /* Load all COEF_BLOCK into NEON registers with the following allocation:
     *       0 1 2 3 | 4 5 6 7
     *      ---------+--------
     *   0 | d4      | d5
     *   1 | d6      | d7
     *   2 | d8      | d9
     *   3 | d10     | d11
     *   4 | d12     | d13
     *   5 | d14     | d15
     *   6 | d16     | d17
     *   7 | d18     | d19
     */
    vld1.16         {d4, d5, d6, d7}, [COEF_BLOCK]!
    vld1.16         {d8, d9, d10, d11}, [COEF_BLOCK]!
    vld1.16         {d12, d13, d14, d15}, [COEF_BLOCK]!
    vld1.16         {d16, d17, d18, d19}, [COEF_BLOCK]!
    /* Dequantize */
    vld1.16         {d20, d21, d22, d23}, [DCT_TABLE]!
    vmul.s16        q2, q2, q10
    vld1.16         {d24, d25, d26, d27}, [DCT_TABLE]!
    vmul.s16        q3, q3, q11
    vmul.s16        q4, q4, q12
    vld1.16         {d28, d29, d30, d31}, [DCT_TABLE]!
    vmul.s16        q5, q5, q13
    vmul.s16        q6, q6, q14
    vld1.16         {d20, d21, d22, d23}, [DCT_TABLE]!
    vmul.s16        q7, q7, q15
    vmul.s16        q8, q8, q10
    vmul.s16        q9, q9, q11

    /* Pass 1 : process columns from input, store into work array.*/
    idct_helper     q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, q14
    /* Transpose */
    vtrn.16 q2, q3
    vtrn.16 q4, q5
    vtrn.32 q2, q4
    vtrn.32 q3, q5

    vtrn.16 q6, q7
    vtrn.16 q8, q9
    vtrn.32 q6, q8
    vtrn.32 q7, q9

    vswp            d12, d5
    vswp            d14, d7
    vswp            d16, d9
    vswp            d18, d11

    /* Pass 2 */
    idct_helper     q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, q14
    /* Transpose */

    vtrn.16 q2, q3
    vtrn.16 q4, q5
    vtrn.32 q2, q4
    vtrn.32 q3, q5

    vtrn.16 q6, q7
    vtrn.16 q8, q9
    vtrn.32 q6, q8
    vtrn.32 q7, q9

    vswp            d12, d5
    vswp            d14, d7
    vswp            d16, d9
    vswp            d18, d11

    /* Descale and range limit */
    vmov.s16        q15, #(0x80 << 5)
    vqadd.s16       q2, q2, q15
    vqadd.s16       q3, q3, q15
    vqadd.s16       q4, q4, q15
    vqadd.s16       q5, q5, q15
    vqadd.s16       q6, q6, q15
    vqadd.s16       q7, q7, q15
    vqadd.s16       q8, q8, q15
    vqadd.s16       q9, q9, q15
    vqshrun.s16     d4, q2, #5
    vqshrun.s16     d6, q3, #5
    vqshrun.s16     d8, q4, #5
    vqshrun.s16     d10, q5, #5
    vqshrun.s16     d12, q6, #5
    vqshrun.s16     d14, q7, #5
    vqshrun.s16     d16, q8, #5
    vqshrun.s16     d18, q9, #5

    /* Store results to the output buffer */
    .irp            x, d4, d6, d8, d10, d12, d14, d16, d18
    ldr             TMP, [OUTPUT_BUF], #4
    add             TMP, TMP, OUTPUT_COL
    vst1.8          {\x}, [TMP]!
    .endr

    vpop            {d8-d15}
    bx              lr

    .unreq          DCT_TABLE
    .unreq          COEF_BLOCK
    .unreq          OUTPUT_BUF
    .unreq          OUTPUT_COL
    .unreq          TMP

.purgem idct_helper

/*****************************************************************************/

/*
 * jsimd_idct_4x4_neon
 *
 * This function contains inverse-DCT code for getting reduced-size
 * 4x4 pixels output from an 8x8 DCT block. It uses the same  calculations
 * and produces exactly the same output as IJG's original 'jpeg_idct_4x4'
 * function from jpeg-6b (jidctred.c).
 *
 * NOTE: jpeg-8 has an improved implementation of 4x4 inverse-DCT, which
 *       requires much less arithmetic operations and hence should be faster.
 *       The primary purpose of this particular NEON optimized function is
 *       bit exact compatibility with jpeg-6b.
 *
 * TODO: a bit better instructions scheduling can be achieved by expanding
 *       idct_helper/transpose_4x4 macros and reordering instructions,
 *       but readability will suffer somewhat.
 */

#define CONST_BITS  13

#define FIX_0_211164243  (1730)  /* FIX(0.211164243) */
#define FIX_0_509795579  (4176)  /* FIX(0.509795579) */
#define FIX_0_601344887  (4926)  /* FIX(0.601344887) */
#define FIX_0_720959822  (5906)  /* FIX(0.720959822) */
#define FIX_0_765366865  (6270)  /* FIX(0.765366865) */
#define FIX_0_850430095  (6967)  /* FIX(0.850430095) */
#define FIX_0_899976223  (7373)  /* FIX(0.899976223) */
#define FIX_1_061594337  (8697)  /* FIX(1.061594337) */
#define FIX_1_272758580  (10426) /* FIX(1.272758580) */
#define FIX_1_451774981  (11893) /* FIX(1.451774981) */
#define FIX_1_847759065  (15137) /* FIX(1.847759065) */
#define FIX_2_172734803  (17799) /* FIX(2.172734803) */
#define FIX_2_562915447  (20995) /* FIX(2.562915447) */
#define FIX_3_624509785  (29692) /* FIX(3.624509785) */

.balign 16
jsimd_idct_4x4_neon_consts:
    .short     FIX_1_847759065     /* d0[0] */
    .short     -FIX_0_765366865    /* d0[1] */
    .short     -FIX_0_211164243    /* d0[2] */
    .short     FIX_1_451774981     /* d0[3] */
    .short     -FIX_2_172734803    /* d1[0] */
    .short     FIX_1_061594337     /* d1[1] */
    .short     -FIX_0_509795579    /* d1[2] */
    .short     -FIX_0_601344887    /* d1[3] */
    .short     FIX_0_899976223     /* d2[0] */
    .short     FIX_2_562915447     /* d2[1] */
    .short     1 << (CONST_BITS+1) /* d2[2] */
    .short     0                   /* d2[3] */

.macro idct_helper x4, x6, x8, x10, x12, x14, x16, shift, y26, y27, y28, y29
    vmull.s16       q14, \x4,  d2[2]
    vmlal.s16       q14, \x8,  d0[0]
    vmlal.s16       q14, \x14, d0[1]

    vmull.s16       q13, \x16, d1[2]
    vmlal.s16       q13, \x12, d1[3]
    vmlal.s16       q13, \x10, d2[0]
    vmlal.s16       q13, \x6,  d2[1]

    vmull.s16       q15, \x4,  d2[2]
    vmlsl.s16       q15, \x8,  d0[0]
    vmlsl.s16       q15, \x14, d0[1]

    vmull.s16       q12, \x16, d0[2]
    vmlal.s16       q12, \x12, d0[3]
    vmlal.s16       q12, \x10, d1[0]
    vmlal.s16       q12, \x6,  d1[1]

    vadd.s32        q10, q14, q13
    vsub.s32        q14, q14, q13

.if \shift > 16
    vrshr.s32       q10,  q10, #\shift
    vrshr.s32       q14,  q14, #\shift
    vmovn.s32       \y26, q10
    vmovn.s32       \y29, q14
.else
    vrshrn.s32      \y26, q10, #\shift
    vrshrn.s32      \y29, q14, #\shift
.endif

    vadd.s32        q10, q15, q12
    vsub.s32        q15, q15, q12

.if \shift > 16
    vrshr.s32       q10,  q10, #\shift
    vrshr.s32       q15,  q15, #\shift
    vmovn.s32       \y27, q10
    vmovn.s32       \y28, q15
.else
    vrshrn.s32      \y27, q10, #\shift
    vrshrn.s32      \y28, q15, #\shift
.endif

.endm

asm_function jsimd_idct_4x4_neon

    DCT_TABLE       .req r0
    COEF_BLOCK      .req r1
    OUTPUT_BUF      .req r2
    OUTPUT_COL      .req r3
    TMP1            .req r0
    TMP2            .req r1
    TMP3            .req r2
    TMP4            .req ip

    vpush           {d8-d15}

    /* Load constants (d3 is just used for padding) */
    adr             TMP4, jsimd_idct_4x4_neon_consts
    vld1.16         {d0, d1, d2, d3}, [TMP4, :128]

    /* Load all COEF_BLOCK into NEON registers with the following allocation:
     *       0 1 2 3 | 4 5 6 7
     *      ---------+--------
     *   0 | d4      | d5
     *   1 | d6      | d7
     *   2 | d8      | d9
     *   3 | d10     | d11
     *   4 | -       | -
     *   5 | d12     | d13
     *   6 | d14     | d15
     *   7 | d16     | d17
     */
    vld1.16         {d4, d5, d6, d7}, [COEF_BLOCK]!
    vld1.16         {d8, d9, d10, d11}, [COEF_BLOCK]!
    add COEF_BLOCK, COEF_BLOCK, #16
    vld1.16         {d12, d13, d14, d15}, [COEF_BLOCK]!
    vld1.16         {d16, d17}, [COEF_BLOCK]!
    /* dequantize */
    vld1.16         {d18, d19, d20, d21}, [DCT_TABLE]!
    vmul.s16        q2, q2, q9
    vld1.16         {d22, d23, d24, d25}, [DCT_TABLE]!
    vmul.s16        q3, q3, q10
    vmul.s16        q4, q4, q11
    add             DCT_TABLE, DCT_TABLE, #16
    vld1.16         {d26, d27, d28, d29}, [DCT_TABLE]!
    vmul.s16        q5, q5, q12
    vmul.s16        q6, q6, q13
    vld1.16         {d30, d31}, [DCT_TABLE]!
    vmul.s16        q7, q7, q14
    vmul.s16        q8, q8, q15


    /* Pass 1 */
    idct_helper     d4, d6, d8, d10, d12, d14, d16, 12, d4, d6, d8, d10
    transpose_4x4   d4, d6, d8, d10
    idct_helper     d5, d7, d9, d11, d13, d15, d17, 12, d5, d7, d9, d11
    transpose_4x4   d5, d7, d9, d11

    /* Pass 2 */
    idct_helper     d4, d6, d8, d10, d7, d9, d11, 19, d26, d27, d28, d29
    transpose_4x4   d26, d27, d28, d29

    /* Range limit */
    vmov.u16        q15, #0x80
    vadd.s16        q13, q13, q15
    vadd.s16        q14, q14, q15
    vqmovun.s16     d26, q13
    vqmovun.s16     d27, q14

    /* Store results to the output buffer */
    ldmia           OUTPUT_BUF, {TMP1, TMP2, TMP3, TMP4}
    add             TMP1, TMP1, OUTPUT_COL
    add             TMP2, TMP2, OUTPUT_COL
    add             TMP3, TMP3, OUTPUT_COL
    add             TMP4, TMP4, OUTPUT_COL

#if defined(__ARMEL__) && !RESPECT_STRICT_ALIGNMENT
    /* We can use much less instructions on little endian systems if the
     * OS kernel is not configured to trap unaligned memory accesses
     */
    vst1.32         {d26[0]}, [TMP1]!
    vst1.32         {d27[0]}, [TMP3]!
    vst1.32         {d26[1]}, [TMP2]!
    vst1.32         {d27[1]}, [TMP4]!
#else
    vst1.8          {d26[0]}, [TMP1]!
    vst1.8          {d27[0]}, [TMP3]!
    vst1.8          {d26[1]}, [TMP1]!
    vst1.8          {d27[1]}, [TMP3]!
    vst1.8          {d26[2]}, [TMP1]!
    vst1.8          {d27[2]}, [TMP3]!
    vst1.8          {d26[3]}, [TMP1]!
    vst1.8          {d27[3]}, [TMP3]!

    vst1.8          {d26[4]}, [TMP2]!
    vst1.8          {d27[4]}, [TMP4]!
    vst1.8          {d26[5]}, [TMP2]!
    vst1.8          {d27[5]}, [TMP4]!
    vst1.8          {d26[6]}, [TMP2]!
    vst1.8          {d27[6]}, [TMP4]!
    vst1.8          {d26[7]}, [TMP2]!
    vst1.8          {d27[7]}, [TMP4]!
#endif

    vpop            {d8-d15}
    bx              lr

    .unreq          DCT_TABLE
    .unreq          COEF_BLOCK
    .unreq          OUTPUT_BUF
    .unreq          OUTPUT_COL
    .unreq          TMP1
    .unreq          TMP2
    .unreq          TMP3
    .unreq          TMP4

.purgem idct_helper

/*****************************************************************************/

/*
 * jsimd_idct_2x2_neon
 *
 * This function contains inverse-DCT code for getting reduced-size
 * 2x2 pixels output from an 8x8 DCT block. It uses the same  calculations
 * and produces exactly the same output as IJG's original 'jpeg_idct_2x2'
 * function from jpeg-6b (jidctred.c).
 *
 * NOTE: jpeg-8 has an improved implementation of 2x2 inverse-DCT, which
 *       requires much less arithmetic operations and hence should be faster.
 *       The primary purpose of this particular NEON optimized function is
 *       bit exact compatibility with jpeg-6b.
 */

.balign 8
jsimd_idct_2x2_neon_consts:
    .short     -FIX_0_720959822    /* d0[0] */
    .short     FIX_0_850430095     /* d0[1] */
    .short     -FIX_1_272758580    /* d0[2] */
    .short     FIX_3_624509785     /* d0[3] */

.macro idct_helper x4, x6, x10, x12, x16, shift, y26, y27
    vshll.s16  q14,  \x4,  #15
    vmull.s16  q13,  \x6,  d0[3]
    vmlal.s16  q13,  \x10, d0[2]
    vmlal.s16  q13,  \x12, d0[1]
    vmlal.s16  q13,  \x16, d0[0]

    vadd.s32   q10,  q14,  q13
    vsub.s32   q14,  q14,  q13

.if \shift > 16
    vrshr.s32  q10,  q10,  #\shift
    vrshr.s32  q14,  q14,  #\shift
    vmovn.s32  \y26, q10
    vmovn.s32  \y27, q14
.else
    vrshrn.s32 \y26, q10,  #\shift
    vrshrn.s32 \y27, q14,  #\shift
.endif

.endm

asm_function jsimd_idct_2x2_neon

    DCT_TABLE       .req r0
    COEF_BLOCK      .req r1
    OUTPUT_BUF      .req r2
    OUTPUT_COL      .req r3
    TMP1            .req r0
    TMP2            .req ip

    vpush           {d8-d15}

    /* Load constants */
    adr             TMP2, jsimd_idct_2x2_neon_consts
    vld1.16         {d0}, [TMP2, :64]

    /* Load all COEF_BLOCK into NEON registers with the following allocation:
     *       0 1 2 3 | 4 5 6 7
     *      ---------+--------
     *   0 | d4      | d5
     *   1 | d6      | d7
     *   2 | -       | -
     *   3 | d10     | d11
     *   4 | -       | -
     *   5 | d12     | d13
     *   6 | -       | -
     *   7 | d16     | d17
     */

    vld1.16         {d4, d5, d6, d7}, [COEF_BLOCK]!
    add             COEF_BLOCK, COEF_BLOCK, #16
    vld1.16         {d10, d11}, [COEF_BLOCK]!
    add             COEF_BLOCK, COEF_BLOCK, #16
    vld1.16         {d12, d13}, [COEF_BLOCK]!
    add             COEF_BLOCK, COEF_BLOCK, #16
    vld1.16         {d16, d17}, [COEF_BLOCK]!
    /* Dequantize */
    vld1.16         {d18, d19, d20, d21}, [DCT_TABLE]!
    vmul.s16        q2, q2, q9
    vmul.s16        q3, q3, q10
    add             DCT_TABLE, DCT_TABLE, #16
    vld1.16         {d24, d25}, [DCT_TABLE]!
    vmul.s16        q5, q5, q12
    add             DCT_TABLE, DCT_TABLE, #16
    vld1.16         {d26, d27}, [DCT_TABLE]!
    vmul.s16        q6, q6, q13
    add             DCT_TABLE, DCT_TABLE, #16
    vld1.16         {d30, d31}, [DCT_TABLE]!
    vmul.s16        q8, q8, q15

    /* Pass 1 */
    vmull.s16       q13, d6,  d0[3]
    vmlal.s16       q13, d10, d0[2]
    vmlal.s16       q13, d12, d0[1]
    vmlal.s16       q13, d16, d0[0]
    vmull.s16       q12, d7,  d0[3]
    vmlal.s16       q12, d11, d0[2]
    vmlal.s16       q12, d13, d0[1]
    vmlal.s16       q12, d17, d0[0]
    vshll.s16       q14, d4,  #15
    vshll.s16       q15, d5,  #15
    vadd.s32        q10, q14, q13
    vsub.s32        q14, q14, q13
    vrshrn.s32      d4,  q10, #13
    vrshrn.s32      d6,  q14, #13
    vadd.s32        q10, q15, q12
    vsub.s32        q14, q15, q12
    vrshrn.s32      d5,  q10, #13
    vrshrn.s32      d7,  q14, #13
    vtrn.16         q2,  q3
    vtrn.32         q3,  q5

    /* Pass 2 */
    idct_helper     d4, d6, d10, d7, d11, 20, d26, d27

    /* Range limit */
    vmov.u16        q15, #0x80
    vadd.s16        q13, q13, q15
    vqmovun.s16     d26, q13
    vqmovun.s16     d27, q13

    /* Store results to the output buffer */
    ldmia           OUTPUT_BUF, {TMP1, TMP2}
    add             TMP1, TMP1, OUTPUT_COL
    add             TMP2, TMP2, OUTPUT_COL

    vst1.8          {d26[0]}, [TMP1]!
    vst1.8          {d27[4]}, [TMP1]!
    vst1.8          {d26[1]}, [TMP2]!
    vst1.8          {d27[5]}, [TMP2]!

    vpop            {d8-d15}
    bx              lr

    .unreq          DCT_TABLE
    .unreq          COEF_BLOCK
    .unreq          OUTPUT_BUF
    .unreq          OUTPUT_COL
    .unreq          TMP1
    .unreq          TMP2

.purgem idct_helper

/*****************************************************************************/

/*
 * jsimd_ycc_rgba8888_convert_neon
 * jsimd_ycc_rgb565_convert_neon
 * Colorspace conversion YCbCr -> RGB
 */


.macro do_load size
    .if \size == 8
        vld1.8  {d4}, [U]!
        vld1.8  {d5}, [V]!
        vld1.8  {d0}, [Y]!
        pld     [Y, #64]
        pld     [U, #64]
        pld     [V, #64]
    .elseif \size == 4
        vld1.8  {d4[0]}, [U]!
        vld1.8  {d4[1]}, [U]!
        vld1.8  {d4[2]}, [U]!
        vld1.8  {d4[3]}, [U]!
        vld1.8  {d5[0]}, [V]!
        vld1.8  {d5[1]}, [V]!
        vld1.8  {d5[2]}, [V]!
        vld1.8  {d5[3]}, [V]!
        vld1.8  {d0[0]}, [Y]!
        vld1.8  {d0[1]}, [Y]!
        vld1.8  {d0[2]}, [Y]!
        vld1.8  {d0[3]}, [Y]!
    .elseif \size == 2
        vld1.8  {d4[4]}, [U]!
        vld1.8  {d4[5]}, [U]!
        vld1.8  {d5[4]}, [V]!
        vld1.8  {d5[5]}, [V]!
        vld1.8  {d0[4]}, [Y]!
        vld1.8  {d0[5]}, [Y]!
    .elseif \size == 1
        vld1.8  {d4[6]}, [U]!
        vld1.8  {d5[6]}, [V]!
        vld1.8  {d0[6]}, [Y]!
    .else
        .error unsupported macroblock size
    .endif
.endm





.macro do_store bpp, size
    .if \bpp == 16
            /* if 16 bits, pack into RGB565 format */
            vmov      d27, d10          /* insert red channel */
            vsri.u8   d27, d11, #5      /* shift and insert the green channel */
            vsli.u8   d26, d11, #3
            vsri.u8   d26, d12, #3     /* shift and insert the blue channel */

        .if \size == 8
            vst2.8  {d26, d27}, [RGB]!
        .elseif \size == 4
            vst2.8  {d26[0], d27[0]}, [RGB]!
            vst2.8  {d26[1], d27[1]}, [RGB]!
            vst2.8  {d26[2], d27[2]}, [RGB]!
            vst2.8  {d26[3], d27[3]}, [RGB]!
        .elseif \size == 2
            vst2.8  {d26[4], d27[4]}, [RGB]!
            vst2.8  {d26[5], d27[5]}, [RGB]!
        .elseif \size == 1
            vst2.8  {d26[6], d27[6]}, [RGB]!
        .else
            .error unsupported macroblock size
        .endif
    .elseif \bpp == 24
        .if \size == 8
            vst3.8  {d10, d11, d12}, [RGB]!
        .elseif \size == 4
            vst3.8  {d10[0], d11[0], d12[0]}, [RGB]!
            vst3.8  {d10[1], d11[1], d12[1]}, [RGB]!
            vst3.8  {d10[2], d11[2], d12[2]}, [RGB]!
            vst3.8  {d10[3], d11[3], d12[3]}, [RGB]!
        .elseif \size == 2
            vst3.8  {d10[4], d11[4], d12[4]}, [RGB]!
            vst3.8  {d10[5], d11[5], d12[5]}, [RGB]!
        .elseif \size == 1
            vst3.8  {d10[6], d11[6], d12[6]}, [RGB]!
        .else
            .error unsupported macroblock size
        .endif
    .elseif \bpp == 32
        .if \size == 8
            vst4.8  {d10, d11, d12, d13}, [RGB]!
        .elseif \size == 4
            vst4.8  {d10[0], d11[0], d12[0], d13[0]}, [RGB]!
            vst4.8  {d10[1], d11[1], d12[1], d13[1]}, [RGB]!
            vst4.8  {d10[2], d11[2], d12[2], d13[2]}, [RGB]!
            vst4.8  {d10[3], d11[3], d12[3], d13[3]}, [RGB]!
        .elseif \size == 2
            vst4.8  {d10[4], d11[4], d12[4], d13[4]}, [RGB]!
            vst4.8  {d10[5], d11[5], d12[5], d13[5]}, [RGB]!
        .elseif \size == 1
            vst4.8  {d10[6], d11[6], d12[6], d13[6]}, [RGB]!
        .else
            .error unsupported macroblock size
        .endif
    .else
        .error unsupported bpp
    .endif
.endm

.macro generate_jsimd_ycc_rgb_convert_neon colorid, bpp, r_offs, g_offs, b_offs

.macro do_yuv_to_rgb
    vaddw.u8        q3, q1, d4     /* q3 = u - 128 */
    vaddw.u8        q4, q1, d5     /* q2 = v - 128 */
    vmull.s16       q10, d6, d1[1] /* multiply by -11277 */
    vmlal.s16       q10, d8, d1[2] /* multiply by -23401 */
    vmull.s16       q11, d7, d1[1] /* multiply by -11277 */
    vmlal.s16       q11, d9, d1[2] /* multiply by -23401 */
    vmull.s16       q12, d8, d1[0] /* multiply by 22971 */
    vmull.s16       q13, d9, d1[0] /* multiply by 22971 */
    vmull.s16       q14, d6, d1[3] /* multiply by 29033 */
    vmull.s16       q15, d7, d1[3] /* multiply by 29033 */
    vrshrn.s32      d20, q10, #15
    vrshrn.s32      d21, q11, #15
    vrshrn.s32      d24, q12, #14
    vrshrn.s32      d25, q13, #14
    vrshrn.s32      d28, q14, #14
    vrshrn.s32      d29, q15, #14
    vaddw.u8        q10, q10, d0
    vaddw.u8        q12, q12, d0
    vaddw.u8        q14, q14, d0
    vqmovun.s16     d1\g_offs, q10
    vqmovun.s16     d1\r_offs, q12
    vqmovun.s16     d1\b_offs, q14
.endm

/* Apple gas crashes on adrl, work around that by using adr.
 * But this requires a copy of these constants for each function.
 */

.balign 16
jsimd_ycc_\colorid\()_neon_consts:
    .short          0,      0,     0,      0
    .short          22971, -11277, -23401, 29033
    .short          -128,  -128,   -128,   -128
    .short          -128,  -128,   -128,   -128

asm_function jsimd_ycc_\colorid\()_convert_neon
    OUTPUT_WIDTH    .req r0
    INPUT_BUF       .req r1
    INPUT_ROW       .req r2
    OUTPUT_BUF      .req r3
    NUM_ROWS        .req r4

    INPUT_BUF0      .req r5
    INPUT_BUF1      .req r6
    INPUT_BUF2      .req INPUT_BUF

    RGB             .req r7
    Y               .req r8
    U               .req r9
    V               .req r10
    N               .req ip

    /* Load constants to d1, d2, d3 (d0 is just used for padding) */
    adr             ip, jsimd_ycc_\colorid\()_neon_consts
    vld1.16         {d0, d1, d2, d3}, [ip, :128]

    /* Save ARM registers and handle input arguments */
    push            {r4, r5, r6, r7, r8, r9, r10, lr}
    ldr             NUM_ROWS, [sp, #(4 * 8)]
    ldr             INPUT_BUF0, [INPUT_BUF]
    ldr             INPUT_BUF1, [INPUT_BUF, #4]
    ldr             INPUT_BUF2, [INPUT_BUF, #8]
    .unreq          INPUT_BUF

    /* Save NEON registers */
    vpush           {d8-d15}

    /* Initially set d10, d11, d12, d13 to 0xFF */
    vmov.u8         q5, #255
    vmov.u8         q6, #255

    /* Outer loop over scanlines */
    cmp             NUM_ROWS, #1
    blt             9f
0:
    ldr             Y, [INPUT_BUF0, INPUT_ROW, lsl #2]
    ldr             U, [INPUT_BUF1, INPUT_ROW, lsl #2]
    mov             N, OUTPUT_WIDTH
    ldr             V, [INPUT_BUF2, INPUT_ROW, lsl #2]
    add             INPUT_ROW, INPUT_ROW, #1
    ldr             RGB, [OUTPUT_BUF], #4

    /* Inner loop over pixels */
    subs            N, N, #8
    blt             2f
1:
    do_load         8
    do_yuv_to_rgb
    do_store        \bpp, 8
    subs            N, N, #8
    bge             1b
    tst             N, #7
    beq             8f
2:
    tst             N, #4
    beq             3f
    do_load         4
3:
    tst             N, #2
    beq             4f
    do_load         2
4:
    tst             N, #1
    beq             5f
    do_load         1
5:
    do_yuv_to_rgb
    tst             N, #4
    beq             6f
    do_store        \bpp, 4
6:
    tst             N, #2
    beq             7f
    do_store        \bpp, 2
7:
    tst             N, #1
    beq             8f
    do_store        \bpp, 1
8:
    subs            NUM_ROWS, NUM_ROWS, #1
    bgt             0b
9:
    /* Restore all registers and return */
    vpop            {d8-d15}
    pop             {r4, r5, r6, r7, r8, r9, r10, pc}

    .unreq          OUTPUT_WIDTH
    .unreq          INPUT_ROW
    .unreq          OUTPUT_BUF
    .unreq          NUM_ROWS
    .unreq          INPUT_BUF0
    .unreq          INPUT_BUF1
    .unreq          INPUT_BUF2
    .unreq          RGB
    .unreq          Y
    .unreq          U
    .unreq          V
    .unreq          N

.purgem do_yuv_to_rgb

.endm

/*--------------------------------- id ----- bpp R  G  B */
generate_jsimd_ycc_rgb_convert_neon rgba8888, 32, 0, 1, 2
generate_jsimd_ycc_rgb_convert_neon rgb565,  16, 0, 1, 2


.purgem do_load
.purgem do_store

/*****************************************************************************/