summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2026-10-04 14:44:04 +0200
committerJosJuice <josjuice@gmail.com>2026-10-11 10:45:11 +0200
commit6103e4f5c6215ce57d855d99ab714fc0f52e6fc5 (patch)
tree31a984ca7e546e591c04ec064c759bac12c650b9 /Source/Core/VideoCommon
parentd57e268691e6520f776dde63f814754efaf4e2eb (diff)
VideoCommon: Add TextureDecoder_ARM64.cpp
We already have x64 SIMD implementations for decoding most texture formats. This commit adds AArch64 SIMD implementations for all texture formats except the XFB format. (AArch64 was previously using the non-SIMD implementations from TextureDecoder_Generic.cpp.) I left a few TODO comments about things that could be improved using optional ISA extensions. I don't have any immediate plans to work on these. Performance numbers from the unit test are available below. Note that the unit test fits relatively well in cache, so real workloads might spend more time waiting on memory, which could lead to smaller differences between the old and new implementation. Before: I4 took 2676 ns on average (36 iterations) I8 took 3961 ns on average (36 iterations) IA4 took 7304 ns on average (36 iterations) IA8 took 4555 ns on average (36 iterations) RGB565 took 12146 ns on average (36 iterations) RGB5A3 took 33155 ns on average (36 iterations) RGBA8 took 3702 ns on average (36 iterations) C4 (IA8) took 9149 ns on average (36 iterations) C4 (RGB565) took 19398 ns on average (36 iterations) C4 (RGB5A3) took 28533 ns on average (36 iterations) C8 (IA8) took 6766 ns on average (36 iterations) C8 (RGB565) took 15577 ns on average (36 iterations) C8 (RGB5A3) took 18906 ns on average (36 iterations) C14X2 (IA8) took 10603 ns on average (36 iterations) C14X2 (RGB565) took 20700 ns on average (36 iterations) C14X2 (RGB5A3) took 38878 ns on average (36 iterations) CMPR took 6205 ns on average (36 iterations) After: I4 took 2086 ns on average (36 iterations) I8 took 1699 ns on average (36 iterations) IA4 took 1522 ns on average (36 iterations) IA8 took 1673 ns on average (36 iterations) RGB565 took 2443 ns on average (36 iterations) RGB5A3 took 3922 ns on average (36 iterations) RGBA8 took 1983 ns on average (36 iterations) C4 (IA8) took 1646 ns on average (36 iterations) C4 (RGB565) took 2057 ns on average (36 iterations) C4 (RGB5A3) took 1670 ns on average (36 iterations) C8 (IA8) took 3765 ns on average (36 iterations) C8 (RGB565) took 4275 ns on average (36 iterations) C8 (RGB5A3) took 5146 ns on average (36 iterations) C14X2 (IA8) took 5121 ns on average (36 iterations) C14X2 (RGB565) took 5572 ns on average (36 iterations) C14X2 (RGB5A3) took 7142 ns on average (36 iterations) CMPR took 3250 ns on average (36 iterations)
Diffstat (limited to 'Source/Core/VideoCommon')
-rw-r--r--Source/Core/VideoCommon/CMakeLists.txt2
-rw-r--r--Source/Core/VideoCommon/TextureDecoder_ARM64.cpp594
2 files changed, 595 insertions, 1 deletions
diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt
index 6bbdcd9897..659dcf6912 100644
--- a/Source/Core/VideoCommon/CMakeLists.txt
+++ b/Source/Core/VideoCommon/CMakeLists.txt
@@ -256,9 +256,9 @@ if(_M_X86_64)
)
elseif(_M_ARM_64)
target_sources(videocommon PRIVATE
+ TextureDecoder_ARM64.cpp
VertexLoaderARM64.cpp
VertexLoaderARM64.h
- TextureDecoder_Generic.cpp
)
else()
target_sources(videocommon PRIVATE
diff --git a/Source/Core/VideoCommon/TextureDecoder_ARM64.cpp b/Source/Core/VideoCommon/TextureDecoder_ARM64.cpp
new file mode 100644
index 0000000000..3943611d78
--- /dev/null
+++ b/Source/Core/VideoCommon/TextureDecoder_ARM64.cpp
@@ -0,0 +1,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;
+ }
+}