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
|
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/TextureDecoder.h"
#include <arm_neon.h>
#include "Common/CommonTypes.h"
#include "Common/Inline.h"
#include "Common/Swap.h"
template <typename T>
struct RGBA
{
T r;
T g;
T b;
T a;
};
using RGBAx8 = RGBA<uint8x8_t>;
using RGBAx16 = RGBA<uint8x16_t>;
static DOLPHIN_FORCE_INLINE void Store8x2RGBA(u32* dst, int x, int y, int width, RGBAx16 rgba)
{
// Store 2 rows containing 8 texels (32 bytes) each, for a total of 16 texels (64 bytes).
uint16x8_t rg[2], ba[2];
rg[0] = vreinterpretq_u16_u8(vzip1q_u8(rgba.r, rgba.g));
ba[0] = vreinterpretq_u16_u8(vzip1q_u8(rgba.b, rgba.a));
rg[1] = vreinterpretq_u16_u8(vzip2q_u8(rgba.r, rgba.g));
ba[1] = vreinterpretq_u16_u8(vzip2q_u8(rgba.b, rgba.a));
for (int iy = 0; iy < 2; ++iy)
{
uint16x8x2_t values{vzip1q_u16(rg[iy], ba[iy]), vzip2q_u16(rg[iy], ba[iy])};
vst1q_u16_x2(reinterpret_cast<u16*>(dst + (y + iy) * width + x), values);
}
}
static DOLPHIN_FORCE_INLINE void Store4x4RGBA(u32* dst, int x, int y, int width, RGBAx16 rgba)
{
// Store 4 rows containing 4 texels (16 bytes) each, for a total of 16 texels (64 bytes).
uint16x8_t rg_0 = vreinterpretq_u16_u8(vzip1q_u8(rgba.r, rgba.g));
uint16x8_t rg_1 = vreinterpretq_u16_u8(vzip2q_u8(rgba.r, rgba.g));
uint16x8_t ba_0 = vreinterpretq_u16_u8(vzip1q_u8(rgba.b, rgba.a));
uint16x8_t ba_1 = vreinterpretq_u16_u8(vzip2q_u8(rgba.b, rgba.a));
vst1q_u16(reinterpret_cast<u16*>(dst + (y + 0) * width + x), vzip1q_u16(rg_0, ba_0));
vst1q_u16(reinterpret_cast<u16*>(dst + (y + 1) * width + x), vzip2q_u16(rg_0, ba_0));
vst1q_u16(reinterpret_cast<u16*>(dst + (y + 2) * width + x), vzip1q_u16(rg_1, ba_1));
vst1q_u16(reinterpret_cast<u16*>(dst + (y + 3) * width + x), vzip2q_u16(rg_1, ba_1));
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_I4(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
for (int y = 0; y < height; y += 8)
{
for (int x = 0, yStep = (y / 8) * Wsteps8; x < width; x += 8, yStep++)
{
uint8x16x2_t i4 = vld1q_u8_x2(src + 32 * yStep);
for (int i = 0, iy = 0; i < 2; i++, iy += 4)
{
// TODO: vluti4q_laneq_u8 should be faster, but it requires an optional extension
uint8x16_t i8_even = vsriq_n_u8(i4.val[i], i4.val[i], 4);
uint8x16_t i8_odd = vsliq_n_u8(i4.val[i], i4.val[i], 4);
uint8x16x2_t i8 = vzipq_u8(i8_even, i8_odd);
Store8x2RGBA(dst, x, y + iy + 0, width, {i8.val[0], i8.val[0], i8.val[0], i8.val[0]});
Store8x2RGBA(dst, x, y + iy + 2, width, {i8.val[1], i8.val[1], i8.val[1], i8.val[1]});
}
}
}
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_I8(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps8; x < width; x += 8, yStep++)
{
uint8x16x2_t i8 = vld1q_u8_x2(src + 32 * yStep);
for (int i = 0, iy = 0; i < 2; i++, iy += 2)
Store8x2RGBA(dst, x, y + iy, width, {i8.val[i], i8.val[i], i8.val[i], i8.val[i]});
}
}
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_IA4(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps8; x < width; x += 8, yStep++)
{
uint8x16x2_t ia4 = vld1q_u8_x2(src + 32 * yStep);
for (int i = 0, iy = 0; i < 2; i++, iy += 2)
{
uint8x16_t a8 = vsriq_n_u8(ia4.val[i], ia4.val[i], 4);
uint8x16_t i8 = vsliq_n_u8(ia4.val[i], ia4.val[i], 4);
Store8x2RGBA(dst, x, y + iy + 0, width, {i8, i8, i8, a8});
}
}
}
}
static DOLPHIN_FORCE_INLINE RGBAx16 Decode_IA8(uint8x16x2_t ia)
{
return {ia.val[1], ia.val[1], ia.val[1], ia.val[0]};
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_IA8(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps4; x < width; x += 4, yStep++)
{
uint8x16x2_t ia = vld2q_u8(src + 32 * yStep);
Store4x4RGBA(dst, x, y, width, Decode_IA8(ia));
}
}
}
static DOLPHIN_FORCE_INLINE RGBAx8 Decode_RGB565(uint16x8_t rgb565, uint16x8_t rgb565_byteswapped)
{
// rgb565 contains gggbbbbb rrrrrggg, rgb565_byteswapped contains rrrrrggg gggbbbbb.
//
// We're assuming that rgb565_byteswapped is derived from rgb565 and that reading from rgb565
// therefore leads to a shorter critical path than reading from rgb565_byteswapped. If it wasn't
// for that, this function would only need rgb565_byteswapped as a parameter.
uint8x8_t r5 = vmovn_u16(rgb565);
uint8x8_t r8 = vsri_n_u8(r5, r5, 5);
uint8x8_t g6 = vshrn_n_u16(rgb565_byteswapped, 3);
uint8x8_t g8 = vsri_n_u8(g6, g6, 6);
uint8x8_t b5 = vshrn_n_u16(rgb565, 5);
uint8x8_t b8 = vsri_n_u8(b5, b5, 5);
uint8x8_t a8 = vdup_n_u8(0xFF);
return {r8, g8, b8, a8};
}
static DOLPHIN_FORCE_INLINE RGBAx16 Decode_RGB565(uint8x16x2_t rgb565)
{
// rgb565.val[0] contains rrrrrggg, rgb565.val[1] contains gggbbbbb.
uint8x16_t r5 = rgb565.val[0];
uint8x16_t r8 = vsriq_n_u8(r5, r5, 5);
uint8x16_t g6 = vsriq_n_u8(vshlq_n_u8(rgb565.val[0], 5), rgb565.val[1], 3);
uint8x16_t g8 = vsriq_n_u8(g6, g6, 6);
uint8x16_t b5 = vshlq_n_u8(rgb565.val[1], 3);
uint8x16_t b8 = vsriq_n_u8(b5, b5, 5);
uint8x16_t a8 = vdupq_n_u8(0xFF);
return {r8, g8, b8, a8};
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_RGB565(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps4; x < width; x += 4, yStep++)
{
uint8x16x2_t rgb565 = vld2q_u8(src + 32 * yStep);
Store4x4RGBA(dst, x, y, width, Decode_RGB565(rgb565));
}
}
}
static DOLPHIN_FORCE_INLINE RGBAx16 Decode_RGB5A3(uint8x16x2_t rgb5a3)
{
// Case with alpha: rgb5a3.val[0] contains 0aaarrrr, rgb5a3.val[1] contains ggggbbbb
// Case without alpha: rgb5a3.val[0] contains 1rrrrrgg, rgb5a3.val[1] contains gggbbbbb
uint8x16_t r8_with_alpha = vsliq_n_u8(rgb5a3.val[0], rgb5a3.val[0], 4);
uint8x16_t g8_with_alpha = vsriq_n_u8(rgb5a3.val[1], rgb5a3.val[1], 4);
uint8x16_t b8_with_alpha = vsliq_n_u8(rgb5a3.val[1], rgb5a3.val[1], 4);
uint8x16_t a3_with_alpha = vshlq_n_u8(rgb5a3.val[0], 1);
uint8x16_t a6_with_alpha = vsriq_n_u8(a3_with_alpha, a3_with_alpha, 3);
uint8x16_t a8_with_alpha = vsriq_n_u8(a6_with_alpha, a6_with_alpha, 6);
uint8x16_t r5_without_alpha = vshlq_n_u8(rgb5a3.val[0], 1);
uint8x16_t r8_without_alpha = vsriq_n_u8(r5_without_alpha, r5_without_alpha, 5);
uint8x16_t g5_without_alpha = vsriq_n_u8(vshlq_n_u8(rgb5a3.val[0], 6), rgb5a3.val[1], 2);
uint8x16_t g8_without_alpha = vsriq_n_u8(g5_without_alpha, g5_without_alpha, 5);
uint8x16_t b5_without_alpha = vshlq_n_u8(rgb5a3.val[1], 3);
uint8x16_t b8_without_alpha = vsriq_n_u8(b5_without_alpha, b5_without_alpha, 5);
uint8x16_t is_without_alpha = vcltzq_s8(vreinterpretq_s8_u8(rgb5a3.val[0]));
uint8x16_t r8 = vbslq_u8(is_without_alpha, r8_without_alpha, r8_with_alpha);
uint8x16_t g8 = vbslq_u8(is_without_alpha, g8_without_alpha, g8_with_alpha);
uint8x16_t b8 = vbslq_u8(is_without_alpha, b8_without_alpha, b8_with_alpha);
uint8x16_t a8 = vorrq_u8(is_without_alpha, a8_with_alpha);
return {r8, g8, b8, a8};
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_RGB5A3(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps4; x < width; x += 4, yStep++)
{
uint8x16x2_t rgb5a3 = vld2q_u8(src + 32 * yStep);
Store4x4RGBA(dst, x, y, width, Decode_RGB5A3(rgb5a3));
}
}
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_RGBA8(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps4; x < width; x += 4, yStep++)
{
// The input texels are split up into AR and GB components where all AR components come
// grouped up first in 32 bytes followed by the GB components in 32 bytes.
uint8x16x2_t ar = vld2q_u8(src + 64 * yStep);
uint8x16x2_t gb = vld2q_u8(src + 64 * yStep + 32);
Store4x4RGBA(dst, x, y, width, {ar.val[1], gb.val[0], gb.val[1], ar.val[0]});
}
}
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_C4(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8,
RGBAx16 palette)
{
uint8x16_t low_mask = vdupq_n_u8(0x0F);
for (int y = 0; y < height; y += 8)
{
for (int x = 0, yStep = (y / 8) * Wsteps8; x < width; x += 8, yStep++)
{
uint8x16x2_t c4 = vld1q_u8_x2(src + 32 * yStep);
for (int i = 0, iy = 0; i < 2; i++, iy += 4)
{
// TODO: vluti4q_laneq_u8 should be faster, but it requires an optional extension
uint8x16_t c8_even = vshrq_n_u8(c4.val[i], 4);
uint8x16_t c8_odd = vandq_u8(c4.val[i], low_mask);
uint8x16x2_t c8 = vzipq_u8(c8_even, c8_odd);
RGBAx16 rgba_0 = {vqtbl1q_u8(palette.r, c8.val[0]), vqtbl1q_u8(palette.g, c8.val[0]),
vqtbl1q_u8(palette.b, c8.val[0]), vqtbl1q_u8(palette.a, c8.val[0])};
RGBAx16 rgba_1 = {vqtbl1q_u8(palette.r, c8.val[1]), vqtbl1q_u8(palette.g, c8.val[1]),
vqtbl1q_u8(palette.b, c8.val[1]), vqtbl1q_u8(palette.a, c8.val[1])};
Store8x2RGBA(dst, x, y + iy + 0, width, rgba_0);
Store8x2RGBA(dst, x, y + iy + 2, width, rgba_1);
}
}
}
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_C8(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8,
const u8* tlut,
RGBAx16 (*decoder_function)(uint8x16x2_t))
{
// The current approach is about twice as fast as the TBL+TBX+TBX+TBX approach
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps8; x < width; x += 8, yStep++)
{
for (int iy = 0, xStep = 4 * yStep; iy < 4; iy += 2, xStep += 2)
{
const u8* src2 = src + 8 * xStep;
const u16* tlut2 = reinterpret_cast<const u16*>(tlut);
const auto read_four_texels = [](const u16* tlut_, const u8* src_) {
// No gather instruction, so we have to read each value one by one...
// TODO: Newer CPUs could use SVE gather instructions
u16 a = tlut_[src_[0]];
u16 b = tlut_[src_[1]];
u16 c = tlut_[src_[2]];
u16 d = tlut_[src_[3]];
u32 ab = (static_cast<u32>(b) << 16) | a;
u32 cd = (static_cast<u32>(d) << 16) | c;
return (static_cast<u64>(cd) << 32) | ab;
};
// We use the biggest possible GPR -> FPR moves, which are 64-bit
uint64x1_t a = vdup_n_u64(read_four_texels(tlut2, src2 + 0));
uint64x1_t b = vdup_n_u64(read_four_texels(tlut2, src2 + 4));
uint64x1_t c = vdup_n_u64(read_four_texels(tlut2, src2 + 8));
uint64x1_t d = vdup_n_u64(read_four_texels(tlut2, src2 + 12));
uint64x2_t ab = vcombine_u64(a, b);
uint64x2_t cd = vcombine_u64(c, d);
uint8x16x2_t abcd = vuzpq_u8(vreinterpretq_u8_u64(ab), vreinterpretq_u8_u64(cd));
Store8x2RGBA(dst, x, y + iy + 0, width, decoder_function(abcd));
}
}
}
}
template <bool VectorLoads>
static DOLPHIN_FORCE_INLINE void
TexDecoder_DecodeImpl_C14X2(u32* dst, const u8* src, int width, int height, int Wsteps4,
int Wsteps8, const u8* tlut, RGBAx16 (*decoder_function)(uint8x16x2_t))
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0, yStep = (y / 4) * Wsteps4; x < width; x += 4, yStep++)
{
const u16* src2 = reinterpret_cast<const u16*>(src + 32 * yStep);
const u16* tlut2 = reinterpret_cast<const u16*>(tlut);
// For C14X2 specifically: Reading src with vector loads is slightly faster for IA8 and RGB565
// but significantly slower for RGB5A3 (tested on Snapdragon 6 Gen 3), so we let the caller
// choose whether to use vector loads. (For C8, src vector loads are slower in all cases.)
uint64x1_t a, b, c, d;
if constexpr (VectorLoads)
{
uint16x8x2_t tlut_offsets = vld1q_u16_x2(src2);
for (uint16x8_t& vec : tlut_offsets.val)
vec = vreinterpretq_u16_u8(vrev16q_u8(vreinterpretq_u8_u16(vec)));
constexpr auto read_four_texels = [](const u16* tlut_, u64 offsets) {
// No gather instruction, so we have to read each value one by one...
// TODO: Newer CPUs could use SVE gather instructions
u16 a_ = tlut_[(offsets >> 0) & 0x3FFF];
u16 b_ = tlut_[(offsets >> 16) & 0x3FFF];
u16 c_ = tlut_[(offsets >> 32) & 0x3FFF];
u16 d_ = tlut_[(offsets >> 48) & 0x3FFF];
u32 ab = (static_cast<u32>(b_) << 16) | a_;
u32 cd = (static_cast<u32>(d_) << 16) | c_;
return (static_cast<u64>(cd) << 32) | ab;
};
// We use the biggest possible GPR <-> FPR moves, which are 64-bit
a = vdup_n_u64(
read_four_texels(tlut2, vgetq_lane_u64(vreinterpretq_u64_u16(tlut_offsets.val[0]), 0)));
b = vdup_n_u64(
read_four_texels(tlut2, vgetq_lane_u64(vreinterpretq_u64_u16(tlut_offsets.val[0]), 1)));
c = vdup_n_u64(
read_four_texels(tlut2, vgetq_lane_u64(vreinterpretq_u64_u16(tlut_offsets.val[1]), 0)));
d = vdup_n_u64(
read_four_texels(tlut2, vgetq_lane_u64(vreinterpretq_u64_u16(tlut_offsets.val[1]), 1)));
}
else
{
const auto read_four_texels = [](const u16* tlut_, const u16* src_) {
u16 a_ = tlut_[Common::swap16(src_[0]) & 0x3FFF];
u16 b_ = tlut_[Common::swap16(src_[1]) & 0x3FFF];
u16 c_ = tlut_[Common::swap16(src_[2]) & 0x3FFF];
u16 d_ = tlut_[Common::swap16(src_[3]) & 0x3FFF];
u32 ab = (static_cast<u32>(b_) << 16) | a_;
u32 cd = (static_cast<u32>(d_) << 16) | c_;
return (static_cast<u64>(cd) << 32) | ab;
};
// We use the biggest possible GPR -> FPR moves, which are 64-bit
a = vdup_n_u64(read_four_texels(tlut2, src2 + 0));
b = vdup_n_u64(read_four_texels(tlut2, src2 + 4));
c = vdup_n_u64(read_four_texels(tlut2, src2 + 8));
d = vdup_n_u64(read_four_texels(tlut2, src2 + 12));
}
uint64x2_t ab = vcombine_u64(a, b);
uint64x2_t cd = vcombine_u64(c, d);
uint8x16x2_t abcd = vuzpq_u8(vreinterpretq_u8_u64(ab), vreinterpretq_u8_u64(cd));
Store4x4RGBA(dst, x, y, width, decoder_function(abcd));
}
}
}
static DOLPHIN_FORCE_INLINE void TexDecoder_DecodeImpl_CMPR(u32* dst, const u8* src, int width,
int height, int Wsteps4, int Wsteps8)
{
uint8x8_t dxt_blend_factors_0 = vreinterpret_u8_u16(vdup_n_u16(0x0305));
uint8x8_t dxt_blend_factors_1 = vreinterpret_u8_u16(vdup_n_u16(0x0503));
uint8x16_t table_offsets_0_1 = vreinterpretq_u8_u64(vdupq_n_u64(0x0404040400000000));
uint8x16_t table_offsets_2_3 = vreinterpretq_u8_u64(vdupq_n_u64(0x0C0C0C0C08080808));
for (int y = 0; y < height; y += 8)
{
for (int x = 0, yStep = (y / 8) * Wsteps8; x < width; x += 8, yStep++)
{
// Load four blocks at once
uint32x4x2_t values = vld2q_u32(reinterpret_cast<const u32*>(src + 32 * yStep));
uint16x8_t rgb565 = vreinterpretq_u16_u32(values.val[0]);
uint8x16_t c2 = vreinterpretq_u8_u32(values.val[1]);
// Unpack colors 0 and 1
uint16x8_t rgb565_byteswapped =
vreinterpretq_u16_u8(vrev16q_u8(vreinterpretq_u8_u16(rgb565)));
RGBAx8 color_0_1 = Decode_RGB565(rgb565, rgb565_byteswapped);
// Calculate colors 2 and 3
const auto dxt_blend = [](uint8x8_t input, uint8x8_t factors) {
return vshrn_n_u16(vpaddq_u16(vmull_u8(input, factors), vdupq_n_u16(0)), 3);
};
const auto average_blend = [](uint8x8_t input) {
return vshrn_n_u16(vcombine_u16(vpaddl_u8(input), vdup_n_u16(0)), 1);
};
uint8x8_t first_case_r_2 = dxt_blend(color_0_1.r, dxt_blend_factors_0);
uint8x8_t first_case_g_2 = dxt_blend(color_0_1.g, dxt_blend_factors_0);
uint8x8_t first_case_b_2 = dxt_blend(color_0_1.b, dxt_blend_factors_0);
uint8x8_t first_case_r_3 = dxt_blend(color_0_1.r, dxt_blend_factors_1);
uint8x8_t first_case_g_3 = dxt_blend(color_0_1.g, dxt_blend_factors_1);
uint8x8_t first_case_b_3 = dxt_blend(color_0_1.b, dxt_blend_factors_1);
uint8x8_t first_case_r_2_3 = vzip1_u8(first_case_r_2, first_case_r_3);
uint8x8_t first_case_g_2_3 = vzip1_u8(first_case_g_2, first_case_g_3);
uint8x8_t first_case_b_2_3 = vzip1_u8(first_case_b_2, first_case_b_3);
uint8x8_t second_case_r_2 = average_blend(color_0_1.r);
uint8x8_t second_case_g_2 = average_blend(color_0_1.g);
uint8x8_t second_case_b_2 = average_blend(color_0_1.b);
uint8x8_t second_case_r_2_3 = vzip1_u8(second_case_r_2, second_case_r_2);
uint8x8_t second_case_g_2_3 = vzip1_u8(second_case_g_2, second_case_g_2);
uint8x8_t second_case_b_2_3 = vzip1_u8(second_case_b_2, second_case_b_2);
uint16x4_t rgb565_0 = vget_low_u16(vuzp1q_u16(rgb565_byteswapped, vdupq_n_u16(0)));
uint16x4_t rgb565_1 = vget_low_u16(vuzp2q_u16(rgb565_byteswapped, vdupq_n_u16(0)));
uint16x4_t color_0_is_bigger = vcgt_u16(rgb565_0, rgb565_1);
uint8x8_t r_2_3 =
vbsl_u8(vreinterpret_u8_u16(color_0_is_bigger), first_case_r_2_3, second_case_r_2_3);
uint8x8_t g_2_3 =
vbsl_u8(vreinterpret_u8_u16(color_0_is_bigger), first_case_g_2_3, second_case_g_2_3);
uint8x8_t b_2_3 =
vbsl_u8(vreinterpret_u8_u16(color_0_is_bigger), first_case_b_2_3, second_case_b_2_3);
// a_2 is always FF, a_3 is FF iff color 0 is bigger
uint8x8_t a_2_3 = vreinterpret_u8_u16(vorr_u16(color_0_is_bigger, vdup_n_u16(0x00FF)));
// Put all four colors together
const auto zip_colors = [](uint8x8_t color_0_1_, uint8x8_t color_2_3_) {
return vreinterpretq_u8_u16(
vzip1q_u16(vreinterpretq_u16_u8(vcombine_u8(color_0_1_, vdup_n_u8(0))),
vreinterpretq_u16_u8(vcombine_u8(color_2_3_, vdup_n_u8(0)))));
};
RGBAx16 color{
zip_colors(color_0_1.r, r_2_3),
zip_colors(color_0_1.g, g_2_3),
zip_colors(color_0_1.b, b_2_3),
zip_colors(color_0_1.a, a_2_3),
};
// Unpack the indices so each one takes up one byte
// TODO: vluti2q_laneq_u8 should be faster, but it requires an optional extension
uint8x16x4_t c8_interleaved;
c8_interleaved.val[0] = vshrq_n_u8(c2, 6);
c8_interleaved.val[1] = vandq_u8(vshrq_n_u8(c2, 4), vdupq_n_u8(0x03));
c8_interleaved.val[2] = vandq_u8(vshrq_n_u8(c2, 2), vdupq_n_u8(0x03));
c8_interleaved.val[3] = vandq_u8(c2, vdupq_n_u8(0x03));
uint16x8x4_t c8_partially_interleaved;
c8_partially_interleaved.val[0] =
vreinterpretq_u16_u8(vzip1q_u8(c8_interleaved.val[0], c8_interleaved.val[1]));
c8_partially_interleaved.val[1] =
vreinterpretq_u16_u8(vzip2q_u8(c8_interleaved.val[0], c8_interleaved.val[1]));
c8_partially_interleaved.val[2] =
vreinterpretq_u16_u8(vzip1q_u8(c8_interleaved.val[2], c8_interleaved.val[3]));
c8_partially_interleaved.val[3] =
vreinterpretq_u16_u8(vzip2q_u8(c8_interleaved.val[2], c8_interleaved.val[3]));
uint32x4x4_t c8;
c8.val[0] = vreinterpretq_u32_u16(
vzip1q_u16(c8_partially_interleaved.val[0], c8_partially_interleaved.val[2]));
c8.val[1] = vreinterpretq_u32_u16(
vzip2q_u16(c8_partially_interleaved.val[0], c8_partially_interleaved.val[2]));
c8.val[2] = vreinterpretq_u32_u16(
vzip1q_u16(c8_partially_interleaved.val[1], c8_partially_interleaved.val[3]));
c8.val[3] = vreinterpretq_u32_u16(
vzip2q_u16(c8_partially_interleaved.val[1], c8_partially_interleaved.val[3]));
// Rearrange the lines so every line from the first block is next to a line from the second
// block, and every line from the third block is next to a line from the fourth block.
// This lets us write 8 texels at a time to dst instead of just 4.
uint8x16x4_t c8_lines;
c8_lines.val[0] = vreinterpretq_u8_u32(vzip1q_u32(c8.val[0], c8.val[1]));
c8_lines.val[1] = vreinterpretq_u8_u32(vzip2q_u32(c8.val[0], c8.val[1]));
c8_lines.val[2] = vreinterpretq_u8_u32(vzip1q_u32(c8.val[2], c8.val[3]));
c8_lines.val[3] = vreinterpretq_u8_u32(vzip2q_u32(c8.val[2], c8.val[3]));
// Finally, do the table lookup and store the result
for (int i = 0; i < 4; ++i)
{
uint8x16_t table_indices =
vaddq_u8(c8_lines.val[i], i < 2 ? table_offsets_0_1 : table_offsets_2_3);
RGBAx16 rgba = {
vqtbl1q_u8(color.r, table_indices),
vqtbl1q_u8(color.g, table_indices),
vqtbl1q_u8(color.b, table_indices),
vqtbl1q_u8(color.a, table_indices),
};
Store8x2RGBA(dst, x, y + i * 2, width, rgba);
}
}
}
}
void _TexDecoder_DecodeImpl(u32* dst, const u8* src, int width, int height, TextureFormat texformat,
const u8* tlut, TLUTFormat tlutfmt)
{
const int Wsteps4 = (width + 3) / 4;
const int Wsteps8 = (width + 7) / 8;
switch (texformat)
{
case TextureFormat::I4:
TexDecoder_DecodeImpl_I4(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::I8:
TexDecoder_DecodeImpl_I8(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::IA4:
TexDecoder_DecodeImpl_IA4(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::IA8:
TexDecoder_DecodeImpl_IA8(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::RGB565:
TexDecoder_DecodeImpl_RGB565(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::RGB5A3:
TexDecoder_DecodeImpl_RGB5A3(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::RGBA8:
TexDecoder_DecodeImpl_RGBA8(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::C4:
{
RGBAx16 palette;
switch (tlutfmt)
{
default:
case TLUTFormat::IA8:
palette = Decode_IA8(vld2q_u8(tlut));
break;
case TLUTFormat::RGB565:
palette = Decode_RGB565(vld2q_u8(tlut));
break;
case TLUTFormat::RGB5A3:
palette = Decode_RGB5A3(vld2q_u8(tlut));
break;
}
TexDecoder_DecodeImpl_C4(dst, src, width, height, Wsteps4, Wsteps8, palette);
break;
}
case TextureFormat::C8:
switch (tlutfmt)
{
case TLUTFormat::IA8:
TexDecoder_DecodeImpl_C8(dst, src, width, height, Wsteps4, Wsteps8, tlut, Decode_IA8);
break;
case TLUTFormat::RGB565:
TexDecoder_DecodeImpl_C8(dst, src, width, height, Wsteps4, Wsteps8, tlut, Decode_RGB565);
break;
case TLUTFormat::RGB5A3:
TexDecoder_DecodeImpl_C8(dst, src, width, height, Wsteps4, Wsteps8, tlut, Decode_RGB5A3);
break;
}
break;
case TextureFormat::C14X2:
switch (tlutfmt)
{
case TLUTFormat::IA8:
TexDecoder_DecodeImpl_C14X2<true>(dst, src, width, height, Wsteps4, Wsteps8, tlut,
Decode_IA8);
break;
case TLUTFormat::RGB565:
TexDecoder_DecodeImpl_C14X2<true>(dst, src, width, height, Wsteps4, Wsteps8, tlut,
Decode_RGB565);
break;
case TLUTFormat::RGB5A3:
TexDecoder_DecodeImpl_C14X2<false>(dst, src, width, height, Wsteps4, Wsteps8, tlut,
Decode_RGB5A3);
break;
}
break;
case TextureFormat::CMPR:
TexDecoder_DecodeImpl_CMPR(dst, src, width, height, Wsteps4, Wsteps8);
break;
case TextureFormat::XFB:
TexDecoder_DecodeXFB(reinterpret_cast<u8*>(dst), src, width, height, width * 2);
break;
}
}
|