Ver Fonte

Merge remote-tracking branch 'origin/master' into ssl

comex há 3 anos atrás
pai
commit
98685d48e3
100 ficheiros alterados com 12544 adições e 8859 exclusões
  1. 23 20
      .gitmodules
  2. 2 2
      CMakeLists.txt
  3. 394 279
      dist/languages/ca.ts
  4. 456 417
      dist/languages/cs.ts
  5. 573 439
      dist/languages/da.ts
  6. 216 175
      dist/languages/de.ts
  7. 282 167
      dist/languages/el.ts
  8. 385 270
      dist/languages/es.ts
  9. 455 415
      dist/languages/fr.ts
  10. 453 414
      dist/languages/id.ts
  11. 403 288
      dist/languages/it.ts
  12. 475 436
      dist/languages/ja_JP.ts
  13. 483 441
      dist/languages/ko_KR.ts
  14. 384 269
      dist/languages/nb.ts
  15. 387 272
      dist/languages/nl.ts
  16. 407 292
      dist/languages/pl.ts
  17. 403 288
      dist/languages/pt_BR.ts
  18. 474 435
      dist/languages/pt_PT.ts
  19. 396 281
      dist/languages/ru_RU.ts
  20. 452 413
      dist/languages/sv.ts
  21. 384 269
      dist/languages/tr_TR.ts
  22. 392 277
      dist/languages/uk.ts
  23. 470 431
      dist/languages/vi.ts
  24. 470 431
      dist/languages/vi_VN.ts
  25. 454 415
      dist/languages/zh_CN.ts
  26. 266 171
      dist/languages/zh_TW.ts
  27. 8 0
      externals/CMakeLists.txt
  28. 1 1
      externals/SDL
  29. 1522 0
      externals/bc_decoder/bc_decoder.cpp
  30. 43 0
      externals/bc_decoder/bc_decoder.h
  31. 2 2
      externals/nx_tzdb/CMakeLists.txt
  32. 1 1
      externals/nx_tzdb/tzdb_to_nx
  33. 1 0
      externals/vma/VulkanMemoryAllocator
  34. 8 0
      externals/vma/vma.cpp
  35. 2 2
      src/android/app/build.gradle.kts
  36. 4 1
      src/android/app/src/main/AndroidManifest.xml
  37. 16 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
  38. 63 2
      src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
  39. 1 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
  40. 101 71
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
  41. 62 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
  42. 1 5
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
  43. 1 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
  44. 86 36
      src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
  45. 8 26
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
  46. 7 16
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
  47. 2 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
  48. 59 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
  49. 0 1
      src/android/app/src/main/jni/CMakeLists.txt
  50. 124 122
      src/android/app/src/main/jni/native.cpp
  51. 0 165
      src/android/app/src/main/jni/native.h
  52. 9 0
      src/android/app/src/main/res/drawable/ic_pip_mute.xml
  53. 9 0
      src/android/app/src/main/res/drawable/ic_pip_unmute.xml
  54. 20 6
      src/android/app/src/main/res/values/strings.xml
  55. 3 0
      src/android/gradle.properties
  56. 5 3
      src/audio_core/device/audio_buffers.h
  57. 6 6
      src/audio_core/device/device_session.cpp
  58. 4 3
      src/audio_core/device/device_session.h
  59. 3 2
      src/audio_core/in/audio_in_system.cpp
  60. 2 2
      src/audio_core/out/audio_out_system.cpp
  61. 0 1
      src/audio_core/renderer/adsp/adsp.cpp
  62. 2 3
      src/audio_core/renderer/adsp/audio_renderer.cpp
  63. 0 1
      src/audio_core/renderer/adsp/command_list_processor.cpp
  64. 11 12
      src/audio_core/renderer/command/data_source/decode.cpp
  65. 4 4
      src/audio_core/renderer/command/effect/compressor.cpp
  66. 7 7
      src/audio_core/renderer/command/effect/delay.cpp
  67. 2 2
      src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
  68. 6 6
      src/audio_core/renderer/command/effect/light_limiter.cpp
  69. 6 6
      src/audio_core/renderer/command/effect/reverb.cpp
  70. 6 9
      src/audio_core/renderer/command/performance/performance.cpp
  71. 2 2
      src/audio_core/renderer/command/sink/circular_buffer.cpp
  72. 2 3
      src/audio_core/renderer/command/sink/device.cpp
  73. 3 3
      src/audio_core/renderer/mix/mix_context.cpp
  74. 2 2
      src/audio_core/renderer/nodes/node_states.cpp
  75. 1 1
      src/audio_core/renderer/nodes/node_states.h
  76. 1 0
      src/audio_core/renderer/system.cpp
  77. 1 1
      src/audio_core/sink/null_sink.h
  78. 8 8
      src/audio_core/sink/sink_stream.cpp
  79. 4 1
      src/audio_core/sink/sink_stream.h
  80. 2 0
      src/common/CMakeLists.txt
  81. 10 4
      src/common/fs/fs.cpp
  82. 1 1
      src/common/fs/fs_types.h
  83. 41 4
      src/common/input.h
  84. 2 1
      src/common/ring_buffer.h
  85. 9 0
      src/common/scratch_buffer.h
  86. 16 3
      src/common/settings.cpp
  87. 10 2
      src/common/settings.h
  88. 2 3
      src/common/steady_clock.cpp
  89. 1 0
      src/common/telemetry.cpp
  90. 1 1
      src/common/thread.h
  91. 32 45
      src/common/wall_clock.cpp
  92. 61 28
      src/common/wall_clock.h
  93. 4 0
      src/common/x64/cpu_detect.cpp
  94. 1 0
      src/common/x64/cpu_detect.h
  95. 38 32
      src/common/x64/cpu_wait.cpp
  96. 26 140
      src/common/x64/native_clock.cpp
  97. 20 39
      src/common/x64/native_clock.h
  98. 39 0
      src/common/x64/rdtsc.cpp
  99. 37 0
      src/common/x64/rdtsc.h
  100. 0 1
      src/core/CMakeLists.txt

+ 23 - 20
.gitmodules

@@ -2,35 +2,35 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 [submodule "enet"]
-    path = externals/enet
-    url = https://github.com/lsalzman/enet.git
+	path = externals/enet
+	url = https://github.com/lsalzman/enet.git
 [submodule "inih"]
-    path = externals/inih/inih
-    url = https://github.com/benhoyt/inih.git
+	path = externals/inih/inih
+	url = https://github.com/benhoyt/inih.git
 [submodule "cubeb"]
-    path = externals/cubeb
-    url = https://github.com/mozilla/cubeb.git
+	path = externals/cubeb
+	url = https://github.com/mozilla/cubeb.git
 [submodule "dynarmic"]
-    path = externals/dynarmic
-    url = https://github.com/MerryMage/dynarmic.git
+	path = externals/dynarmic
+	url = https://github.com/merryhime/dynarmic.git
 [submodule "libusb"]
 	path = externals/libusb/libusb
 	url = https://github.com/libusb/libusb.git
 [submodule "discord-rpc"]
-    path = externals/discord-rpc
-    url = https://github.com/yuzu-emu/discord-rpc.git
+	path = externals/discord-rpc
+	url = https://github.com/yuzu-emu/discord-rpc.git
 [submodule "Vulkan-Headers"]
-    path = externals/Vulkan-Headers
-    url = https://github.com/KhronosGroup/Vulkan-Headers.git
+	path = externals/Vulkan-Headers
+	url = https://github.com/KhronosGroup/Vulkan-Headers.git
 [submodule "sirit"]
-    path = externals/sirit
-    url = https://github.com/yuzu-emu/sirit
+	path = externals/sirit
+	url = https://github.com/yuzu-emu/sirit.git
 [submodule "mbedtls"]
-    path = externals/mbedtls
-    url = https://github.com/yuzu-emu/mbedtls
+	path = externals/mbedtls
+	url = https://github.com/yuzu-emu/mbedtls.git
 [submodule "xbyak"]
-    path = externals/xbyak
-    url = https://github.com/herumi/xbyak.git
+	path = externals/xbyak
+	url = https://github.com/herumi/xbyak.git
 [submodule "opus"]
 	path = externals/opus/opus
 	url = https://github.com/xiph/opus.git
@@ -45,13 +45,16 @@
 	url = https://github.com/FFmpeg/FFmpeg.git
 [submodule "vcpkg"]
 	path = externals/vcpkg
-	url = https://github.com/Microsoft/vcpkg.git
+	url = https://github.com/microsoft/vcpkg.git
 [submodule "cpp-jwt"]
 	path = externals/cpp-jwt
 	url = https://github.com/arun11299/cpp-jwt.git
 [submodule "libadrenotools"]
 	path = externals/libadrenotools
-	url = https://github.com/bylaws/libadrenotools
+	url = https://github.com/bylaws/libadrenotools.git
 [submodule "tzdb_to_nx"]
 	path = externals/nx_tzdb/tzdb_to_nx
 	url = https://github.com/lat9nq/tzdb_to_nx.git
+[submodule "VulkanMemoryAllocator"]
+	path = externals/vma/VulkanMemoryAllocator
+	url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git

+ 2 - 2
CMakeLists.txt

@@ -504,7 +504,7 @@ if (ENABLE_SDL2)
     if (YUZU_USE_BUNDLED_SDL2)
         # Detect toolchain and platform
         if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
-            set(SDL2_VER "SDL2-2.0.18")
+            set(SDL2_VER "SDL2-2.28.0")
         else()
             message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
         endif()
@@ -524,7 +524,7 @@ if (ENABLE_SDL2)
     elseif (YUZU_USE_EXTERNAL_SDL2)
         message(STATUS "Using SDL2 from externals.")
     else()
-        find_package(SDL2 2.0.18 REQUIRED)
+        find_package(SDL2 2.26.4 REQUIRED)
     endif()
 endif()
 

Diff do ficheiro suprimidas por serem muito extensas
+ 394 - 279
dist/languages/ca.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 456 - 417
dist/languages/cs.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 573 - 439
dist/languages/da.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 216 - 175
dist/languages/de.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 282 - 167
dist/languages/el.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 385 - 270
dist/languages/es.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 455 - 415
dist/languages/fr.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 453 - 414
dist/languages/id.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 403 - 288
dist/languages/it.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 475 - 436
dist/languages/ja_JP.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 483 - 441
dist/languages/ko_KR.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 384 - 269
dist/languages/nb.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 387 - 272
dist/languages/nl.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 407 - 292
dist/languages/pl.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 403 - 288
dist/languages/pt_BR.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 474 - 435
dist/languages/pt_PT.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 396 - 281
dist/languages/ru_RU.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 452 - 413
dist/languages/sv.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 384 - 269
dist/languages/tr_TR.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 392 - 277
dist/languages/uk.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 470 - 431
dist/languages/vi.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 470 - 431
dist/languages/vi_VN.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 454 - 415
dist/languages/zh_CN.ts


Diff do ficheiro suprimidas por serem muito extensas
+ 266 - 171
dist/languages/zh_TW.ts


+ 8 - 0
externals/CMakeLists.txt

@@ -143,6 +143,11 @@ endif()
 # TZDB (Time Zone Database)
 add_subdirectory(nx_tzdb)
 
+# VMA
+add_library(vma vma/vma.cpp)
+target_include_directories(vma PUBLIC ./vma/VulkanMemoryAllocator/include)
+target_link_libraries(vma PRIVATE Vulkan::Headers)
+
 if (NOT TARGET LLVM::Demangle)
     add_library(demangle demangle/ItaniumDemangle.cpp)
     target_include_directories(demangle PUBLIC ./demangle)
@@ -152,6 +157,9 @@ endif()
 add_library(stb stb/stb_dxt.cpp)
 target_include_directories(stb PUBLIC ./stb)
 
+add_library(bc_decoder bc_decoder/bc_decoder.cpp)
+target_include_directories(bc_decoder PUBLIC ./bc_decoder)
+
 if (ANDROID)
    if (ARCHITECTURE_arm64)
        add_subdirectory(libadrenotools)

+ 1 - 1
externals/SDL

@@ -1 +1 @@
-Subproject commit f17058b562c8a1090c0c996b42982721ace90903
+Subproject commit 491fba1d06a4810645092b2559b9cc94abeb23bb

+ 1522 - 0
externals/bc_decoder/bc_decoder.cpp

@@ -0,0 +1,1522 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+
+// This BCn Decoder is directly derivative of Swiftshader's BCn Decoder found at: https://github.com/google/swiftshader/blob/d070309f7d154d6764cbd514b1a5c8bfcef61d06/src/Device/BC_Decoder.cpp
+// This file does not follow the Skyline code conventions but has certain Skyline specific code
+// There are a lot of implicit and narrowing conversions in this file due to this (Warnings are disabled as a result)
+
+#include <array>
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+namespace {
+    constexpr int BlockWidth = 4;
+    constexpr int BlockHeight = 4;
+
+    struct BC_color {
+        void decode(uint8_t *dst, size_t x, size_t y, size_t dstW, size_t dstH, size_t dstPitch, size_t dstBpp, bool hasAlphaChannel, bool hasSeparateAlpha) const {
+            Color c[4];
+            c[0].extract565(c0);
+            c[1].extract565(c1);
+            if (hasSeparateAlpha || (c0 > c1)) {
+                c[2] = ((c[0] * 2) + c[1]) / 3;
+                c[3] = ((c[1] * 2) + c[0]) / 3;
+            } else {
+                c[2] = (c[0] + c[1]) >> 1;
+                if (hasAlphaChannel) {
+                    c[3].clearAlpha();
+                }
+            }
+
+            for (int j = 0; j < BlockHeight && (y + j) < dstH; j++) {
+                size_t dstOffset = j * dstPitch;
+                size_t idxOffset = j * BlockHeight;
+                for (size_t i = 0; i < BlockWidth && (x + i) < dstW; i++, idxOffset++, dstOffset += dstBpp) {
+                    *reinterpret_cast<unsigned int *>(dst + dstOffset) = c[getIdx(idxOffset)].pack8888();
+                }
+            }
+        }
+
+      private:
+        struct Color {
+            Color() {
+                c[0] = c[1] = c[2] = 0;
+                c[3] = 0xFF000000;
+            }
+
+            void extract565(const unsigned int c565) {
+                c[0] = ((c565 & 0x0000001F) << 3) | ((c565 & 0x0000001C) >> 2);
+                c[1] = ((c565 & 0x000007E0) >> 3) | ((c565 & 0x00000600) >> 9);
+                c[2] = ((c565 & 0x0000F800) >> 8) | ((c565 & 0x0000E000) >> 13);
+            }
+
+            unsigned int pack8888() const {
+                return ((c[0] & 0xFF) << 16) | ((c[1] & 0xFF) << 8) | (c[2] & 0xFF) | c[3];
+            }
+
+            void clearAlpha() {
+                c[3] = 0;
+            }
+
+            Color operator*(int factor) const {
+                Color res;
+                for (int i = 0; i < 4; ++i) {
+                    res.c[i] = c[i] * factor;
+                }
+                return res;
+            }
+
+            Color operator/(int factor) const {
+                Color res;
+                for (int i = 0; i < 4; ++i) {
+                    res.c[i] = c[i] / factor;
+                }
+                return res;
+            }
+
+            Color operator>>(int shift) const {
+                Color res;
+                for (int i = 0; i < 4; ++i) {
+                    res.c[i] = c[i] >> shift;
+                }
+                return res;
+            }
+
+            Color operator+(Color const &obj) const {
+                Color res;
+                for (int i = 0; i < 4; ++i) {
+                    res.c[i] = c[i] + obj.c[i];
+                }
+                return res;
+            }
+
+          private:
+            int c[4];
+        };
+
+        size_t getIdx(int i) const {
+            size_t offset = i << 1;  // 2 bytes per index
+            return (idx & (0x3 << offset)) >> offset;
+        }
+
+        unsigned short c0;
+        unsigned short c1;
+        unsigned int idx;
+    };
+    static_assert(sizeof(BC_color) == 8, "BC_color must be 8 bytes");
+
+    struct BC_channel {
+        void decode(uint8_t *dst, size_t x, size_t y, size_t dstW, size_t dstH, size_t dstPitch, size_t dstBpp, size_t channel, bool isSigned) const {
+            int c[8] = {0};
+
+            if (isSigned) {
+                c[0] = static_cast<signed char>(data & 0xFF);
+                c[1] = static_cast<signed char>((data & 0xFF00) >> 8);
+            } else {
+                c[0] = static_cast<uint8_t>(data & 0xFF);
+                c[1] = static_cast<uint8_t>((data & 0xFF00) >> 8);
+            }
+
+            if (c[0] > c[1]) {
+                for (int i = 2; i < 8; ++i) {
+                    c[i] = ((8 - i) * c[0] + (i - 1) * c[1]) / 7;
+                }
+            } else {
+                for (int i = 2; i < 6; ++i) {
+                    c[i] = ((6 - i) * c[0] + (i - 1) * c[1]) / 5;
+                }
+                c[6] = isSigned ? -128 : 0;
+                c[7] = isSigned ? 127 : 255;
+            }
+
+            for (size_t j = 0; j < BlockHeight && (y + j) < dstH; j++) {
+                for (size_t i = 0; i < BlockWidth && (x + i) < dstW; i++) {
+                    dst[channel + (i * dstBpp) + (j * dstPitch)] = static_cast<uint8_t>(c[getIdx((j * BlockHeight) + i)]);
+                }
+            }
+        }
+
+      private:
+        uint8_t getIdx(int i) const {
+            int offset = i * 3 + 16;
+            return static_cast<uint8_t>((data & (0x7ull << offset)) >> offset);
+        }
+
+        uint64_t data;
+    };
+    static_assert(sizeof(BC_channel) == 8, "BC_channel must be 8 bytes");
+
+    struct BC_alpha {
+        void decode(uint8_t *dst, size_t x, size_t y, size_t dstW, size_t dstH, size_t dstPitch, size_t dstBpp) const {
+            dst += 3;  // Write only to alpha (channel 3)
+            for (size_t j = 0; j < BlockHeight && (y + j) < dstH; j++, dst += dstPitch) {
+                uint8_t *dstRow = dst;
+                for (size_t i = 0; i < BlockWidth && (x + i) < dstW; i++, dstRow += dstBpp) {
+                    *dstRow = getAlpha(j * BlockHeight + i);
+                }
+            }
+        }
+
+      private:
+        uint8_t getAlpha(int i) const {
+            int offset = i << 2;
+            int alpha = (data & (0xFull << offset)) >> offset;
+            return static_cast<uint8_t>(alpha | (alpha << 4));
+        }
+
+        uint64_t data;
+    };
+    static_assert(sizeof(BC_alpha) == 8, "BC_alpha must be 8 bytes");
+
+    namespace BC6H {
+        static constexpr int MaxPartitions = 64;
+
+        // @fmt:off
+
+        static constexpr uint8_t PartitionTable2[MaxPartitions][16] = {
+            { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1 },
+            { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 },
+            { 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1 },
+            { 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1 },
+            { 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1 },
+            { 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1 },
+            { 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1 },
+            { 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
+            { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 },
+            { 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
+            { 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
+            { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0 },
+            { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 },
+            { 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1 },
+            { 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 },
+            { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 },
+            { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0 },
+            { 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0 },
+            { 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0 },
+            { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+            { 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0 },
+            { 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0 },
+            { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 },
+            { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1 },
+            { 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0 },
+            { 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0 },
+            { 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0 },
+            { 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0 },
+            { 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1 },
+            { 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1 },
+            { 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0 },
+            { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0 },
+            { 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0 },
+            { 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0 },
+            { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 },
+            { 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1 },
+            { 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1 },
+            { 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 },
+            { 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 },
+            { 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 },
+            { 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0 },
+            { 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0 },
+            { 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1 },
+            { 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1 },
+            { 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0 },
+            { 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0 },
+            { 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1 },
+            { 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1 },
+            { 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1 },
+            { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1 },
+            { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1 },
+            { 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+            { 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0 },
+            { 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1 },
+        };
+
+        static constexpr uint8_t AnchorTable2[MaxPartitions] = {
+            0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+            0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+            0xf, 0x2, 0x8, 0x2, 0x2, 0x8, 0x8, 0xf,
+            0x2, 0x8, 0x2, 0x2, 0x8, 0x8, 0x2, 0x2,
+            0xf, 0xf, 0x6, 0x8, 0x2, 0x8, 0xf, 0xf,
+            0x2, 0x8, 0x2, 0x2, 0x2, 0xf, 0xf, 0x6,
+            0x6, 0x2, 0x6, 0x8, 0xf, 0xf, 0x2, 0x2,
+            0xf, 0xf, 0xf, 0xf, 0xf, 0x2, 0x2, 0xf,
+        };
+
+    // @fmt:on
+
+        // 1.0f in half-precision floating point format
+        static constexpr uint16_t halfFloat1 = 0x3C00;
+        union Color {
+            struct RGBA {
+                uint16_t r = 0;
+                uint16_t g = 0;
+                uint16_t b = 0;
+                uint16_t a = halfFloat1;
+
+                RGBA(uint16_t r, uint16_t g, uint16_t b)
+                    : r(r), g(g), b(b) {
+                }
+
+                RGBA &operator=(const RGBA &other) {
+                    this->r = other.r;
+                    this->g = other.g;
+                    this->b = other.b;
+                    this->a = halfFloat1;
+
+                    return *this;
+                }
+            };
+
+            Color(uint16_t r, uint16_t g, uint16_t b)
+                : rgba(r, g, b) {
+            }
+
+            Color(int r, int g, int b)
+                : rgba((uint16_t) r, (uint16_t) g, (uint16_t) b) {
+            }
+
+            Color() {}
+
+            Color(const Color &other) {
+                this->rgba = other.rgba;
+            }
+
+            Color &operator=(const Color &other) {
+                this->rgba = other.rgba;
+
+                return *this;
+            }
+
+            RGBA rgba;
+            uint16_t channel[4];
+        };
+        static_assert(sizeof(Color) == 8, "BC6h::Color must be 8 bytes long");
+
+        inline int32_t extendSign(int32_t val, size_t size) {
+            // Suppose we have a 2-bit integer being stored in 4 bit variable:
+            //    x = 0b00AB
+            //
+            // In order to sign extend x, we need to turn the 0s into A's:
+            //    x_extend = 0bAAAB
+            //
+            // We can do that by flipping A in x then subtracting 0b0010 from x.
+            // Suppose A is 1:
+            //    x       = 0b001B
+            //    x_flip  = 0b000B
+            //    x_minus = 0b111B
+            // Since A is flipped to 0, subtracting the mask sets it and all the bits above it to 1.
+            // And if A is 0:
+            //    x       = 0b000B
+            //    x_flip  = 0b001B
+            //    x_minus = 0b000B
+            // We unset the bit we flipped, and touch no other bit
+            uint16_t mask = 1u << (size - 1);
+            return (val ^ mask) - mask;
+        }
+
+        static int constexpr RGBfChannels = 3;
+        struct RGBf {
+            uint16_t channel[RGBfChannels];
+            size_t size[RGBfChannels];
+            bool isSigned;
+
+            RGBf() {
+                static_assert(RGBfChannels == 3, "RGBf must have exactly 3 channels");
+                static_assert(sizeof(channel) / sizeof(channel[0]) == RGBfChannels, "RGBf must have exactly 3 channels");
+                static_assert(sizeof(channel) / sizeof(channel[0]) == sizeof(size) / sizeof(size[0]), "RGBf requires equally sized arrays for channels and channel sizes");
+
+                for (int i = 0; i < RGBfChannels; i++) {
+                    channel[i] = 0;
+                    size[i] = 0;
+                }
+
+                isSigned = false;
+            }
+
+            void extendSign() {
+                for (int i = 0; i < RGBfChannels; i++) {
+                    channel[i] = BC6H::extendSign(channel[i], size[i]);
+                }
+            }
+
+            // Assuming this is the delta, take the base-endpoint and transform this into
+            // a proper endpoint.
+            //
+            // The final computed endpoint is truncated to the base-endpoint's size;
+            void resolveDelta(RGBf base) {
+                for (int i = 0; i < RGBfChannels; i++) {
+                    size[i] = base.size[i];
+                    channel[i] = (base.channel[i] + channel[i]) & ((1 << base.size[i]) - 1);
+                }
+
+                // Per the spec:
+                // "For signed formats, the results of the delta calculation must be sign
+                // extended as well."
+                if (isSigned) {
+                    extendSign();
+                }
+            }
+
+            void unquantize() {
+                if (isSigned) {
+                    unquantizeSigned();
+                } else {
+                    unquantizeUnsigned();
+                }
+            }
+
+            void unquantizeUnsigned() {
+                for (int i = 0; i < RGBfChannels; i++) {
+                    if (size[i] >= 15 || channel[i] == 0) {
+                        continue;
+                    } else if (channel[i] == ((1u << size[i]) - 1)) {
+                        channel[i] = 0xFFFFu;
+                    } else {
+                        // Need 32 bits to avoid overflow
+                        uint32_t tmp = channel[i];
+                        channel[i] = (uint16_t) (((tmp << 16) + 0x8000) >> size[i]);
+                    }
+                    size[i] = 16;
+                }
+            }
+
+            void unquantizeSigned() {
+                for (int i = 0; i < RGBfChannels; i++) {
+                    if (size[i] >= 16 || channel[i] == 0) {
+                        continue;
+                    }
+
+                    int16_t value = (int16_t)channel[i];
+                    int32_t result = value;
+                    bool signBit = value < 0;
+                    if (signBit) {
+                        value = -value;
+                    }
+
+                    if (value >= ((1 << (size[i] - 1)) - 1)) {
+                        result = 0x7FFF;
+                    } else {
+                        // Need 32 bits to avoid overflow
+                        int32_t tmp = value;
+                        result = (((tmp << 15) + 0x4000) >> (size[i] - 1));
+                    }
+
+                    if (signBit) {
+                        result = -result;
+                    }
+
+                    channel[i] = (uint16_t) result;
+                    size[i] = 16;
+                }
+            }
+        };
+
+        struct Data {
+            uint64_t low64;
+            uint64_t high64;
+
+            Data() = default;
+
+            Data(uint64_t low64, uint64_t high64)
+                : low64(low64), high64(high64) {
+            }
+
+            // Consumes the lowest N bits from from low64 and high64 where N is:
+            //      abs(MSB - LSB)
+            // MSB and LSB come from the block description of the BC6h spec and specify
+            // the location of the bits in the returned bitstring.
+            //
+            // If MSB < LSB, then the bits are reversed. Otherwise, the bitstring is read and
+            // shifted without further modification.
+            //
+            uint32_t consumeBits(uint32_t MSB, uint32_t LSB) {
+                bool reversed = MSB < LSB;
+                if (reversed) {
+                    std::swap(MSB, LSB);
+                }
+                assert(MSB - LSB + 1 < sizeof(uint32_t) * 8);
+
+                uint32_t numBits = MSB - LSB + 1;
+                uint32_t mask = (1 << numBits) - 1;
+                // Read the low N bits
+                uint32_t bits = (low64 & mask);
+
+                low64 >>= numBits;
+                // Put the low N bits of high64 into the high 64-N bits of low64
+                low64 |= (high64 & mask) << (sizeof(high64) * 8 - numBits);
+                high64 >>= numBits;
+
+                if (reversed) {
+                    uint32_t tmp = 0;
+                    for (uint32_t numSwaps = 0; numSwaps < numBits; numSwaps++) {
+                        tmp <<= 1;
+                        tmp |= (bits & 1);
+                        bits >>= 1;
+                    }
+
+                    bits = tmp;
+                }
+
+                return bits << LSB;
+            }
+        };
+
+        struct IndexInfo {
+            uint64_t value;
+            int numBits;
+        };
+
+// Interpolates between two endpoints, then does a final unquantization step
+        Color interpolate(RGBf e0, RGBf e1, const IndexInfo &index, bool isSigned) {
+            static constexpr uint32_t weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
+            static constexpr uint32_t weights4[] = {0, 4, 9, 13, 17, 21, 26, 30,
+                                                    34, 38, 43, 47, 51, 55, 60, 64};
+            static constexpr uint32_t const *weightsN[] = {
+                nullptr, nullptr, nullptr, weights3, weights4
+            };
+            auto weights = weightsN[index.numBits];
+            assert(weights != nullptr);
+            Color color;
+            uint32_t e0Weight = 64 - weights[index.value];
+            uint32_t e1Weight = weights[index.value];
+
+            for (int i = 0; i < RGBfChannels; i++) {
+                int32_t e0Channel = e0.channel[i];
+                int32_t e1Channel = e1.channel[i];
+
+                if (isSigned) {
+                    e0Channel = extendSign(e0Channel, 16);
+                    e1Channel = extendSign(e1Channel, 16);
+                }
+
+                int32_t e0Value = e0Channel * e0Weight;
+                int32_t e1Value = e1Channel * e1Weight;
+
+                uint32_t tmp = ((e0Value + e1Value + 32) >> 6);
+
+                // Need to unquantize value to limit it to the legal range of half-precision
+                // floats. We do this by scaling by 31/32 or 31/64 depending on if the value
+                // is signed or unsigned.
+                if (isSigned) {
+                    tmp = ((tmp & 0x80000000) != 0) ? (((~tmp + 1) * 31) >> 5) | 0x8000 : (tmp * 31) >> 5;
+                    // Don't return -0.0f, just normalize it to 0.0f.
+                    if (tmp == 0x8000)
+                        tmp = 0;
+                } else {
+                    tmp = (tmp * 31) >> 6;
+                }
+
+                color.channel[i] = (uint16_t) tmp;
+            }
+
+            return color;
+        }
+
+        enum DataType {
+            // Endpoints
+            EP0 = 0,
+            EP1 = 1,
+            EP2 = 2,
+            EP3 = 3,
+            Mode,
+            Partition,
+            End,
+        };
+
+        enum Channel {
+            R = 0,
+            G = 1,
+            B = 2,
+            None,
+        };
+
+        struct DeltaBits {
+            size_t channel[3];
+
+            constexpr DeltaBits()
+                : channel{0, 0, 0} {
+            }
+
+            constexpr DeltaBits(size_t r, size_t g, size_t b)
+                : channel{r, g, b} {
+            }
+        };
+
+        struct ModeDesc {
+            int number;
+            bool hasDelta;
+            int partitionCount;
+            int endpointBits;
+            DeltaBits deltaBits;
+
+            constexpr ModeDesc()
+                : number(-1), hasDelta(false), partitionCount(0), endpointBits(0) {
+            }
+
+            constexpr ModeDesc(int number, bool hasDelta, int partitionCount, int endpointBits, DeltaBits deltaBits)
+                : number(number), hasDelta(hasDelta), partitionCount(partitionCount), endpointBits(endpointBits), deltaBits(deltaBits) {
+            }
+        };
+
+        struct BlockDesc {
+            DataType type;
+            Channel channel;
+            int MSB;
+            int LSB;
+            ModeDesc modeDesc;
+
+            constexpr BlockDesc()
+                : type(End), channel(None), MSB(0), LSB(0), modeDesc() {
+            }
+
+            constexpr BlockDesc(const DataType type, Channel channel, int MSB, int LSB, ModeDesc modeDesc)
+                : type(type), channel(channel), MSB(MSB), LSB(LSB), modeDesc(modeDesc) {
+            }
+
+            constexpr BlockDesc(DataType type, Channel channel, int MSB, int LSB)
+                : type(type), channel(channel), MSB(MSB), LSB(LSB), modeDesc() {
+            }
+        };
+
+// Turns a legal mode into an index into the BlockDesc table.
+// Illegal or reserved modes return -1.
+        static int modeToIndex(uint8_t mode) {
+            if (mode <= 3) {
+                return mode;
+            } else if ((mode & 0x2) != 0) {
+                if (mode <= 18) {
+// Turns 6 into 4, 7 into 5, 10 into 6, etc.
+                    return (mode / 2) + 1 + (mode & 0x1);
+                } else if (mode == 22 || mode == 26 || mode == 30) {
+// Turns 22 into 11, 26 into 12, etc.
+                    return mode / 4 + 6;
+                }
+            }
+
+            return -1;
+        }
+
+// Returns a description of the bitfields for each mode from the LSB
+// to the MSB before the index data starts.
+//
+// The numbers come from the BC6h block description. Each BlockDesc in the
+//   {Type, Channel, MSB, LSB}
+//   * Type describes which endpoint this is, or if this is a mode, a partition
+//     number, or the end of the block description.
+//   * Channel describes one of the 3 color channels within an endpoint
+//   * MSB and LSB specificy:
+//      * The size of the bitfield being read
+//      * The position of the bitfield within the variable it is being read to
+//      * If the bitfield is stored in reverse bit order
+//     If MSB < LSB then the bitfield is stored in reverse order. The size of
+//     the bitfield is abs(MSB-LSB+1). And the position of the bitfield within
+//     the variable is min(LSB, MSB).
+//
+// Invalid or reserved modes return an empty list.
+        static constexpr int NumBlocks = 14;
+// The largest number of descriptions within a block.
+        static constexpr int MaxBlockDescIndex = 26;
+        static constexpr BlockDesc blockDescs[NumBlocks][MaxBlockDescIndex] = {
+// @fmt:off
+// Mode 0, Index 0
+{
+{ Mode, None, 1, 0, { 0, true, 2, 10, { 5, 5, 5 } } },
+{ EP2, G, 4, 4 }, { EP2, B, 4, 4 }, { EP3, B, 4, 4 },
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 4, 0 }, { EP3, G, 4, 4 }, { EP2, G, 3, 0 },
+{ EP1, G, 4, 0 }, { EP3, B, 0, 0 }, { EP3, G, 3, 0 },
+{ EP1, B, 4, 0 }, { EP3, B, 1, 1 }, { EP2, B, 3, 0 },
+{ EP2, R, 4, 0 }, { EP3, B, 2, 2 }, { EP3, R, 4, 0 },
+{ EP3, B, 3, 3 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 1, Index 1
+{
+{ Mode, None, 1, 0, { 1, true, 2, 7, { 6, 6, 6 } } },
+{ EP2, G, 5, 5 }, { EP3, G, 5, 4 }, { EP0, R, 6, 0 },
+{ EP3, B, 1, 0 }, { EP2, B, 4, 4 }, { EP0, G, 6, 0 },
+{ EP2, B, 5, 5 }, { EP3, B, 2, 2 }, { EP2, G, 4, 4 },
+{ EP0, B, 6, 0 }, { EP3, B, 3, 3 }, { EP3, B, 5, 5 },
+{ EP3, B, 4, 4 }, { EP1, R, 5, 0 }, { EP2, G, 3, 0 },
+{ EP1, G, 5, 0 }, { EP3, G, 3, 0 }, { EP1, B, 5, 0 },
+{ EP2, B, 3, 0 }, { EP2, R, 5, 0 }, { EP3, R, 5, 0 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 2, Index 2
+{
+{ Mode, None, 4, 0, { 2, true, 2, 11, { 5, 4, 4 } } },
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 4, 0 }, { EP0, R, 10, 10 }, { EP2, G, 3, 0 },
+{ EP1, G, 3, 0 }, { EP0, G, 10, 10 }, { EP3, B, 0, 0 },
+{ EP3, G, 3, 0 }, { EP1, B, 3, 0 }, { EP0, B, 10, 10 },
+{ EP3, B, 1, 1 }, { EP2, B, 3, 0 }, { EP2, R, 4, 0 },
+{ EP3, B, 2, 2 }, { EP3, R, 4, 0 }, { EP3, B, 3, 3 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 3, Index 3
+{
+{ Mode, None, 4, 0, { 3, false, 1, 10, { 0, 0, 0 } } },
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 9, 0 }, { EP1, G, 9, 0 }, { EP1, B, 9, 0 },
+{ End, None, 0, 0},
+},
+// Mode 6, Index 4
+{
+{ Mode, None, 4, 0, { 6, true, 2, 11, { 4, 5, 4 } } }, // 1 1
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 3, 0 }, { EP0, R, 10, 10 }, { EP3, G, 4, 4 },
+{ EP2, G, 3, 0 }, { EP1, G, 4, 0 }, { EP0, G, 10, 10 },
+{ EP3, G, 3, 0 }, { EP1, B, 3, 0 }, { EP0, B, 10, 10 },
+{ EP3, B, 1, 1 }, { EP2, B, 3, 0 }, { EP2, R, 3, 0 },
+{ EP3, B, 0, 0 }, { EP3, B, 2, 2 }, { EP3, R, 3, 0 }, // 18 19
+{ EP2, G, 4, 4 }, { EP3, B, 3, 3 }, // 2 21
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 7, Index 5
+{
+{ Mode, None, 4, 0, { 7, true, 1, 11, { 9, 9, 9 } } },
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 8, 0 }, { EP0, R, 10, 10 }, { EP1, G, 8, 0 },
+{ EP0, G, 10, 10 }, { EP1, B, 8, 0 }, { EP0, B, 10, 10 },
+{ End, None, 0, 0},
+},
+// Mode 10, Index 6
+{
+{ Mode, None, 4, 0, { 10, true, 2, 11, { 4, 4, 5 } } },
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 3, 0 }, { EP0, R, 10, 10 }, { EP2, B, 4, 4 },
+{ EP2, G, 3, 0 }, { EP1, G, 3, 0 }, { EP0, G, 10, 10 },
+{ EP3, B, 0, 0 }, { EP3, G, 3, 0 }, { EP1, B, 4, 0 },
+{ EP0, B, 10, 10 }, { EP2, B, 3, 0 }, { EP2, R, 3, 0 },
+{ EP3, B, 1, 1 }, { EP3, B, 2, 2 }, { EP3, R, 3, 0 },
+{ EP3, B, 4, 4 }, { EP3, B, 3, 3 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 11, Index 7
+{
+{ Mode, None, 4, 0, { 11, true, 1, 12, { 8, 8, 8 } } },
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 7, 0 }, { EP0, R, 10, 11 }, { EP1, G, 7, 0 },
+{ EP0, G, 10, 11 }, { EP1, B, 7, 0 }, { EP0, B, 10, 11 },
+{ End, None, 0, 0},
+},
+// Mode 14, Index 8
+{
+{ Mode, None, 4, 0, { 14, true, 2, 9, { 5, 5, 5 } } },
+{ EP0, R, 8, 0 }, { EP2, B, 4, 4 }, { EP0, G, 8, 0 },
+{ EP2, G, 4, 4 }, { EP0, B, 8, 0 }, { EP3, B, 4, 4 },
+{ EP1, R, 4, 0 }, { EP3, G, 4, 4 }, { EP2, G, 3, 0 },
+{ EP1, G, 4, 0 }, { EP3, B, 0, 0 }, { EP3, G, 3, 0 },
+{ EP1, B, 4, 0 }, { EP3, B, 1, 1 }, { EP2, B, 3, 0 },
+{ EP2, R, 4, 0 }, { EP3, B, 2, 2 }, { EP3, R, 4, 0 },
+{ EP3, B, 3, 3 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 15, Index 9
+{
+{ Mode, None, 4, 0, { 15, true, 1, 16, { 4, 4, 4 } } },
+{ EP0, R, 9, 0 }, { EP0, G, 9, 0 }, { EP0, B, 9, 0 },
+{ EP1, R, 3, 0 }, { EP0, R, 10, 15 }, { EP1, G, 3, 0 },
+{ EP0, G, 10, 15 }, { EP1, B, 3, 0 }, { EP0, B, 10, 15 },
+{ End, None, 0, 0},
+},
+// Mode 18, Index 10
+{
+{ Mode, None, 4, 0, { 18, true, 2, 8, { 6, 5, 5 } } },
+{ EP0, R, 7, 0 }, { EP3, G, 4, 4 }, { EP2, B, 4, 4 },
+{ EP0, G, 7, 0 }, { EP3, B, 2, 2 }, { EP2, G, 4, 4 },
+{ EP0, B, 7, 0 }, { EP3, B, 3, 3 }, { EP3, B, 4, 4 },
+{ EP1, R, 5, 0 }, { EP2, G, 3, 0 }, { EP1, G, 4, 0 },
+{ EP3, B, 0, 0 }, { EP3, G, 3, 0 }, { EP1, B, 4, 0 },
+{ EP3, B, 1, 1 }, { EP2, B, 3, 0 }, { EP2, R, 5, 0 },
+{ EP3, R, 5, 0 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 22, Index 11
+{
+{ Mode, None, 4, 0, { 22, true, 2, 8, { 5, 6, 5 } } },
+{ EP0, R, 7, 0 }, { EP3, B, 0, 0 }, { EP2, B, 4, 4 },
+{ EP0, G, 7, 0 }, { EP2, G, 5, 5 }, { EP2, G, 4, 4 },
+{ EP0, B, 7, 0 }, { EP3, G, 5, 5 }, { EP3, B, 4, 4 },
+{ EP1, R, 4, 0 }, { EP3, G, 4, 4 }, { EP2, G, 3, 0 },
+{ EP1, G, 5, 0 }, { EP3, G, 3, 0 }, { EP1, B, 4, 0 },
+{ EP3, B, 1, 1 }, { EP2, B, 3, 0 }, { EP2, R, 4, 0 },
+{ EP3, B, 2, 2 }, { EP3, R, 4, 0 }, { EP3, B, 3, 3 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 26, Index 12
+{
+{ Mode, None, 4, 0, { 26, true, 2, 8, { 5, 5, 6 } } },
+{ EP0, R, 7, 0 }, { EP3, B, 1, 1 }, { EP2, B, 4, 4 },
+{ EP0, G, 7, 0 }, { EP2, B, 5, 5 }, { EP2, G, 4, 4 },
+{ EP0, B, 7, 0 }, { EP3, B, 5, 5 }, { EP3, B, 4, 4 },
+{ EP1, R, 4, 0 }, { EP3, G, 4, 4 }, { EP2, G, 3, 0 },
+{ EP1, G, 4, 0 }, { EP3, B, 0, 0 }, { EP3, G, 3, 0 },
+{ EP1, B, 5, 0 }, { EP2, B, 3, 0 }, { EP2, R, 4, 0 },
+{ EP3, B, 2, 2 }, { EP3, R, 4, 0 }, { EP3, B, 3, 3 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+},
+// Mode 30, Index 13
+{
+{ Mode, None, 4, 0, { 30, false, 2, 6, { 0, 0, 0 } } },
+{ EP0, R, 5, 0 }, { EP3, G, 4, 4 }, { EP3, B, 0, 0 },
+{ EP3, B, 1, 1 }, { EP2, B, 4, 4 }, { EP0, G, 5, 0 },
+{ EP2, G, 5, 5 }, { EP2, B, 5, 5 }, { EP3, B, 2, 2 },
+{ EP2, G, 4, 4 }, { EP0, B, 5, 0 }, { EP3, G, 5, 5 },
+{ EP3, B, 3, 3 }, { EP3, B, 5, 5 }, { EP3, B, 4, 4 },
+{ EP1, R, 5, 0 }, { EP2, G, 3, 0 }, { EP1, G, 5, 0 },
+{ EP3, G, 3, 0 }, { EP1, B, 5, 0 }, { EP2, B, 3, 0 },
+{ EP2, R, 5, 0 }, { EP3, R, 5, 0 },
+{ Partition, None, 4, 0 },
+{ End, None, 0, 0},
+}
+// @fmt:on
+        };
+
+        struct Block {
+            uint64_t low64;
+            uint64_t high64;
+
+            void decode(uint8_t *dst, size_t dstX, size_t dstY, size_t dstWidth, size_t dstHeight, size_t dstPitch, size_t dstBpp, bool isSigned) const {
+                uint8_t mode = 0;
+                Data data(low64, high64);
+                assert(dstBpp == sizeof(Color));
+
+                if ((data.low64 & 0x2) == 0) {
+                    mode = data.consumeBits(1, 0);
+                } else {
+                    mode = data.consumeBits(4, 0);
+                }
+
+                int blockIndex = modeToIndex(mode);
+                // Handle illegal or reserved mode
+                if (blockIndex == -1) {
+                    for (int y = 0; y < 4 && y + dstY < dstHeight; y++) {
+                        for (int x = 0; x < 4 && x + dstX < dstWidth; x++) {
+                            auto out = reinterpret_cast<Color *>(dst + sizeof(Color) * x + dstPitch * y);
+                            out->rgba = {0, 0, 0};
+                        }
+                    }
+                    return;
+                }
+                const BlockDesc *blockDesc = blockDescs[blockIndex];
+
+                RGBf e[4];
+                e[0].isSigned = e[1].isSigned = e[2].isSigned = e[3].isSigned = isSigned;
+
+                int partition = 0;
+                ModeDesc modeDesc;
+                for (int index = 0; blockDesc[index].type != End; index++) {
+                    const BlockDesc desc = blockDesc[index];
+
+                    switch (desc.type) {
+                        case Mode:
+                            modeDesc = desc.modeDesc;
+                            assert(modeDesc.number == mode);
+
+                            e[0].size[0] = e[0].size[1] = e[0].size[2] = modeDesc.endpointBits;
+                            for (int i = 0; i < RGBfChannels; i++) {
+                                if (modeDesc.hasDelta) {
+                                    e[1].size[i] = e[2].size[i] = e[3].size[i] = modeDesc.deltaBits.channel[i];
+                                } else {
+                                    e[1].size[i] = e[2].size[i] = e[3].size[i] = modeDesc.endpointBits;
+                                }
+                            }
+                            break;
+                        case Partition:
+                            partition |= data.consumeBits(desc.MSB, desc.LSB);
+                            break;
+                        case EP0:
+                        case EP1:
+                        case EP2:
+                        case EP3:
+                            e[desc.type].channel[desc.channel] |= data.consumeBits(desc.MSB, desc.LSB);
+                            break;
+                        default:
+                            assert(false);
+                            return;
+                    }
+                }
+
+                // Sign extension
+                if (isSigned) {
+                    for (int ep = 0; ep < modeDesc.partitionCount * 2; ep++) {
+                        e[ep].extendSign();
+                    }
+                } else if (modeDesc.hasDelta) {
+                    // Don't sign-extend the base endpoint in an unsigned format.
+                    for (int ep = 1; ep < modeDesc.partitionCount * 2; ep++) {
+                        e[ep].extendSign();
+                    }
+                }
+
+                // Turn the deltas into endpoints
+                if (modeDesc.hasDelta) {
+                    for (int ep = 1; ep < modeDesc.partitionCount * 2; ep++) {
+                        e[ep].resolveDelta(e[0]);
+                    }
+                }
+
+                for (int ep = 0; ep < modeDesc.partitionCount * 2; ep++) {
+                    e[ep].unquantize();
+                }
+
+                // Get the indices, calculate final colors, and output
+                for (int y = 0; y < 4; y++) {
+                    for (int x = 0; x < 4; x++) {
+                        int pixelNum = x + y * 4;
+                        IndexInfo idx;
+                        bool isAnchor = false;
+                        int firstEndpoint = 0;
+                        // Bc6H can have either 1 or 2 petitions depending on the mode.
+                        // The number of petitions affects the number of indices with implicit
+                        // leading 0 bits and the number of bits per index.
+                        if (modeDesc.partitionCount == 1) {
+                            idx.numBits = 4;
+                            // There's an implicit leading 0 bit for the first idx
+                            isAnchor = (pixelNum == 0);
+                        } else {
+                            idx.numBits = 3;
+                            // There are 2 indices with implicit leading 0-bits.
+                            isAnchor = ((pixelNum == 0) || (pixelNum == AnchorTable2[partition]));
+                            firstEndpoint = PartitionTable2[partition][pixelNum] * 2;
+                        }
+
+                        idx.value = data.consumeBits(idx.numBits - isAnchor - 1, 0);
+
+                        // Don't exit the loop early, we need to consume these index bits regardless if
+                        // we actually output them or not.
+                        if ((y + dstY >= dstHeight) || (x + dstX >= dstWidth)) {
+                            continue;
+                        }
+
+                        Color color = interpolate(e[firstEndpoint], e[firstEndpoint + 1], idx, isSigned);
+                        auto out = reinterpret_cast<Color *>(dst + dstBpp * x + dstPitch * y);
+                        *out = color;
+                    }
+                }
+            }
+        };
+
+    }  // namespace BC6H
+
+    namespace BC7 {
+// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_bptc.txt
+// https://docs.microsoft.com/en-us/windows/win32/direct3d11/bc7-format
+
+        struct Bitfield {
+            int offset;
+            int count;
+
+            constexpr Bitfield Then(const int bits) { return {offset + count, bits}; }
+
+            constexpr bool operator==(const Bitfield &rhs) {
+                return offset == rhs.offset && count == rhs.count;
+            }
+        };
+
+        struct Mode {
+            const int IDX;  // Mode index
+            const int NS;   // Number of subsets in each partition
+            const int PB;   // Partition bits
+            const int RB;   // Rotation bits
+            const int ISB;  // Index selection bits
+            const int CB;   // Color bits
+            const int AB;   // Alpha bits
+            const int EPB;  // Endpoint P-bits
+            const int SPB;  // Shared P-bits
+            const int IB;   // Primary index bits per element
+            const int IBC;  // Primary index bits total
+            const int IB2;  // Secondary index bits per element
+
+            constexpr int NumColors() const { return NS * 2; }
+
+            constexpr Bitfield Partition() const { return {IDX + 1, PB}; }
+
+            constexpr Bitfield Rotation() const { return Partition().Then(RB); }
+
+            constexpr Bitfield IndexSelection() const { return Rotation().Then(ISB); }
+
+            constexpr Bitfield Red(int idx) const {
+                return IndexSelection().Then(CB * idx).Then(CB);
+            }
+
+            constexpr Bitfield Green(int idx) const {
+                return Red(NumColors() - 1).Then(CB * idx).Then(CB);
+            }
+
+            constexpr Bitfield Blue(int idx) const {
+                return Green(NumColors() - 1).Then(CB * idx).Then(CB);
+            }
+
+            constexpr Bitfield Alpha(int idx) const {
+                return Blue(NumColors() - 1).Then(AB * idx).Then(AB);
+            }
+
+            constexpr Bitfield EndpointPBit(int idx) const {
+                return Alpha(NumColors() - 1).Then(EPB * idx).Then(EPB);
+            }
+
+            constexpr Bitfield SharedPBit0() const {
+                return EndpointPBit(NumColors() - 1).Then(SPB);
+            }
+
+            constexpr Bitfield SharedPBit1() const {
+                return SharedPBit0().Then(SPB);
+            }
+
+            constexpr Bitfield PrimaryIndex(int offset, int count) const {
+                return SharedPBit1().Then(offset).Then(count);
+            }
+
+            constexpr Bitfield SecondaryIndex(int offset, int count) const {
+                return SharedPBit1().Then(IBC + offset).Then(count);
+            }
+        };
+
+        static constexpr Mode Modes[] = {
+            //     IDX  NS   PB   RB   ISB  CB   AB   EPB  SPB  IB   IBC, IB2
+            /**/ {0x0, 0x3, 0x4, 0x0, 0x0, 0x4, 0x0, 0x1, 0x0, 0x3, 0x2d, 0x0},
+/**/ {0x1, 0x2, 0x6, 0x0, 0x0, 0x6, 0x0, 0x0, 0x1, 0x3, 0x2e, 0x0},
+/**/ {0x2, 0x3, 0x6, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x2, 0x1d, 0x0},
+/**/ {0x3, 0x2, 0x6, 0x0, 0x0, 0x7, 0x0, 0x1, 0x0, 0x2, 0x1e, 0x0},
+/**/ {0x4, 0x1, 0x0, 0x2, 0x1, 0x5, 0x6, 0x0, 0x0, 0x2, 0x1f, 0x3},
+/**/ {0x5, 0x1, 0x0, 0x2, 0x0, 0x7, 0x8, 0x0, 0x0, 0x2, 0x1f, 0x2},
+/**/ {0x6, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x1, 0x0, 0x4, 0x3f, 0x0},
+/**/ {0x7, 0x2, 0x6, 0x0, 0x0, 0x5, 0x5, 0x1, 0x0, 0x2, 0x1e, 0x0},
+/**/ {-1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x0},
+        };
+
+        static constexpr int MaxPartitions = 64;
+        static constexpr int MaxSubsets = 3;
+
+        static constexpr uint8_t PartitionTable2[MaxPartitions][16] = {
+            {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1},
+            {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1},
+            {0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1},
+            {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1},
+            {0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1},
+            {0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1},
+            {0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1},
+            {0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1},
+            {0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
+            {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0},
+            {0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0},
+            {0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
+            {0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0},
+            {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0},
+            {0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1},
+            {0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0},
+            {0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0},
+            {0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0},
+            {0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0},
+            {0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0},
+            {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0},
+            {0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0},
+            {0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0},
+            {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
+            {0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1},
+            {0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0},
+            {0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0},
+            {0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0},
+            {0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0},
+            {0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1},
+            {0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1},
+            {0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0},
+            {0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0},
+            {0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0},
+            {0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0},
+            {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0},
+            {0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1},
+            {0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1},
+            {0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0},
+            {0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0},
+            {0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0},
+            {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0},
+            {0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0},
+            {0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1},
+            {0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1},
+            {0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0},
+            {0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0},
+            {0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1},
+            {0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1},
+            {0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1},
+            {0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1},
+            {0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1},
+            {0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0},
+            {0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0},
+            {0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1},
+        };
+
+        static constexpr uint8_t PartitionTable3[MaxPartitions][16] = {
+            {0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 1, 2, 2, 2, 2},
+            {0, 0, 0, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1},
+            {0, 0, 0, 0, 2, 0, 0, 1, 2, 2, 1, 1, 2, 2, 1, 1},
+            {0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 1, 0, 1, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2},
+            {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 2, 2},
+            {0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1},
+            {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2},
+            {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2},
+            {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2},
+            {0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2},
+            {0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2},
+            {0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2},
+            {0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2},
+            {0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0},
+            {0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2},
+            {0, 1, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0},
+            {0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2},
+            {0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1},
+            {0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2},
+            {0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1},
+            {0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2},
+            {0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 1, 0, 2, 2, 1, 0},
+            {0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1, 0, 0, 0, 0},
+            {0, 0, 1, 2, 0, 0, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2},
+            {0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1, 0, 1, 1, 0},
+            {0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1},
+            {0, 0, 2, 2, 1, 1, 0, 2, 1, 1, 0, 2, 0, 0, 2, 2},
+            {0, 1, 1, 0, 0, 1, 1, 0, 2, 0, 0, 2, 2, 2, 2, 2},
+            {0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1},
+            {0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 1, 1, 2, 2, 2, 1},
+            {0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 2, 2, 2},
+            {0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 2, 0, 0, 1, 1},
+            {0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 2, 2, 0, 2, 2, 2},
+            {0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0},
+            {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0},
+            {0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0},
+            {0, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 0, 1, 2, 0},
+            {0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1},
+            {0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1},
+            {0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2},
+            {0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1},
+            {0, 0, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2, 1, 1, 2, 2},
+            {0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 1, 1},
+            {0, 2, 2, 0, 1, 2, 2, 1, 0, 2, 2, 0, 1, 2, 2, 1},
+            {0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 1},
+            {0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1},
+            {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2},
+            {0, 2, 2, 2, 0, 1, 1, 1, 0, 2, 2, 2, 0, 1, 1, 1},
+            {0, 0, 0, 2, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 2},
+            {0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2},
+            {0, 2, 2, 2, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2},
+            {0, 0, 0, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2},
+            {0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2},
+            {0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2},
+            {0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2},
+            {0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2},
+            {0, 0, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2},
+            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2},
+            {0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1},
+            {0, 2, 2, 2, 1, 2, 2, 2, 0, 2, 2, 2, 1, 2, 2, 2},
+            {0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
+            {0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0},
+        };
+
+        static constexpr uint8_t AnchorTable2[MaxPartitions] = {
+// @fmt:off
+0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+0xf, 0x2, 0x8, 0x2, 0x2, 0x8, 0x8, 0xf,
+0x2, 0x8, 0x2, 0x2, 0x8, 0x8, 0x2, 0x2,
+0xf, 0xf, 0x6, 0x8, 0x2, 0x8, 0xf, 0xf,
+0x2, 0x8, 0x2, 0x2, 0x2, 0xf, 0xf, 0x6,
+0x6, 0x2, 0x6, 0x8, 0xf, 0xf, 0x2, 0x2,
+0xf, 0xf, 0xf, 0xf, 0xf, 0x2, 0x2, 0xf,
+// @fmt:on
+        };
+
+        static constexpr uint8_t AnchorTable3a[MaxPartitions] = {
+// @fmt:off
+0x3, 0x3, 0xf, 0xf, 0x8, 0x3, 0xf, 0xf,
+0x8, 0x8, 0x6, 0x6, 0x6, 0x5, 0x3, 0x3,
+0x3, 0x3, 0x8, 0xf, 0x3, 0x3, 0x6, 0xa,
+0x5, 0x8, 0x8, 0x6, 0x8, 0x5, 0xf, 0xf,
+0x8, 0xf, 0x3, 0x5, 0x6, 0xa, 0x8, 0xf,
+0xf, 0x3, 0xf, 0x5, 0xf, 0xf, 0xf, 0xf,
+0x3, 0xf, 0x5, 0x5, 0x5, 0x8, 0x5, 0xa,
+0x5, 0xa, 0x8, 0xd, 0xf, 0xc, 0x3, 0x3,
+// @fmt:on
+        };
+
+        static constexpr uint8_t AnchorTable3b[MaxPartitions] = {
+// @fmt:off
+0xf, 0x8, 0x8, 0x3, 0xf, 0xf, 0x3, 0x8,
+0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x8,
+0xf, 0x8, 0xf, 0x3, 0xf, 0x8, 0xf, 0x8,
+0x3, 0xf, 0x6, 0xa, 0xf, 0xf, 0xa, 0x8,
+0xf, 0x3, 0xf, 0xa, 0xa, 0x8, 0x9, 0xa,
+0x6, 0xf, 0x8, 0xf, 0x3, 0x6, 0x6, 0x8,
+0xf, 0x3, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+0xf, 0xf, 0xf, 0xf, 0x3, 0xf, 0xf, 0x8,
+// @fmt:on
+        };
+
+        struct Color {
+            struct RGB {
+                RGB() = default;
+
+                RGB(uint8_t r, uint8_t g, uint8_t b)
+                    : b(b), g(g), r(r) {}
+
+                RGB(int r, int g, int b)
+                    : b(static_cast<uint8_t>(b)), g(static_cast<uint8_t>(g)), r(static_cast<uint8_t>(r)) {}
+
+                RGB operator<<(int shift) const { return {r << shift, g << shift, b << shift}; }
+
+                RGB operator>>(int shift) const { return {r >> shift, g >> shift, b >> shift}; }
+
+                RGB operator|(int bits) const { return {r | bits, g | bits, b | bits}; }
+
+                RGB operator|(const RGB &rhs) const { return {r | rhs.r, g | rhs.g, b | rhs.b}; }
+
+                RGB operator+(const RGB &rhs) const { return {r + rhs.r, g + rhs.g, b + rhs.b}; }
+
+                uint8_t b;
+                uint8_t g;
+                uint8_t r;
+            };
+
+            RGB rgb;
+            uint8_t a;
+        };
+
+        static_assert(sizeof(Color) == 4, "Color size must be 4 bytes");
+
+        struct Block {
+            constexpr uint64_t Get(const Bitfield &bf) const {
+                uint64_t mask = (1ULL << bf.count) - 1;
+                if (bf.offset + bf.count <= 64) {
+                    return (low >> bf.offset) & mask;
+                }
+                if (bf.offset >= 64) {
+                    return (high >> (bf.offset - 64)) & mask;
+                }
+                return ((low >> bf.offset) | (high << (64 - bf.offset))) & mask;
+            }
+
+            const Mode &mode() const {
+                if ((low & 0b00000001) != 0) {
+                    return Modes[0];
+                }
+                if ((low & 0b00000010) != 0) {
+                    return Modes[1];
+                }
+                if ((low & 0b00000100) != 0) {
+                    return Modes[2];
+                }
+                if ((low & 0b00001000) != 0) {
+                    return Modes[3];
+                }
+                if ((low & 0b00010000) != 0) {
+                    return Modes[4];
+                }
+                if ((low & 0b00100000) != 0) {
+                    return Modes[5];
+                }
+                if ((low & 0b01000000) != 0) {
+                    return Modes[6];
+                }
+                if ((low & 0b10000000) != 0) {
+                    return Modes[7];
+                }
+                return Modes[8];  // Invalid mode
+            }
+
+            struct IndexInfo {
+                uint64_t value;
+                int numBits;
+            };
+
+            uint8_t interpolate(uint8_t e0, uint8_t e1, const IndexInfo &index) const {
+                static constexpr uint16_t weights2[] = {0, 21, 43, 64};
+                static constexpr uint16_t weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
+                static constexpr uint16_t weights4[] = {0, 4, 9, 13, 17, 21, 26, 30,
+                                                        34, 38, 43, 47, 51, 55, 60, 64};
+                static constexpr uint16_t const *weightsN[] = {
+                    nullptr, nullptr, weights2, weights3, weights4
+                };
+                auto weights = weightsN[index.numBits];
+                assert(weights != nullptr);
+                return (uint8_t) (((64 - weights[index.value]) * uint16_t(e0) + weights[index.value] * uint16_t(e1) + 32) >> 6);
+            }
+
+            void decode(uint8_t *dst, size_t dstX, size_t dstY, size_t dstWidth, size_t dstHeight, size_t dstPitch) const {
+                auto const &mode = this->mode();
+
+                if (mode.IDX < 0)  // Invalid mode:
+                {
+                    for (size_t y = 0; y < 4 && y + dstY < dstHeight; y++) {
+                        for (size_t x = 0; x < 4 && x + dstX < dstWidth; x++) {
+                            auto out = reinterpret_cast<Color *>(dst + sizeof(Color) * x + dstPitch * y);
+                            out->rgb = {0, 0, 0};
+                            out->a = 0;
+                        }
+                    }
+                    return;
+                }
+
+                using Endpoint = std::array<Color, 2>;
+                std::array<Endpoint, MaxSubsets> subsets;
+
+                for (size_t i = 0; i < mode.NS; i++) {
+                    auto &subset = subsets[i];
+                    subset[0].rgb.r = Get(mode.Red(i * 2 + 0));
+                    subset[0].rgb.g = Get(mode.Green(i * 2 + 0));
+                    subset[0].rgb.b = Get(mode.Blue(i * 2 + 0));
+                    subset[0].a = (mode.AB > 0) ? Get(mode.Alpha(i * 2 + 0)) : 255;
+
+                    subset[1].rgb.r = Get(mode.Red(i * 2 + 1));
+                    subset[1].rgb.g = Get(mode.Green(i * 2 + 1));
+                    subset[1].rgb.b = Get(mode.Blue(i * 2 + 1));
+                    subset[1].a = (mode.AB > 0) ? Get(mode.Alpha(i * 2 + 1)) : 255;
+                }
+
+                if (mode.SPB > 0) {
+                    auto pbit0 = Get(mode.SharedPBit0());
+                    auto pbit1 = Get(mode.SharedPBit1());
+                    subsets[0][0].rgb = (subsets[0][0].rgb << 1) | pbit0;
+                    subsets[0][1].rgb = (subsets[0][1].rgb << 1) | pbit0;
+                    subsets[1][0].rgb = (subsets[1][0].rgb << 1) | pbit1;
+                    subsets[1][1].rgb = (subsets[1][1].rgb << 1) | pbit1;
+                }
+
+                if (mode.EPB > 0) {
+                    for (size_t i = 0; i < mode.NS; i++) {
+                        auto &subset = subsets[i];
+                        auto pbit0 = Get(mode.EndpointPBit(i * 2 + 0));
+                        auto pbit1 = Get(mode.EndpointPBit(i * 2 + 1));
+                        subset[0].rgb = (subset[0].rgb << 1) | pbit0;
+                        subset[1].rgb = (subset[1].rgb << 1) | pbit1;
+                        if (mode.AB > 0) {
+                            subset[0].a = (subset[0].a << 1) | pbit0;
+                            subset[1].a = (subset[1].a << 1) | pbit1;
+                        }
+                    }
+                }
+
+                auto const colorBits = mode.CB + mode.SPB + mode.EPB;
+                auto const alphaBits = mode.AB + mode.SPB + mode.EPB;
+
+                for (size_t i = 0; i < mode.NS; i++) {
+                    auto &subset = subsets[i];
+                    subset[0].rgb = subset[0].rgb << (8 - colorBits);
+                    subset[1].rgb = subset[1].rgb << (8 - colorBits);
+                    subset[0].rgb = subset[0].rgb | (subset[0].rgb >> colorBits);
+                    subset[1].rgb = subset[1].rgb | (subset[1].rgb >> colorBits);
+
+                    if (mode.AB > 0) {
+                        subset[0].a = subset[0].a << (8 - alphaBits);
+                        subset[1].a = subset[1].a << (8 - alphaBits);
+                        subset[0].a = subset[0].a | (subset[0].a >> alphaBits);
+                        subset[1].a = subset[1].a | (subset[1].a >> alphaBits);
+                    }
+                }
+
+                int colorIndexBitOffset = 0;
+                int alphaIndexBitOffset = 0;
+                for (int y = 0; y < 4; y++) {
+                    for (int x = 0; x < 4; x++) {
+                        auto texelIdx = y * 4 + x;
+                        auto partitionIdx = Get(mode.Partition());
+                        assert(partitionIdx < MaxPartitions);
+                        auto subsetIdx = subsetIndex(mode, partitionIdx, texelIdx);
+                        assert(subsetIdx < MaxSubsets);
+                        auto const &subset = subsets[subsetIdx];
+
+                        auto anchorIdx = anchorIndex(mode, partitionIdx, subsetIdx);
+                        auto isAnchor = anchorIdx == texelIdx;
+                        auto colorIdx = colorIndex(mode, isAnchor, colorIndexBitOffset);
+                        auto alphaIdx = alphaIndex(mode, isAnchor, alphaIndexBitOffset);
+
+                        if (y + dstY >= dstHeight || x + dstX >= dstWidth) {
+                            // Don't be tempted to skip early at the loops:
+                            // The calls to colorIndex() and alphaIndex() adjust bit
+                            // offsets that need to be carefully tracked.
+                            continue;
+                        }
+
+                        Color output;
+                        // Note: We flip r and b channels past this point as the texture storage is BGR while the output is RGB
+                        output.rgb.r = interpolate(subset[0].rgb.b, subset[1].rgb.b, colorIdx);
+                        output.rgb.g = interpolate(subset[0].rgb.g, subset[1].rgb.g, colorIdx);
+                        output.rgb.b = interpolate(subset[0].rgb.r, subset[1].rgb.r, colorIdx);
+                        output.a = interpolate(subset[0].a, subset[1].a, alphaIdx);
+
+                        switch (Get(mode.Rotation())) {
+                            default:
+                                break;
+                            case 1:
+                                std::swap(output.a, output.rgb.b);
+                                break;
+                            case 2:
+                                std::swap(output.a, output.rgb.g);
+                                break;
+                            case 3:
+                                std::swap(output.a, output.rgb.r);
+                                break;
+                        }
+
+                        auto out = reinterpret_cast<Color *>(dst + sizeof(Color) * x + dstPitch * y);
+                        *out = output;
+                    }
+                }
+            }
+
+            int subsetIndex(const Mode &mode, int partitionIdx, int texelIndex) const {
+                switch (mode.NS) {
+                    default:
+                        return 0;
+                    case 2:
+                        return PartitionTable2[partitionIdx][texelIndex];
+                    case 3:
+                        return PartitionTable3[partitionIdx][texelIndex];
+                }
+            }
+
+            int anchorIndex(const Mode &mode, int partitionIdx, int subsetIdx) const {
+                // ARB_texture_compression_bptc states:
+                // "In partition zero, the anchor index is always index zero.
+                // In other partitions, the anchor index is specified by tables
+                // Table.A2 and Table.A3.""
+                // Note: This is really confusing - I believe they meant subset instead
+                // of partition here.
+                switch (subsetIdx) {
+                    default:
+                        return 0;
+                    case 1:
+                        return mode.NS == 2 ? AnchorTable2[partitionIdx] : AnchorTable3a[partitionIdx];
+                    case 2:
+                        return AnchorTable3b[partitionIdx];
+                }
+            }
+
+            IndexInfo colorIndex(const Mode &mode, bool isAnchor,
+                                 int &indexBitOffset) const {
+                // ARB_texture_compression_bptc states:
+                // "The index value for interpolating color comes from the secondary
+                // index for the texel if the format has an index selection bit and its
+                // value is one and from the primary index otherwise.""
+                auto idx = Get(mode.IndexSelection());
+                assert(idx <= 1);
+                bool secondary = idx == 1;
+                auto numBits = secondary ? mode.IB2 : mode.IB;
+                auto numReadBits = numBits - (isAnchor ? 1 : 0);
+                auto index =
+                    Get(secondary ? mode.SecondaryIndex(indexBitOffset, numReadBits)
+                                  : mode.PrimaryIndex(indexBitOffset, numReadBits));
+                indexBitOffset += numReadBits;
+                return {index, numBits};
+            }
+
+            IndexInfo alphaIndex(const Mode &mode, bool isAnchor,
+                                 int &indexBitOffset) const {
+                // ARB_texture_compression_bptc states:
+                // "The alpha index comes from the secondary index if the block has a
+                // secondary index and the block either doesn't have an index selection
+                // bit or that bit is zero and the primary index otherwise."
+                auto idx = Get(mode.IndexSelection());
+                assert(idx <= 1);
+                bool secondary = (mode.IB2 != 0) && (idx == 0);
+                auto numBits = secondary ? mode.IB2 : mode.IB;
+                auto numReadBits = numBits - (isAnchor ? 1 : 0);
+                auto index =
+                    Get(secondary ? mode.SecondaryIndex(indexBitOffset, numReadBits)
+                                  : mode.PrimaryIndex(indexBitOffset, numReadBits));
+                indexBitOffset += numReadBits;
+                return {index, numBits};
+            }
+
+            // Assumes little-endian
+            uint64_t low;
+            uint64_t high;
+        };
+
+    }  // namespace BC7
+}  // anonymous namespace
+
+namespace bcn {
+    constexpr size_t R8Bpp{1}; //!< The amount of bytes per pixel in R8
+    constexpr size_t R8g8Bpp{2}; //!< The amount of bytes per pixel in R8G8
+    constexpr size_t R8g8b8a8Bpp{4}; //!< The amount of bytes per pixel in R8G8B8A8
+    constexpr size_t R16g16b16a16Bpp{8}; //!< The amount of bytes per pixel in R16G16B16
+
+    void DecodeBc1(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height) {
+        const auto *color{reinterpret_cast<const BC_color *>(src)};
+        size_t pitch{R8g8b8a8Bpp * width};
+        color->decode(dst, x, y, width, height, pitch, R8g8b8a8Bpp, true, false);
+    }
+
+    void DecodeBc2(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height) {
+        const auto *alpha{reinterpret_cast<const BC_alpha *>(src)};
+        const auto *color{reinterpret_cast<const BC_color *>(src + 8)};
+        size_t pitch{R8g8b8a8Bpp * width};
+        color->decode(dst, x, y, width, height, pitch, R8g8b8a8Bpp, false, true);
+        alpha->decode(dst, x, y, width, height, pitch, R8g8b8a8Bpp);
+    }
+
+    void DecodeBc3(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height) {
+        const auto *alpha{reinterpret_cast<const BC_channel *>(src)};
+        const auto *color{reinterpret_cast<const BC_color *>(src + 8)};
+        size_t pitch{R8g8b8a8Bpp * width};
+        color->decode(dst, x, y, width, height, pitch, R8g8b8a8Bpp, false, true);
+        alpha->decode(dst, x, y, width, height, pitch, R8g8b8a8Bpp, 3, false);
+    }
+
+    void DecodeBc4(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned) {
+        const auto *red{reinterpret_cast<const BC_channel *>(src)};
+        size_t pitch{R8Bpp * width};
+        red->decode(dst, x, y, width, height, pitch, R8Bpp, 0, isSigned);
+    }
+
+    void DecodeBc5(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned) {
+        const auto *red{reinterpret_cast<const BC_channel *>(src)};
+        const auto *green{reinterpret_cast<const BC_channel *>(src + 8)};
+        size_t pitch{R8g8Bpp * width};
+        red->decode(dst, x, y, width, height, pitch, R8g8Bpp, 0, isSigned);
+        green->decode(dst, x, y, width, height, pitch, R8g8Bpp, 1, isSigned);
+    }
+
+    void DecodeBc6(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned) {
+        const auto *block{reinterpret_cast<const BC6H::Block *>(src)};
+        size_t pitch{R16g16b16a16Bpp * width};
+        block->decode(dst, x, y, width, height, pitch, R16g16b16a16Bpp, isSigned);
+    }
+
+    void DecodeBc7(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height) {
+        const auto *block{reinterpret_cast<const BC7::Block *>(src)};
+        size_t pitch{R8g8b8a8Bpp * width};
+        block->decode(dst, x, y, width, height, pitch);
+    }
+}

+ 43 - 0
externals/bc_decoder/bc_decoder.h

@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
+
+#pragma once
+
+#include <cstdint>
+
+namespace bcn {
+    /**
+     * @brief Decodes a BC1 encoded image to R8G8B8A8
+     */
+    void DecodeBc1(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
+
+    /**
+     * @brief Decodes a BC2 encoded image to R8G8B8A8
+     */
+    void DecodeBc2(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
+
+    /**
+     * @brief Decodes a BC3 encoded image to R8G8B8A8
+     */
+    void DecodeBc3(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
+
+    /**
+     * @brief Decodes a BC4 encoded image to R8
+     */
+    void DecodeBc4(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
+
+    /**
+     * @brief Decodes a BC5 encoded image to R8G8
+     */
+    void DecodeBc5(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
+
+    /**
+     * @brief Decodes a BC6 encoded image to R16G16B16A16
+     */
+    void DecodeBc6(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
+
+    /**
+     * @brief Decodes a BC7 encoded image to R8G8B8A8
+     */
+    void DecodeBc7(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
+}

+ 2 - 2
externals/nx_tzdb/CMakeLists.txt

@@ -7,7 +7,7 @@ add_library(nx_tzdb INTERFACE)
 
 find_program(GIT git)
 find_program(GNU_MAKE make)
-find_program(GNU_DATE date)
+find_program(DATE_PROG date)
 
 set(CAN_BUILD_NX_TZDB true)
 
@@ -17,7 +17,7 @@ endif()
 if (NOT GNU_MAKE)
     set(CAN_BUILD_NX_TZDB false)
 endif()
-if (NOT GNU_DATE)
+if (NOT DATE_PROG)
     set(CAN_BUILD_NX_TZDB false)
 endif()
 if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)

+ 1 - 1
externals/nx_tzdb/tzdb_to_nx

@@ -1 +1 @@
-Subproject commit 34df65eff295c2bd9ee9e6a077d662486d5cabb3
+Subproject commit 212afa2394a74226dcf1b7996a570aae17debb69

+ 1 - 0
externals/vma/VulkanMemoryAllocator

@@ -0,0 +1 @@
+Subproject commit 0aa3989b8f382f185fdf646cc83a1d16fa31d6ab

+ 8 - 0
externals/vma/vma.cpp

@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#define VMA_IMPLEMENTATION
+#define VMA_STATIC_VULKAN_FUNCTIONS 0
+#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
+
+#include <vk_mem_alloc.h>

+ 2 - 2
src/android/app/build.gradle.kts

@@ -26,7 +26,7 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn
 android {
     namespace = "org.yuzu.yuzu_emu"
 
-    compileSdkVersion = "android-33"
+    compileSdkVersion = "android-34"
     ndkVersion = "25.2.9519653"
 
     buildFeatures {
@@ -51,7 +51,7 @@ android {
         // TODO If this is ever modified, change application_id in strings.xml
         applicationId = "org.yuzu.yuzu_emu"
         minSdk = 30
-        targetSdk = 33
+        targetSdk = 34
         versionName = getGitVersion()
 
         // If you want to use autoVersion for the versionCode, create a property in local.properties

+ 4 - 1
src/android/app/src/main/AndroidManifest.xml

@@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
     <uses-permission android:name="android.permission.NFC" />
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
@@ -69,7 +70,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
                 android:resource="@xml/nfc_tech_filter" />
         </activity>
 
-        <service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/>
+        <service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
+            <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
+        </service>
 
         <provider
             android:name=".features.DocumentProvider"

+ 16 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt

@@ -286,7 +286,7 @@ object NativeLibrary {
     /**
      * Unpauses emulation from a paused state.
      */
-    external fun unPauseEmulation()
+    external fun unpauseEmulation()
 
     /**
      * Pauses emulation.
@@ -313,6 +313,21 @@ object NativeLibrary {
      */
     external fun isPaused(): Boolean
 
+    /**
+     * Mutes emulation sound
+     */
+    external fun muteAudio(): Boolean
+
+    /**
+     * Unmutes emulation sound
+     */
+    external fun unmuteAudio(): Boolean
+
+    /**
+     * Returns true if emulation audio is muted.
+     */
+    external fun isMuted(): Boolean
+
     /**
      * Returns the performance stats for the current game
      */

+ 63 - 2
src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt

@@ -27,13 +27,13 @@ import android.view.MotionEvent
 import android.view.Surface
 import android.view.View
 import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
 import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
 import androidx.navigation.fragment.NavHostFragment
-import kotlin.math.roundToInt
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
@@ -44,8 +44,10 @@ import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
 import org.yuzu.yuzu_emu.utils.ForegroundService
 import org.yuzu.yuzu_emu.utils.InputHandler
+import org.yuzu.yuzu_emu.utils.MemoryUtil
 import org.yuzu.yuzu_emu.utils.NfcReader
 import org.yuzu.yuzu_emu.utils.ThemeHelper
+import kotlin.math.roundToInt
 
 class EmulationActivity : AppCompatActivity(), SensorEventListener {
     private lateinit var binding: ActivityEmulationBinding
@@ -63,6 +65,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
 
     private val actionPause = "ACTION_EMULATOR_PAUSE"
     private val actionPlay = "ACTION_EMULATOR_PLAY"
+    private val actionMute = "ACTION_EMULATOR_MUTE"
+    private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
 
     private val settingsViewModel: SettingsViewModel by viewModels()
 
@@ -102,6 +106,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         inputHandler = InputHandler()
         inputHandler.initialize()
 
+        val memoryUtil = MemoryUtil(this)
+        if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
+            Toast.makeText(
+                this,
+                getString(
+                    R.string.device_memory_inadequate,
+                    memoryUtil.getDeviceRAM(),
+                    "8 ${getString(R.string.memory_gigabyte)}"
+                ),
+                Toast.LENGTH_LONG
+            ).show()
+        }
+
         // Start a foreground service to prevent the app from getting killed in the background
         val startIntent = Intent(this, ForegroundService::class.java)
         startForegroundService(startIntent)
@@ -305,6 +322,41 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
             pictureInPictureActions.add(pauseRemoteAction)
         }
 
+        if (NativeLibrary.isMuted()) {
+            val unmuteIcon = Icon.createWithResource(
+                this@EmulationActivity,
+                R.drawable.ic_pip_unmute
+            )
+            val unmutePendingIntent = PendingIntent.getBroadcast(
+                this@EmulationActivity,
+                R.drawable.ic_pip_unmute,
+                Intent(actionUnmute),
+                pendingFlags
+            )
+            val unmuteRemoteAction = RemoteAction(
+                unmuteIcon,
+                getString(R.string.unmute),
+                getString(R.string.unmute),
+                unmutePendingIntent
+            )
+            pictureInPictureActions.add(unmuteRemoteAction)
+        } else {
+            val muteIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_mute)
+            val mutePendingIntent = PendingIntent.getBroadcast(
+                this@EmulationActivity,
+                R.drawable.ic_pip_mute,
+                Intent(actionMute),
+                pendingFlags
+            )
+            val muteRemoteAction = RemoteAction(
+                muteIcon,
+                getString(R.string.mute),
+                getString(R.string.mute),
+                mutePendingIntent
+            )
+            pictureInPictureActions.add(muteRemoteAction)
+        }
+
         return this.apply { setActions(pictureInPictureActions) }
     }
 
@@ -322,10 +374,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
     private var pictureInPictureReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context?, intent: Intent) {
             if (intent.action == actionPlay) {
-                if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation()
+                if (NativeLibrary.isPaused()) NativeLibrary.unpauseEmulation()
             } else if (intent.action == actionPause) {
                 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
             }
+            if (intent.action == actionUnmute) {
+                if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
+            } else if (intent.action == actionMute) {
+                if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
+            }
             buildPictureInPictureParams()
         }
     }
@@ -339,6 +396,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
             IntentFilter().apply {
                 addAction(actionPause)
                 addAction(actionPlay)
+                addAction(actionMute)
+                addAction(actionUnmute)
             }.also {
                 registerReceiver(pictureInPictureReceiver, it)
             }
@@ -347,6 +406,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
                 unregisterReceiver(pictureInPictureReceiver)
             } catch (ignored: Exception) {
             }
+            // Always resume audio, since there is no UI button
+            if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
         }
     }
 

+ 1 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt

@@ -714,7 +714,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 State.PAUSED -> {
                     Log.debug("[EmulationFragment] Resuming emulation.")
                     NativeLibrary.surfaceChanged(surface)
-                    NativeLibrary.unPauseEmulation()
+                    NativeLibrary.unpauseEmulation()
                 }
 
                 else -> Log.debug("[EmulationFragment] Bug, run called while already running.")

+ 101 - 71
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt

@@ -68,79 +68,109 @@ class HomeSettingsFragment : Fragment() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         mainActivity = requireActivity() as MainActivity
 
-        val optionsList: MutableList<HomeSetting> = mutableListOf(
-            HomeSetting(
-                R.string.advanced_settings,
-                R.string.settings_description,
-                R.drawable.ic_settings
-            ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
-            HomeSetting(
-                R.string.open_user_folder,
-                R.string.open_user_folder_description,
-                R.drawable.ic_folder_open
-            ) { openFileManager() },
-            HomeSetting(
-                R.string.preferences_theme,
-                R.string.theme_and_color_description,
-                R.drawable.ic_palette
-            ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
-            HomeSetting(
-                R.string.install_gpu_driver,
-                R.string.install_gpu_driver_description,
-                R.drawable.ic_exit
-            ) { driverInstaller() },
-            HomeSetting(
-                R.string.install_amiibo_keys,
-                R.string.install_amiibo_keys_description,
-                R.drawable.ic_nfc
-            ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
-            HomeSetting(
-                R.string.install_game_content,
-                R.string.install_game_content_description,
-                R.drawable.ic_system_update_alt
-            ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) },
-            HomeSetting(
-                R.string.select_games_folder,
-                R.string.select_games_folder_description,
-                R.drawable.ic_add
-            ) {
-                mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
-            },
-            HomeSetting(
-                R.string.manage_save_data,
-                R.string.import_export_saves_description,
-                R.drawable.ic_save
-            ) {
-                ImportExportSavesFragment().show(
-                    parentFragmentManager,
-                    ImportExportSavesFragment.TAG
+        val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
+            add(
+                HomeSetting(
+                    R.string.advanced_settings,
+                    R.string.settings_description,
+                    R.drawable.ic_settings
+                ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
+            )
+            add(
+                HomeSetting(
+                    R.string.open_user_folder,
+                    R.string.open_user_folder_description,
+                    R.drawable.ic_folder_open
+                ) { openFileManager() }
+            )
+            add(
+                HomeSetting(
+                    R.string.preferences_theme,
+                    R.string.theme_and_color_description,
+                    R.drawable.ic_palette
+                ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
+            )
+
+            if (GpuDriverHelper.supportsCustomDriverLoading()) {
+                add(
+                    HomeSetting(
+                        R.string.install_gpu_driver,
+                        R.string.install_gpu_driver_description,
+                        R.drawable.ic_exit
+                    ) { driverInstaller() }
                 )
-            },
-            HomeSetting(
-                R.string.install_prod_keys,
-                R.string.install_prod_keys_description,
-                R.drawable.ic_unlock
-            ) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
-            HomeSetting(
-                R.string.install_firmware,
-                R.string.install_firmware_description,
-                R.drawable.ic_firmware
-            ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
-            HomeSetting(
-                R.string.share_log,
-                R.string.share_log_description,
-                R.drawable.ic_log
-            ) { shareLog() },
-            HomeSetting(
-                R.string.about,
-                R.string.about_description,
-                R.drawable.ic_info_outline
-            ) {
-                exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
-                parentFragmentManager.primaryNavigationFragment?.findNavController()
-                    ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
             }
-        )
+
+            add(
+                HomeSetting(
+                    R.string.install_amiibo_keys,
+                    R.string.install_amiibo_keys_description,
+                    R.drawable.ic_nfc
+                ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
+            )
+            add(
+                HomeSetting(
+                    R.string.install_game_content,
+                    R.string.install_game_content_description,
+                    R.drawable.ic_system_update_alt
+                ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
+            )
+            add(
+                HomeSetting(
+                    R.string.select_games_folder,
+                    R.string.select_games_folder_description,
+                    R.drawable.ic_add
+                ) {
+                    mainActivity.getGamesDirectory.launch(
+                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
+                    )
+                }
+            )
+            add(
+                HomeSetting(
+                    R.string.manage_save_data,
+                    R.string.import_export_saves_description,
+                    R.drawable.ic_save
+                ) {
+                    ImportExportSavesFragment().show(
+                        parentFragmentManager,
+                        ImportExportSavesFragment.TAG
+                    )
+                }
+            )
+            add(
+                HomeSetting(
+                    R.string.install_prod_keys,
+                    R.string.install_prod_keys_description,
+                    R.drawable.ic_unlock
+                ) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
+            )
+            add(
+                HomeSetting(
+                    R.string.install_firmware,
+                    R.string.install_firmware_description,
+                    R.drawable.ic_firmware
+                ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
+            )
+            add(
+                HomeSetting(
+                    R.string.share_log,
+                    R.string.share_log_description,
+                    R.drawable.ic_log
+                ) { shareLog() }
+            )
+            add(
+                HomeSetting(
+                    R.string.about,
+                    R.string.about_description,
+                    R.drawable.ic_info_outline
+                ) {
+                    exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+                    parentFragmentManager.primaryNavigationFragment?.findNavController()
+                        ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
+                }
+            )
+        }
 
         if (!BuildConfig.PREMIUM) {
             optionsList.add(

+ 62 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt

@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+
+class LongMessageDialogFragment : DialogFragment() {
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        val titleId = requireArguments().getInt(TITLE)
+        val description = requireArguments().getString(DESCRIPTION)
+        val helpLinkId = requireArguments().getInt(HELP_LINK)
+
+        val dialog = MaterialAlertDialogBuilder(requireContext())
+            .setPositiveButton(R.string.close, null)
+            .setTitle(titleId)
+            .setMessage(description)
+
+        if (helpLinkId != 0) {
+            dialog.setNeutralButton(R.string.learn_more) { _, _ ->
+                openLink(getString(helpLinkId))
+            }
+        }
+
+        return dialog.show()
+    }
+
+    private fun openLink(link: String) {
+        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
+        startActivity(intent)
+    }
+
+    companion object {
+        const val TAG = "LongMessageDialogFragment"
+
+        private const val TITLE = "Title"
+        private const val DESCRIPTION = "Description"
+        private const val HELP_LINK = "Link"
+
+        fun newInstance(
+            titleId: Int,
+            description: String,
+            helpLinkId: Int = 0
+        ): LongMessageDialogFragment {
+            val dialog = LongMessageDialogFragment()
+            val bundle = Bundle()
+            bundle.apply {
+                putInt(TITLE, titleId)
+                putString(DESCRIPTION, description)
+                putInt(HELP_LINK, helpLinkId)
+            }
+            dialog.arguments = bundle
+            return dialog
+        }
+    }
+}

+ 1 - 5
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt

@@ -29,7 +29,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.GamesViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
-import org.yuzu.yuzu_emu.utils.FileUtil
 
 class SearchFragment : Fragment() {
     private var _binding: FragmentSearchBinding? = null
@@ -128,10 +127,7 @@ class SearchFragment : Fragment() {
 
             R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
 
-            R.id.chip_retail -> baseList.filter {
-                FileUtil.hasExtension(it.path, "xci") ||
-                    FileUtil.hasExtension(it.path, "nsp")
-            }
+            R.id.chip_retail -> baseList.filter { !it.isHomebrew }
 
             else -> baseList
         }

+ 1 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt

@@ -43,7 +43,7 @@ class Game(
 
     companion object {
         val extensions: Set<String> = HashSet(
-            listOf(".xci", ".nsp", ".nca", ".nro")
+            listOf("xci", "nsp", "nca", "nro")
         )
     }
 }

+ 86 - 36
src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt

@@ -4,6 +4,7 @@
 package org.yuzu.yuzu_emu.ui.main
 
 import android.content.Intent
+import android.net.Uri
 import android.os.Bundle
 import android.view.View
 import android.view.ViewGroup.MarginLayoutParams
@@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
 import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
+import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.model.GamesViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -294,7 +296,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                 return@registerForActivityResult
             }
 
-            if (!FileUtil.hasExtension(result, "keys")) {
+            if (FileUtil.getExtension(result) != "keys") {
                 MessageDialogFragment.newInstance(
                     R.string.reading_keys_failure,
                     R.string.install_prod_keys_failure_extension_description
@@ -391,7 +393,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                 return@registerForActivityResult
             }
 
-            if (!FileUtil.hasExtension(result, "bin")) {
+            if (FileUtil.getExtension(result) != "bin") {
                 MessageDialogFragment.newInstance(
                     R.string.reading_keys_failure,
                     R.string.install_amiibo_keys_failure_extension_description
@@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
             }
         }
 
-    val installGameUpdate =
-        registerForActivityResult(ActivityResultContracts.OpenDocument()) {
-            if (it == null) {
-                return@registerForActivityResult
-            }
-
+    val installGameUpdate = registerForActivityResult(
+        ActivityResultContracts.OpenMultipleDocuments()
+    ) { documents: List<Uri> ->
+        if (documents.isNotEmpty()) {
             IndeterminateProgressDialogFragment.newInstance(
                 this@MainActivity,
                 R.string.install_game_content
             ) {
-                val result = NativeLibrary.installFileToNand(it.toString())
+                var installSuccess = 0
+                var installOverwrite = 0
+                var errorBaseGame = 0
+                var errorExtension = 0
+                var errorOther = 0
+                var errorTotal = 0
                 lifecycleScope.launch {
-                    withContext(Dispatchers.Main) {
-                        when (result) {
+                    documents.forEach {
+                        when (NativeLibrary.installFileToNand(it.toString())) {
                             NativeLibrary.InstallFileToNandResult.Success -> {
-                                Toast.makeText(
-                                    applicationContext,
-                                    R.string.install_game_content_success,
-                                    Toast.LENGTH_SHORT
-                                ).show()
+                                installSuccess += 1
                             }
 
                             NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
-                                Toast.makeText(
-                                    applicationContext,
-                                    R.string.install_game_content_success_overwrite,
-                                    Toast.LENGTH_SHORT
-                                ).show()
+                                installOverwrite += 1
                             }
 
                             NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
-                                MessageDialogFragment.newInstance(
-                                    R.string.install_game_content_failure,
-                                    R.string.install_game_content_failure_base
-                                ).show(supportFragmentManager, MessageDialogFragment.TAG)
+                                errorBaseGame += 1
                             }
 
                             NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
-                                MessageDialogFragment.newInstance(
-                                    R.string.install_game_content_failure,
-                                    R.string.install_game_content_failure_file_extension,
-                                    R.string.install_game_content_help_link
-                                ).show(supportFragmentManager, MessageDialogFragment.TAG)
+                                errorExtension += 1
                             }
 
                             else -> {
-                                MessageDialogFragment.newInstance(
-                                    R.string.install_game_content_failure,
-                                    R.string.install_game_content_failure_description,
-                                    R.string.install_game_content_help_link
-                                ).show(supportFragmentManager, MessageDialogFragment.TAG)
+                                errorOther += 1
                             }
                         }
                     }
+                    withContext(Dispatchers.Main) {
+                        val separator = System.getProperty("line.separator") ?: "\n"
+                        val installResult = StringBuilder()
+                        if (installSuccess > 0) {
+                            installResult.append(
+                                getString(
+                                    R.string.install_game_content_success_install,
+                                    installSuccess
+                                )
+                            )
+                            installResult.append(separator)
+                        }
+                        if (installOverwrite > 0) {
+                            installResult.append(
+                                getString(
+                                    R.string.install_game_content_success_overwrite,
+                                    installOverwrite
+                                )
+                            )
+                            installResult.append(separator)
+                        }
+                        errorTotal = errorBaseGame + errorExtension + errorOther
+                        if (errorTotal > 0) {
+                            installResult.append(separator)
+                            installResult.append(
+                                getString(
+                                    R.string.install_game_content_failed_count,
+                                    errorTotal
+                                )
+                            )
+                            installResult.append(separator)
+                            if (errorBaseGame > 0) {
+                                installResult.append(separator)
+                                installResult.append(
+                                    getString(R.string.install_game_content_failure_base)
+                                )
+                                installResult.append(separator)
+                            }
+                            if (errorExtension > 0) {
+                                installResult.append(separator)
+                                installResult.append(
+                                    getString(R.string.install_game_content_failure_file_extension)
+                                )
+                                installResult.append(separator)
+                            }
+                            if (errorOther > 0) {
+                                installResult.append(
+                                    getString(R.string.install_game_content_failure_description)
+                                )
+                                installResult.append(separator)
+                            }
+                            LongMessageDialogFragment.newInstance(
+                                R.string.install_game_content_failure,
+                                installResult.toString().trim(),
+                                R.string.install_game_content_help_link
+                            ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
+                        } else {
+                            LongMessageDialogFragment.newInstance(
+                                R.string.install_game_content_success,
+                                installResult.toString().trim()
+                            ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
+                        }
+                    }
                 }
-                return@newInstance result
+                return@newInstance installSuccess + installOverwrite + errorTotal
             }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
         }
+    }
 }

+ 8 - 26
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt

@@ -7,7 +7,6 @@ import android.content.Context
 import android.database.Cursor
 import android.net.Uri
 import android.provider.DocumentsContract
-import android.provider.OpenableColumns
 import androidx.documentfile.provider.DocumentFile
 import java.io.BufferedInputStream
 import java.io.File
@@ -185,19 +184,18 @@ object FileUtil {
 
     /**
      * Get file display name from given path
-     * @param path content uri path
+     * @param uri content uri
      * @return String display name
      */
-    fun getFilename(context: Context, path: String): String {
-        val resolver = context.contentResolver
+    fun getFilename(uri: Uri): String {
+        val resolver = YuzuApplication.appContext.contentResolver
         val columns = arrayOf(
             DocumentsContract.Document.COLUMN_DISPLAY_NAME
         )
         var filename = ""
         var c: Cursor? = null
         try {
-            val mUri = Uri.parse(path)
-            c = resolver.query(mUri, columns, null, null, null)
+            c = resolver.query(uri, columns, null, null, null)
             c!!.moveToNext()
             filename = c.getString(0)
         } catch (e: Exception) {
@@ -326,25 +324,9 @@ object FileUtil {
         }
     }
 
-    fun hasExtension(path: String, extension: String): Boolean =
-        path.substring(path.lastIndexOf(".") + 1).contains(extension)
-
-    fun hasExtension(uri: Uri, extension: String): Boolean {
-        val fileName: String?
-        val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
-        val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
-        cursor?.moveToFirst()
-
-        if (nameIndex == null) {
-            return false
-        }
-
-        fileName = cursor.getString(nameIndex)
-        cursor.close()
-
-        if (fileName == null) {
-            return false
-        }
-        return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
+    fun getExtension(uri: Uri): String {
+        val fileName = getFilename(uri)
+        return fileName.substring(fileName.lastIndexOf(".") + 1)
+            .lowercase()
     }
 }

+ 7 - 16
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt

@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
 import android.content.SharedPreferences
 import android.net.Uri
 import androidx.preference.PreferenceManager
-import java.util.*
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import org.yuzu.yuzu_emu.NativeLibrary
@@ -33,15 +32,9 @@ object GameHelper {
         val children = FileUtil.listFiles(context, gamesUri)
         for (file in children) {
             if (!file.isDirectory) {
-                val filename = file.uri.toString()
-                val extensionStart = filename.lastIndexOf('.')
-                if (extensionStart > 0) {
-                    val fileExtension = filename.substring(extensionStart)
-
-                    // Check that the file has an extension we care about before trying to read out of it.
-                    if (Game.extensions.contains(fileExtension.lowercase(Locale.getDefault()))) {
-                        games.add(getGame(filename))
-                    }
+                // Check that the file has an extension we care about before trying to read out of it.
+                if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
+                    games.add(getGame(file.uri))
                 }
             }
         }
@@ -59,21 +52,19 @@ object GameHelper {
         return games.toList()
     }
 
-    private fun getGame(filePath: String): Game {
+    private fun getGame(uri: Uri): Game {
+        val filePath = uri.toString()
         var name = NativeLibrary.getTitle(filePath)
 
         // If the game's title field is empty, use the filename.
         if (name.isEmpty()) {
-            name = filePath.substring(filePath.lastIndexOf("/") + 1)
+            name = FileUtil.getFilename(uri)
         }
         var gameId = NativeLibrary.getGameId(filePath)
 
         // If the game's ID field is empty, use the filename without extension.
         if (gameId.isEmpty()) {
-            gameId = filePath.substring(
-                filePath.lastIndexOf("/") + 1,
-                filePath.lastIndexOf(".")
-            )
+            gameId = name.substring(0, name.lastIndexOf("."))
         }
 
         val newGame = Game(

+ 2 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt

@@ -113,6 +113,8 @@ object GpuDriverHelper {
         initializeDriverParameters(context)
     }
 
+    external fun supportsCustomDriverLoading(): Boolean
+
     // Parse the custom driver metadata to retrieve the name.
     val customDriverName: String?
         get() {

+ 59 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt

@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.app.ActivityManager
+import android.content.Context
+import org.yuzu.yuzu_emu.R
+import java.util.Locale
+
+class MemoryUtil(val context: Context) {
+
+    private val Long.floatForm: String
+        get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
+
+    private fun bytesToSizeUnit(size: Long): String {
+        return when {
+            size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
+            size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}"
+            size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}"
+            size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}"
+            size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}"
+            size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}"
+            else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}"
+        }
+    }
+
+    private val totalMemory =
+        with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
+            val memInfo = ActivityManager.MemoryInfo()
+            getMemoryInfo(memInfo)
+            memInfo.totalMem
+        }
+
+    fun isLessThan(minimum: Int, size: Long): Boolean {
+        return when (size) {
+            Kb -> totalMemory < Mb && totalMemory < minimum
+            Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
+            Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
+            Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
+            Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
+            Eb -> totalMemory / Eb < minimum
+            else -> totalMemory < Kb && totalMemory < minimum
+        }
+    }
+
+    fun getDeviceRAM(): String {
+        return bytesToSizeUnit(totalMemory)
+    }
+
+    companion object {
+        const val Kb: Long = 1024
+        const val Mb = Kb * 1024
+        const val Gb = Mb * 1024
+        const val Tb = Gb * 1024
+        const val Pb = Tb * 1024
+        const val Eb = Pb * 1024
+    }
+}

+ 0 - 1
src/android/app/src/main/jni/CMakeLists.txt

@@ -14,7 +14,6 @@ add_library(yuzu-android SHARED
     id_cache.cpp
     id_cache.h
     native.cpp
-    native.h
 )
 
 set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})

+ 124 - 122
src/android/app/src/main/jni/native.cpp

@@ -14,6 +14,7 @@
 #include <android/api-level.h>
 #include <android/native_window_jni.h>
 #include <core/loader/nro.h>
+#include <jni.h>
 
 #include "common/detached_tasks.h"
 #include "common/dynamic_library.h"
@@ -59,6 +60,9 @@
 #include "video_core/rasterizer_interface.h"
 #include "video_core/renderer_base.h"
 
+#define jconst [[maybe_unused]] const auto
+#define jauto [[maybe_unused]] auto
+
 namespace {
 
 class EmulationSession final {
@@ -98,8 +102,8 @@ public:
     }
 
     int InstallFileToNand(std::string filename) {
-        const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
-                                  std::size_t block_size) {
+        jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
+                              std::size_t block_size) {
             if (src == nullptr || dest == nullptr) {
                 return false;
             }
@@ -108,10 +112,10 @@ public:
             }
 
             using namespace Common::Literals;
-            std::vector<u8> buffer(1_MiB);
+            [[maybe_unused]] std::vector<u8> buffer(1_MiB);
 
             for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
-                const auto read = src->Read(buffer.data(), buffer.size(), i);
+                jconst read = src->Read(buffer.data(), buffer.size(), i);
                 dest->Write(buffer.data(), read, i);
             }
             return true;
@@ -128,14 +132,14 @@ public:
         m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
         m_system.GetFileSystemController().CreateFactories(*m_vfs);
 
-        std::shared_ptr<FileSys::NSP> nsp;
+        [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
         if (filename.ends_with("nsp")) {
             nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
             if (nsp->IsExtractedType()) {
                 return InstallError;
             }
         } else if (filename.ends_with("xci")) {
-            const auto xci =
+            jconst xci =
                 std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
             nsp = xci->GetSecurePartitionNSP();
         } else {
@@ -150,7 +154,7 @@ public:
             return InstallError;
         }
 
-        const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
+        jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
             *nsp, true, copy_func);
 
         switch (res) {
@@ -233,10 +237,11 @@ public:
         m_system.SetFilesystem(m_vfs);
 
         // Initialize system.
-        auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
+        jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
         m_software_keyboard = android_keyboard.get();
         m_system.SetShuttingDown(false);
         m_system.ApplySettings();
+        Settings::LogSettings();
         m_system.HIDCore().ReloadInputDevices();
         m_system.SetAppletFrontendSet({
             nullptr,                     // Amiibo Settings
@@ -330,7 +335,7 @@ public:
 
         while (true) {
             {
-                std::unique_lock lock(m_mutex);
+                [[maybe_unused]] std::unique_lock lock(m_mutex);
                 if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
                                   [&]() { return !m_is_running; })) {
                     // Emulation halted.
@@ -362,7 +367,7 @@ public:
     }
 
     bool IsHandheldOnly() {
-        const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
+        jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
 
         if (npad_style_set.fullkey == 1) {
             return false;
@@ -375,17 +380,17 @@ public:
         return !Settings::values.use_docked_mode.GetValue();
     }
 
-    void SetDeviceType(int index, int type) {
-        auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+    void SetDeviceType([[maybe_unused]] int index, int type) {
+        jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
         controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
     }
 
-    void OnGamepadConnectEvent(int index) {
-        auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+    void OnGamepadConnectEvent([[maybe_unused]] int index) {
+        jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
 
         // Ensure that player1 is configured correctly and handheld disconnected
         if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
-            auto handheld =
+            jauto handheld =
                 m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
 
             if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
@@ -397,7 +402,8 @@ public:
 
         // Ensure that handheld is configured correctly and player 1 disconnected
         if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
-            auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+            jauto player1 =
+                m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
 
             if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
                 player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
@@ -411,8 +417,8 @@ public:
         }
     }
 
-    void OnGamepadDisconnectEvent(int index) {
-        auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+    void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
+        jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
         controller->Disconnect();
     }
 
@@ -428,7 +434,7 @@ private:
     };
 
     RomMetadata GetRomMetadata(const std::string& path) {
-        if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
+        if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
             return search->second;
         }
 
@@ -436,14 +442,14 @@ private:
     }
 
     RomMetadata CacheRomMetadata(const std::string& path) {
-        const auto file = Core::GetGameFileFromPath(m_vfs, path);
-        auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
+        jconst file = Core::GetGameFileFromPath(m_vfs, path);
+        jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
 
         RomMetadata entry;
         loader->ReadTitle(entry.title);
         loader->ReadIcon(entry.icon);
         if (loader->GetFileType() == Loader::FileType::NRO) {
-            auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
+            jauto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
             entry.isHomebrew = loader_nro->IsHomebrew();
         } else {
             entry.isHomebrew = false;
@@ -514,7 +520,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
 
     SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
 
-    const auto result = EmulationSession::GetInstance().InitializeEmulation(filepath);
+    jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
     if (result != Core::SystemResultStatus::Success) {
         return result;
     }
@@ -526,83 +532,104 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
 
 extern "C" {
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz,
-                                                          jobject surf) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance,
+                                                          [[maybe_unused]] jobject surf) {
     EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf));
     EmulationSession::GetInstance().SurfaceChanged();
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
-                                                            [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
     ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
     EmulationSession::GetInstance().SetNativeWindow(nullptr);
     EmulationSession::GetInstance().SurfaceChanged();
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
-                                                           [[maybe_unused]] jclass clazz,
-                                                           jstring j_directory) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
+                                                           [[maybe_unused]] jstring j_directory) {
     Common::FS::SetAppDirectory(GetJString(env, j_directory));
 }
 
-int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env,
-                                                            [[maybe_unused]] jclass clazz,
-                                                            jstring j_file) {
+int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
+                                                            [[maybe_unused]] jstring j_file) {
     return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
 }
 
-void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
-    JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
-    jstring custom_driver_name, jstring file_redirect_dir) {
+void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
+                                                                       jstring hook_lib_dir,
+                                                                       jstring custom_driver_dir,
+                                                                       jstring custom_driver_name,
+                                                                       jstring file_redirect_dir) {
     EmulationSession::GetInstance().InitializeGpuDriver(
         GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir),
         GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz) {
+[[maybe_unused]] static bool CheckKgslPresent() {
+    constexpr auto KgslPath{"/dev/kgsl-3d0"};
+
+    return access(KgslPath, F_OK) == 0;
+}
+
+[[maybe_unused]] bool SupportsCustomDriver() {
+    return android_get_device_api_level() >= 28 && CheckKgslPresent();
+}
+
+jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
+    JNIEnv* env, jobject instance) {
+#ifdef ARCHITECTURE_arm64
+    // If the KGSL device exists custom drivers can be loaded using adrenotools
+    return SupportsCustomDriver();
+#else
+    return false;
+#endif
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) {
     Core::Crypto::KeyManager::Instance().ReloadKeys();
     return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env,
-                                                            [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) {
     EmulationSession::GetInstance().UnPauseEmulation();
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation(JNIEnv* env, jclass clazz) {
     EmulationSession::GetInstance().PauseEmulation();
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
-                                                         [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass clazz) {
     EmulationSession::GetInstance().HaltEmulation();
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata([[maybe_unused]] JNIEnv* env,
-                                                            [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
     EmulationSession::GetInstance().ResetRomMetadata();
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env,
-                                                         [[maybe_unused]] jclass clazz) {
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
     return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env,
-                                                        [[maybe_unused]] jclass clazz) {
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass clazz) {
     return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
-                                                              [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
+    Settings::values.audio_muted = true;
+}
+
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
+    Settings::values.audio_muted = false;
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
+    return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
     return EmulationSession::GetInstance().IsHandheldOnly();
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env,
-                                                             [[maybe_unused]] jclass clazz,
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz,
                                                              jint j_device, jint j_type) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
@@ -610,8 +637,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JN
     return static_cast<jboolean>(true);
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env,
-                                                                     [[maybe_unused]] jclass clazz,
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz,
                                                                      jint j_device) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
@@ -619,17 +645,16 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unu
     return static_cast<jboolean>(true);
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
-    [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device) {
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz,
+                                                                        jint j_device) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
     }
     return static_cast<jboolean>(true);
 }
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
-                                                                    [[maybe_unused]] jclass clazz,
-                                                                    [[maybe_unused]] jint j_device,
-                                                                    jint j_button, jint action) {
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
+                                                                    jint j_device, jint j_button,
+                                                                    jint action) {
     if (EmulationSession::GetInstance().IsRunning()) {
         // Ensure gamepad is connected
         EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
@@ -639,8 +664,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unus
     return static_cast<jboolean>(true);
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_unused]] JNIEnv* env,
-                                                                      [[maybe_unused]] jclass clazz,
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz,
                                                                       jint j_device, jint stick_id,
                                                                       jfloat x, jfloat y) {
     if (EmulationSession::GetInstance().IsRunning()) {
@@ -650,9 +674,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_un
 }
 
 jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
-    [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device,
-    jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y, jfloat gyro_z, jfloat accel_x,
-    jfloat accel_y, jfloat accel_z) {
+    JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y,
+    jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
             j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
@@ -660,8 +683,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
     return static_cast<jboolean>(true);
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNIEnv* env,
-                                                            [[maybe_unused]] jclass clazz,
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz,
                                                             jbyteArray j_data) {
     jboolean isCopy{false};
     std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
@@ -673,108 +695,92 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNI
     return static_cast<jboolean>(true);
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag([[maybe_unused]] JNIEnv* env,
-                                                              [[maybe_unused]] jclass clazz) {
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().Window().OnRemoveNfcTag();
     }
     return static_cast<jboolean>(true);
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed([[maybe_unused]] JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz, jint id,
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id,
                                                           jfloat x, jfloat y) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
     }
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env,
-                                                        [[maybe_unused]] jclass clazz, jint id,
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id,
                                                         jfloat x, jfloat y) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
     }
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased([[maybe_unused]] JNIEnv* env,
-                                                           [[maybe_unused]] jclass clazz, jint id) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) {
     if (EmulationSession::GetInstance().IsRunning()) {
         EmulationSession::GetInstance().Window().OnTouchReleased(id);
     }
 }
 
-jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon([[maybe_unused]] JNIEnv* env,
-                                                         [[maybe_unused]] jclass clazz,
-                                                         [[maybe_unused]] jstring j_filename) {
-    auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
+jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
+                                                         jstring j_filename) {
+    jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
     jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
     env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
                             reinterpret_cast<jbyte*>(icon_data.data()));
     return icon;
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle([[maybe_unused]] JNIEnv* env,
-                                                       [[maybe_unused]] jclass clazz,
-                                                       [[maybe_unused]] jstring j_filename) {
-    auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
+                                                       jstring j_filename) {
+    jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
     return env->NewStringUTF(title.c_str());
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription([[maybe_unused]] JNIEnv* env,
-                                                             [[maybe_unused]] jclass clazz,
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
                                                              jstring j_filename) {
     return j_filename;
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId([[maybe_unused]] JNIEnv* env,
-                                                        [[maybe_unused]] jclass clazz,
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
                                                         jstring j_filename) {
     return j_filename;
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions([[maybe_unused]] JNIEnv* env,
-                                                         [[maybe_unused]] jclass clazz,
-                                                         [[maybe_unused]] jstring j_filename) {
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
+                                                         jstring j_filename) {
     return env->NewStringUTF("");
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv* env,
-                                                         [[maybe_unused]] jclass clazz,
-                                                         [[maybe_unused]] jstring j_filename) {
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
+                                                         jstring j_filename) {
     return env->NewStringUTF("");
 }
 
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz,
-                                                          [[maybe_unused]] jstring j_filename) {
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
+                                                          jstring j_filename) {
     return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
-    [[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
     // Create the default config.ini.
     Config{};
     // Initialize the emulated system.
     EmulationSession::GetInstance().System().Initialize();
 }
 
-jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore([[maybe_unused]] JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz) {
+jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
     return {};
 }
 
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
-    [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_file,
-    [[maybe_unused]] jstring j_savestate, [[maybe_unused]] jboolean j_delete_savestate) {}
+    JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
     Config{};
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JNIEnv* env,
-                                                             [[maybe_unused]] jclass clazz,
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
                                                              jstring j_game_id, jstring j_section,
                                                              jstring j_key) {
     std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
@@ -788,8 +794,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JN
     return env->NewStringUTF("");
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEnv* env,
-                                                          [[maybe_unused]] jclass clazz,
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
                                                           jstring j_game_id, jstring j_section,
                                                           jstring j_key, jstring j_value) {
     std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
@@ -803,20 +808,18 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEn
     env->ReleaseStringUTFChars(j_value, value.data());
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni([[maybe_unused]] JNIEnv* env,
-                                                       [[maybe_unused]] jclass clazz,
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
                                                        jstring j_game_id) {
     std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
 
     env->ReleaseStringUTFChars(j_game_id, game_id.data());
 }
 
-jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]] JNIEnv* env,
-                                                                [[maybe_unused]] jclass clazz) {
+jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
     jdoubleArray j_stats = env->NewDoubleArray(4);
 
     if (EmulationSession::GetInstance().IsRunning()) {
-        const auto results = EmulationSession::GetInstance().PerfStats();
+        jconst results = EmulationSession::GetInstance().PerfStats();
 
         // Converting the structure into an array makes it easier to pass it to the frontend
         double stats[4] = {results.system_fps, results.average_game_fps, results.frametime,
@@ -828,11 +831,11 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]]
     return j_stats;
 }
 
-void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(
-    [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_path) {}
+void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
+                                                                           jclass clazz,
+                                                                           jstring j_path) {}
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unused]] JNIEnv* env,
-                                                                    [[maybe_unused]] jclass clazz,
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz,
                                                                     jstring j_path) {
     const std::string path = GetJString(env, j_path);
 
@@ -843,8 +846,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unus
     }
 }
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv* env,
-                                                         [[maybe_unused]] jclass clazz) {
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo(JNIEnv* env, jclass clazz) {
     LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc);
     LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
 }

+ 0 - 165
src/android/app/src/main/jni/native.h

@@ -1,165 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <jni.h>
-
-// Function calls from the Java side
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation(JNIEnv* env,
-                                                                              jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIEnv* env,
-                                                                            jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env,
-                                                                           jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env,
-                                                                              jclass clazz);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
-                                                                           jclass clazz);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env,
-                                                                                jclass clazz);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env,
-                                                                               jclass clazz,
-                                                                               jstring j_device,
-                                                                               jstring j_type);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(
-    JNIEnv* env, jclass clazz, jstring j_device);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
-    JNIEnv* env, jclass clazz, jstring j_device);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
-    JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMoveEvent(
-    JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadAxisEvent(
-    JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env,
-                                                                              jclass clazz,
-                                                                              jbyteArray j_data);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env,
-                                                                                jclass clazz);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JNIEnv* env,
-                                                                              jclass clazz,
-                                                                              jfloat x, jfloat y,
-                                                                              jboolean pressed);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz,
-                                                                          jfloat x, jfloat y);
-
-JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env,
-                                                                           jclass clazz,
-                                                                           jstring j_file);
-
-JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz,
-                                                                         jstring j_filename);
-
-JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription(JNIEnv* env,
-                                                                               jclass clazz,
-                                                                               jstring j_filename);
-
-JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGameId(JNIEnv* env, jclass clazz,
-                                                                          jstring j_filename);
-
-JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetRegions(JNIEnv* env,
-                                                                           jclass clazz,
-                                                                           jstring j_filename);
-
-JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetCompany(JNIEnv* env,
-                                                                           jclass clazz,
-                                                                           jstring j_filename);
-
-JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGitRevision(JNIEnv* env,
-                                                                               jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetAppDirectory(JNIEnv* env,
-                                                                             jclass clazz,
-                                                                             jstring j_directory);
-
-JNIEXPORT void JNICALL
-Java_org_yuzu_yuzu_1emu_NativeLibrary_Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeGpuDriver(
-    JNIEnv* env, jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
-    jstring custom_driver_name, jstring file_redirect_dir);
-
-JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env,
-                                                                            jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_SetSysDirectory(
-    JNIEnv* env, jclass clazz, jstring path_);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetSysDirectory(JNIEnv* env,
-                                                                             jclass clazz,
-                                                                             jstring path);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeEmulation(JNIEnv* env,
-                                                                                 jclass clazz);
-
-JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_DefaultCPUCore(JNIEnv* env,
-                                                                            jclass clazz);
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetProfiling(JNIEnv* env, jclass clazz,
-                                                                          jboolean enable);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_WriteProfileResults(JNIEnv* env,
-                                                                                 jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_NotifyOrientationChange(
-    JNIEnv* env, jclass clazz, jint layout_option, jint rotation);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2(
-    JNIEnv* env, jclass clazz, jstring j_path);
-
-JNIEXPORT void JNICALL
-Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z(
-    JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env,
-                                                                            jclass clazz,
-                                                                            jobject surf);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env,
-                                                                              jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitGameIni(JNIEnv* env, jclass clazz,
-                                                                         jstring j_game_id);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadSettings(JNIEnv* env,
-                                                                            jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetUserSetting(
-    JNIEnv* env, jclass clazz, jstring j_game_id, jstring j_section, jstring j_key,
-    jstring j_value);
-
-JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetUserSetting(
-    JNIEnv* env, jclass clazz, jstring game_id, jstring section, jstring key);
-
-JNIEXPORT jdoubleArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
-                                                                                  jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_LogDeviceInfo(JNIEnv* env,
-                                                                           jclass clazz);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardText(
-    JNIEnv* env, jclass clazz, jstring j_text);
-
-JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardInput(
-    JNIEnv* env, jclass clazz, jint j_key_code);
-
-#ifdef __cplusplus
-}
-#endif

+ 9 - 0
src/android/app/src/main/res/drawable/ic_pip_mute.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M7,9v6h4l5,5V4l-5,5H7z" />
+</vector>

+ 9 - 0
src/android/app/src/main/res/drawable/ic_pip_unmute.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z" />
+</vector>

+ 20 - 6
src/android/app/src/main/res/values/strings.xml

@@ -104,12 +104,14 @@
     <string name="share_log_missing">No log file found</string>
     <string name="install_game_content">Install game content</string>
     <string name="install_game_content_description">Install game updates or DLC</string>
-    <string name="install_game_content_failure">Error installing file to NAND</string>
-    <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string>
-    <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string>
-    <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string>
-    <string name="install_game_content_success">Game content installed successfully</string>
-    <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
+    <string name="install_game_content_failure">Error installing file(s) to NAND</string>
+    <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
+    <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
+    <string name="install_game_content_failure_file_extension">Only NSP and XCI content is supported. Please verify the game content(s) are valid.</string>
+    <string name="install_game_content_failed_count">%1$d installation error(s)</string>
+    <string name="install_game_content_success">Game content(s) installed successfully</string>
+    <string name="install_game_content_success_install">%1$d installed successfully</string>
+    <string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
     <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
 
     <!-- About screen strings -->
@@ -270,6 +272,7 @@
     <string name="fatal_error">Fatal Error</string>
     <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
     <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
+    <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
 
     <!-- Region Names -->
     <string name="region_japan">Japan</string>
@@ -300,6 +303,15 @@
     <string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
     <string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
 
+    <!-- Memory Sizes -->
+    <string name="memory_byte">Byte</string>
+    <string name="memory_kilobyte">KB</string>
+    <string name="memory_megabyte">MB</string>
+    <string name="memory_gigabyte">GB</string>
+    <string name="memory_terabyte">TB</string>
+    <string name="memory_petabyte">PB</string>
+    <string name="memory_exabyte">EB</string>
+
     <!-- Renderer APIs -->
     <string name="renderer_vulkan">Vulkan</string>
     <string name="renderer_none">None</string>
@@ -387,6 +399,8 @@
     <string name="picture_in_picture_description">Minimize window when placed in the background</string>
     <string name="pause">Pause</string>
     <string name="play">Play</string>
+    <string name="mute">Mute</string>
+    <string name="unmute">Unmute</string>
 
     <!-- Licenses screen strings -->
     <string name="licenses">Licenses</string>

+ 3 - 0
src/android/gradle.properties

@@ -15,3 +15,6 @@ android.useAndroidX=true
 kotlin.code.style=official
 kotlin.parallel.tasks.in.project=true
 android.defaults.buildfeatures.buildconfig=true
+
+# Android Gradle plugin 8.0.2
+android.suppressUnsupportedCompileSdk=34

+ 5 - 3
src/audio_core/device/audio_buffers.h

@@ -7,6 +7,7 @@
 #include <mutex>
 #include <span>
 #include <vector>
+#include <boost/container/static_vector.hpp>
 
 #include "audio_buffer.h"
 #include "audio_core/device/device_session.h"
@@ -48,7 +49,7 @@ public:
      *
      * @param out_buffers - The buffers which were registered.
      */
-    void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) {
+    void RegisterBuffers(boost::container::static_vector<AudioBuffer, N>& out_buffers) {
         std::scoped_lock l{lock};
         const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
                                        BufferAppendLimit - registered_count)};
@@ -162,7 +163,8 @@ public:
      * @param max_buffers     - Maximum number of buffers to released.
      * @return The number of buffers released.
      */
-    u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) {
+    u32 GetRegisteredAppendedBuffers(
+        boost::container::static_vector<AudioBuffer, N>& buffers_flushed, u32 max_buffers) {
         std::scoped_lock l{lock};
         if (registered_count + appended_count == 0) {
             return 0;
@@ -270,7 +272,7 @@ public:
      */
     bool FlushBuffers(u32& buffers_released) {
         std::scoped_lock l{lock};
-        std::vector<AudioBuffer> buffers_flushed{};
+        boost::container::static_vector<AudioBuffer, N> buffers_flushed{};
 
         buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit);
 

+ 6 - 6
src/audio_core/device/device_session.cpp

@@ -79,7 +79,7 @@ void DeviceSession::ClearBuffers() {
     }
 }
 
-void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
+void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
     for (const auto& buffer : buffers) {
         Sink::SinkBuffer new_buffer{
             .frames = buffer.size / (channel_count * sizeof(s16)),
@@ -88,13 +88,13 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
             .consumed = false,
         };
 
+        tmp_samples.resize_destructive(buffer.size / sizeof(s16));
         if (type == Sink::StreamType::In) {
-            std::vector<s16> samples{};
-            stream->AppendBuffer(new_buffer, samples);
+            stream->AppendBuffer(new_buffer, tmp_samples);
         } else {
-            std::vector<s16> samples(buffer.size / sizeof(s16));
-            system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
-            stream->AppendBuffer(new_buffer, samples);
+            system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, tmp_samples.data(),
+                                                       buffer.size);
+            stream->AppendBuffer(new_buffer, tmp_samples);
         }
     }
 }

+ 4 - 3
src/audio_core/device/device_session.h

@@ -10,6 +10,7 @@
 
 #include "audio_core/common/common.h"
 #include "audio_core/sink/sink.h"
+#include "common/scratch_buffer.h"
 #include "core/hle/service/audio/errors.h"
 
 namespace Core {
@@ -62,7 +63,7 @@ public:
      *
      * @param buffers - The buffers to play.
      */
-    void AppendBuffers(std::span<const AudioBuffer> buffers) const;
+    void AppendBuffers(std::span<const AudioBuffer> buffers);
 
     /**
      * (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
@@ -146,8 +147,8 @@ private:
     std::shared_ptr<Core::Timing::EventType> thread_event;
     /// Is this session initialised?
     bool initialized{};
-    /// Buffer queue
-    std::vector<AudioBuffer> buffer_queue{};
+    /// Temporary sample buffer
+    Common::ScratchBuffer<s16> tmp_samples{};
 };
 
 } // namespace AudioCore

+ 3 - 2
src/audio_core/in/audio_in_system.cpp

@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include <mutex>
+
 #include "audio_core/audio_event.h"
 #include "audio_core/audio_manager.h"
 #include "audio_core/in/audio_in_system.h"
@@ -89,7 +90,7 @@ Result System::Start() {
     session->Start();
     state = State::Started;
 
-    std::vector<AudioBuffer> buffers_to_flush{};
+    boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
     buffers.RegisterBuffers(buffers_to_flush);
     session->AppendBuffers(buffers_to_flush);
     session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
@@ -134,7 +135,7 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
 
 void System::RegisterBuffers() {
     if (state == State::Started) {
-        std::vector<AudioBuffer> registered_buffers{};
+        boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
         buffers.RegisterBuffers(registered_buffers);
         session->AppendBuffers(registered_buffers);
     }

+ 2 - 2
src/audio_core/out/audio_out_system.cpp

@@ -89,7 +89,7 @@ Result System::Start() {
     session->Start();
     state = State::Started;
 
-    std::vector<AudioBuffer> buffers_to_flush{};
+    boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
     buffers.RegisterBuffers(buffers_to_flush);
     session->AppendBuffers(buffers_to_flush);
     session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
@@ -134,7 +134,7 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
 
 void System::RegisterBuffers() {
     if (state == State::Started) {
-        std::vector<AudioBuffer> registered_buffers{};
+        boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
         buffers.RegisterBuffers(registered_buffers);
         session->AppendBuffers(registered_buffers);
     }

+ 0 - 1
src/audio_core/renderer/adsp/adsp.cpp

@@ -7,7 +7,6 @@
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core_timing.h"
-#include "core/core_timing_util.h"
 #include "core/memory.h"
 
 namespace AudioCore::AudioRenderer::ADSP {

+ 2 - 3
src/audio_core/renderer/adsp/audio_renderer.cpp

@@ -13,7 +13,6 @@
 #include "common/thread.h"
 #include "core/core.h"
 #include "core/core_timing.h"
-#include "core/core_timing_util.h"
 
 MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
 
@@ -144,6 +143,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
 
     mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
 
+    // 0.12 seconds (2304000 / 19200000)
     constexpr u64 max_process_time{2'304'000ULL};
 
     while (!stop_token.stop_requested()) {
@@ -184,8 +184,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
                     u64 max_time{max_process_time};
                     if (index == 1 && command_buffer.applet_resource_user_id ==
                                           mailbox->GetCommandBuffer(0).applet_resource_user_id) {
-                        max_time = max_process_time -
-                                   Core::Timing::CyclesToNs(render_times_taken[0]).count();
+                        max_time = max_process_time - render_times_taken[0];
                         if (render_times_taken[0] > max_process_time) {
                             max_time = 0;
                         }

+ 0 - 1
src/audio_core/renderer/adsp/command_list_processor.cpp

@@ -9,7 +9,6 @@
 #include "common/settings.h"
 #include "core/core.h"
 #include "core/core_timing.h"
-#include "core/core_timing_util.h"
 #include "core/memory.h"
 
 namespace AudioCore::AudioRenderer::ADSP {

+ 11 - 12
src/audio_core/renderer/command/data_source/decode.cpp

@@ -8,6 +8,7 @@
 #include "audio_core/renderer/command/resample/resample.h"
 #include "common/fixed_point.h"
 #include "common/logging/log.h"
+#include "common/scratch_buffer.h"
 #include "core/memory.h"
 
 namespace AudioCore::AudioRenderer {
@@ -27,6 +28,7 @@ constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
 template <typename T>
 static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
                      const DecodeArg& req) {
+    std::array<T, TempBufferSize> tmp_samples{};
     constexpr s32 min{std::numeric_limits<s16>::min()};
     constexpr s32 max{std::numeric_limits<s16>::max()};
 
@@ -49,18 +51,17 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
         const u64 size{channel_count * samples_to_decode};
         const u64 size_bytes{size * sizeof(T)};
 
-        std::vector<T> samples(size);
-        memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
+        memory.ReadBlockUnsafe(source, tmp_samples.data(), size_bytes);
 
         if constexpr (std::is_floating_point_v<T>) {
             for (u32 i = 0; i < samples_to_decode; i++) {
-                auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
+                auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
                                              std::numeric_limits<s16>::max())};
                 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
             }
         } else {
             for (u32 i = 0; i < samples_to_decode; i++) {
-                out_buffer[i] = samples[i * channel_count + req.target_channel];
+                out_buffer[i] = tmp_samples[i * channel_count + req.target_channel];
             }
         }
     } break;
@@ -73,17 +74,16 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
         }
 
         const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
-        std::vector<T> samples(samples_to_decode);
-        memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
+        memory.ReadBlockUnsafe(source, tmp_samples.data(), samples_to_decode * sizeof(T));
 
         if constexpr (std::is_floating_point_v<T>) {
             for (u32 i = 0; i < samples_to_decode; i++) {
-                auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
+                auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
                                              std::numeric_limits<s16>::max())};
                 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
             }
         } else {
-            std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
+            std::memcpy(out_buffer.data(), tmp_samples.data(), samples_to_decode * sizeof(s16));
         }
         break;
     }
@@ -101,6 +101,7 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
  */
 static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
                        const DecodeArg& req) {
+    std::array<u8, TempBufferSize> wavebuffer{};
     constexpr u32 SamplesPerFrame{14};
     constexpr u32 NibblesPerFrame{16};
 
@@ -138,9 +139,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
     }
 
     const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
-    std::vector<u8> wavebuffer(size);
-    memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
-                           wavebuffer.size());
+    memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), size);
 
     auto context{req.adpcm_context};
     auto header{context->header};
@@ -258,7 +257,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
     u32 offset{voice_state.offset};
 
     auto output_buffer{args.output};
-    std::vector<s16> temp_buffer(TempBufferSize, 0);
+    std::array<s16, TempBufferSize> temp_buffer{};
 
     while (remaining_sample_count > 0) {
         const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};

+ 4 - 4
src/audio_core/renderer/command/effect/compressor.cpp

@@ -44,8 +44,8 @@ static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2&
 
 static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
                                   CompressorInfo::State& state, bool enabled,
-                                  std::vector<std::span<const s32>> input_buffers,
-                                  std::vector<std::span<s32>> output_buffers, u32 sample_count) {
+                                  std::span<std::span<const s32>> input_buffers,
+                                  std::span<std::span<s32>> output_buffers, u32 sample_count) {
     if (enabled) {
         auto state_00{state.unk_00};
         auto state_04{state.unk_04};
@@ -124,8 +124,8 @@ void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
 }
 
 void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
-    std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
-    std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+    std::array<std::span<const s32>, MaxChannels> input_buffers{};
+    std::array<std::span<s32>, MaxChannels> output_buffers{};
 
     for (s16 i = 0; i < parameter.channel_count; i++) {
         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

+ 7 - 7
src/audio_core/renderer/command/effect/delay.cpp

@@ -51,7 +51,7 @@ static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
         state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
         state.delay_lines[channel].sample_count = sample_count.to_int_floor();
         state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
-        if (state.delay_lines[channel].buffer.size() == 0) {
+        if (state.delay_lines[channel].sample_count == 0) {
             state.delay_lines[channel].buffer.push_back(0);
         }
         state.delay_lines[channel].buffer_pos = 0;
@@ -74,8 +74,8 @@ static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
  */
 template <size_t NumChannels>
 static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
-                       std::vector<std::span<const s32>>& inputs,
-                       std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+                       std::span<std::span<const s32>> inputs, std::span<std::span<s32>> outputs,
+                       const u32 sample_count) {
     for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
         std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
         for (u32 channel = 0; channel < NumChannels; channel++) {
@@ -153,8 +153,8 @@ static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::St
  * @param sample_count - Number of samples to process.
  */
 static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
-                             const bool enabled, std::vector<std::span<const s32>>& inputs,
-                             std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+                             const bool enabled, std::span<std::span<const s32>> inputs,
+                             std::span<std::span<s32>> outputs, const u32 sample_count) {
 
     if (!IsChannelCountValid(params.channel_count)) {
         LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
@@ -208,8 +208,8 @@ void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proce
 }
 
 void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
-    std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
-    std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+    std::array<std::span<const s32>, MaxChannels> input_buffers{};
+    std::array<std::span<s32>, MaxChannels> output_buffers{};
 
     for (s16 i = 0; i < parameter.channel_count; i++) {
         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

+ 2 - 2
src/audio_core/renderer/command/effect/i3dl2_reverb.cpp

@@ -408,8 +408,8 @@ void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
 }
 
 void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
-    std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
-    std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+    std::array<std::span<const s32>, MaxChannels> input_buffers{};
+    std::array<std::span<s32>, MaxChannels> output_buffers{};
 
     for (u32 i = 0; i < parameter.channel_count; i++) {
         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

+ 6 - 6
src/audio_core/renderer/command/effect/light_limiter.cpp

@@ -47,8 +47,8 @@ static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersio
  */
 static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
                                     LightLimiterInfo::State& state, const bool enabled,
-                                    std::vector<std::span<const s32>>& inputs,
-                                    std::vector<std::span<s32>>& outputs, const u32 sample_count,
+                                    std::span<std::span<const s32>> inputs,
+                                    std::span<std::span<s32>> outputs, const u32 sample_count,
                                     LightLimiterInfo::StatisticsInternal* statistics) {
     constexpr s64 min{std::numeric_limits<s32>::min()};
     constexpr s64 max{std::numeric_limits<s32>::max()};
@@ -147,8 +147,8 @@ void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListP
 }
 
 void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
-    std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
-    std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+    std::array<std::span<const s32>, MaxChannels> input_buffers{};
+    std::array<std::span<s32>, MaxChannels> output_buffers{};
 
     for (u32 i = 0; i < parameter.channel_count; i++) {
         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
@@ -190,8 +190,8 @@ void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListP
 }
 
 void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
-    std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
-    std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+    std::array<std::span<const s32>, MaxChannels> input_buffers{};
+    std::array<std::span<s32>, MaxChannels> output_buffers{};
 
     for (u32 i = 0; i < parameter.channel_count; i++) {
         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

+ 6 - 6
src/audio_core/renderer/command/effect/reverb.cpp

@@ -250,8 +250,8 @@ static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine&
  */
 template <size_t NumChannels>
 static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
-                              std::vector<std::span<const s32>>& inputs,
-                              std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+                              std::span<std::span<const s32>> inputs,
+                              std::span<std::span<s32>> outputs, const u32 sample_count) {
     static constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     };
@@ -369,8 +369,8 @@ static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, Rever
  * @param sample_count - Number of samples to process.
  */
 static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
-                              const bool enabled, std::vector<std::span<const s32>>& inputs,
-                              std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+                              const bool enabled, std::span<std::span<const s32>> inputs,
+                              std::span<std::span<s32>> outputs, const u32 sample_count) {
     if (enabled) {
         switch (params.channel_count) {
         case 0:
@@ -412,8 +412,8 @@ void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc
 }
 
 void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
-    std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
-    std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+    std::array<std::span<const s32>, MaxChannels> input_buffers{};
+    std::array<std::span<s32>, MaxChannels> output_buffers{};
 
     for (u32 i = 0; i < parameter.channel_count; i++) {
         input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

+ 6 - 9
src/audio_core/renderer/command/performance/performance.cpp

@@ -5,7 +5,6 @@
 #include "audio_core/renderer/command/performance/performance.h"
 #include "core/core.h"
 #include "core/core_timing.h"
-#include "core/core_timing_util.h"
 
 namespace AudioCore::AudioRenderer {
 
@@ -18,20 +17,18 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
     auto base{entry_address.translated_address};
     if (state == PerformanceState::Start) {
         auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
-        *start_time_ptr = static_cast<u32>(
-            Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
-                                     processor.start_time - processor.current_processing_time)
-                .count());
+        *start_time_ptr =
+            static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
+                             processor.current_processing_time);
     } else if (state == PerformanceState::Stop) {
         auto processed_time_ptr{
             reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
         auto entry_count_ptr{
             reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
 
-        *processed_time_ptr = static_cast<u32>(
-            Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
-                                     processor.start_time - processor.current_processing_time)
-                .count());
+        *processed_time_ptr =
+            static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
+                             processor.current_processing_time);
         (*entry_count_ptr)++;
     }
 }

+ 2 - 2
src/audio_core/renderer/command/sink/circular_buffer.cpp

@@ -24,7 +24,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
     constexpr s32 min{std::numeric_limits<s16>::min()};
     constexpr s32 max{std::numeric_limits<s16>::max()};
 
-    std::vector<s16> output(processor.sample_count);
+    std::array<s16, TargetSampleCount * MaxChannels> output{};
     for (u32 channel = 0; channel < input_count; channel++) {
         auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
                                                  processor.sample_count)};
@@ -33,7 +33,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
         }
 
         processor.memory->WriteBlockUnsafe(address + pos, output.data(),
-                                           output.size() * sizeof(s16));
+                                           processor.sample_count * sizeof(s16));
         pos += static_cast<u32>(processor.sample_count * sizeof(s16));
         if (pos >= size) {
             pos = 0;

+ 2 - 3
src/audio_core/renderer/command/sink/device.cpp

@@ -33,8 +33,7 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
         .consumed{false},
     };
 
-    std::vector<s16> samples(out_buffer.frames * input_count);
-
+    std::array<s16, TargetSampleCount * MaxChannels> samples{};
     for (u32 channel = 0; channel < input_count; channel++) {
         const auto offset{inputs[channel] * out_buffer.frames};
 
@@ -45,7 +44,7 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
     }
 
     out_buffer.tag = reinterpret_cast<u64>(samples.data());
-    stream->AppendBuffer(out_buffer, samples);
+    stream->AppendBuffer(out_buffer, {samples.data(), out_buffer.frames * input_count});
 
     if (stream->IsPaused()) {
         stream->Start();

+ 3 - 3
src/audio_core/renderer/mix/mix_context.cpp

@@ -125,10 +125,10 @@ bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
         return false;
     }
 
-    std::vector<s32> sorted_results{node_states.GetSortedResuls()};
-    const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
+    auto sorted_results{node_states.GetSortedResuls()};
+    const auto result_size{std::min(count, static_cast<s32>(sorted_results.second))};
     for (s32 i = 0; i < result_size; i++) {
-        sorted_mix_infos[i] = &mix_infos[sorted_results[i]];
+        sorted_mix_infos[i] = &mix_infos[sorted_results.first[i]];
     }
 
     CalcMixBufferOffset();

+ 2 - 2
src/audio_core/renderer/nodes/node_states.cpp

@@ -134,8 +134,8 @@ u32 NodeStates::GetNodeCount() const {
     return node_count;
 }
 
-std::vector<s32> NodeStates::GetSortedResuls() const {
-    return {results.rbegin(), results.rbegin() + result_pos};
+std::pair<std::span<u32>::reverse_iterator, size_t> NodeStates::GetSortedResuls() const {
+    return {results.rbegin(), result_pos};
 }
 
 } // namespace AudioCore::AudioRenderer

+ 1 - 1
src/audio_core/renderer/nodes/node_states.h

@@ -175,7 +175,7 @@ public:
      *
      * @return Vector of nodes in reverse order.
      */
-    std::vector<s32> GetSortedResuls() const;
+    std::pair<std::span<u32>::reverse_iterator, size_t> GetSortedResuls() const;
 
 private:
     /// Number of nodes in the graph

+ 1 - 0
src/audio_core/renderer/system.cpp

@@ -444,6 +444,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
     std::scoped_lock l{lock};
 
     const auto start_time{core.CoreTiming().GetClockTicks()};
+    std::memset(output.data(), 0, output.size());
 
     InfoUpdater info_updater(input, output, process_handle, behavior);
 

+ 1 - 1
src/audio_core/sink/null_sink.h

@@ -20,7 +20,7 @@ public:
     explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
         : SinkStream{system_, type_} {}
     ~NullSinkStreamImpl() override {}
-    void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
+    void AppendBuffer(SinkBuffer&, std::span<s16>) override {}
     std::vector<s16> ReleaseBuffer(u64) override {
         return {};
     }

+ 8 - 8
src/audio_core/sink/sink_stream.cpp

@@ -15,11 +15,10 @@
 #include "common/settings.h"
 #include "core/core.h"
 #include "core/core_timing.h"
-#include "core/core_timing_util.h"
 
 namespace AudioCore::Sink {
 
-void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
+void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
     if (type == StreamType::In) {
         queue.enqueue(buffer);
         queued_buffers++;
@@ -67,15 +66,16 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
                 static_cast<s16>(std::clamp(right_sample, min, max));
         }
 
-        samples.resize(samples.size() / system_channels * device_channels);
+        samples = samples.subspan(0, samples.size() / system_channels * device_channels);
 
     } else if (system_channels == 2 && device_channels == 6) {
         // We need moar samples! Not all games will provide 6 channel audio.
         // TODO: Implement some upmixing here. Currently just passthrough, with other
         // channels left as silence.
-        std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
+        auto new_size = samples.size() / system_channels * device_channels;
+        tmp_samples.resize_destructive(new_size);
 
-        for (u32 read_index = 0, write_index = 0; read_index < samples.size();
+        for (u32 read_index = 0, write_index = 0; read_index < new_size;
              read_index += system_channels, write_index += device_channels) {
             const auto left_sample{static_cast<s16>(std::clamp(
                 static_cast<s32>(
@@ -83,7 +83,7 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
                     volume),
                 min, max))};
 
-            new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
+            tmp_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
 
             const auto right_sample{static_cast<s16>(std::clamp(
                 static_cast<s32>(
@@ -91,9 +91,9 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
                     volume),
                 min, max))};
 
-            new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
+            tmp_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
         }
-        samples = std::move(new_samples);
+        samples = std::span<s16>(tmp_samples);
 
     } else if (volume != 1.0f) {
         for (u32 i = 0; i < samples.size(); i++) {

+ 4 - 1
src/audio_core/sink/sink_stream.h

@@ -16,6 +16,7 @@
 #include "common/polyfill_thread.h"
 #include "common/reader_writer_queue.h"
 #include "common/ring_buffer.h"
+#include "common/scratch_buffer.h"
 #include "common/thread.h"
 
 namespace Core {
@@ -170,7 +171,7 @@ public:
      * @param buffer  - Audio buffer information to be queued.
      * @param samples - The s16 samples to be queue for playback.
      */
-    virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
+    virtual void AppendBuffer(SinkBuffer& buffer, std::span<s16> samples);
 
     /**
      * Release a buffer. Audio In only, will fill a buffer with recorded samples.
@@ -255,6 +256,8 @@ private:
     /// Signalled when ring buffer entries are consumed
     std::condition_variable_any release_cv;
     std::mutex release_mutex;
+    /// Temporary buffer for appending samples when upmixing
+    Common::ScratchBuffer<s16> tmp_samples{};
 };
 
 using SinkStreamPtr = std::unique_ptr<SinkStream>;

+ 2 - 0
src/common/CMakeLists.txt

@@ -172,6 +172,8 @@ if(ARCHITECTURE_x86_64)
             x64/cpu_wait.h
             x64/native_clock.cpp
             x64/native_clock.h
+            x64/rdtsc.cpp
+            x64/rdtsc.h
             x64/xbyak_abi.h
             x64/xbyak_util.h
     )

+ 10 - 4
src/common/fs/fs.cpp

@@ -436,7 +436,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
 
         if (True(filter & DirEntryFilter::File) &&
             entry.status().type() == fs::file_type::regular) {
-            if (!callback(entry.path())) {
+            if (!callback(entry)) {
                 callback_error = true;
                 break;
             }
@@ -444,7 +444,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
 
         if (True(filter & DirEntryFilter::Directory) &&
             entry.status().type() == fs::file_type::directory) {
-            if (!callback(entry.path())) {
+            if (!callback(entry)) {
                 callback_error = true;
                 break;
             }
@@ -493,7 +493,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
 
         if (True(filter & DirEntryFilter::File) &&
             entry.status().type() == fs::file_type::regular) {
-            if (!callback(entry.path())) {
+            if (!callback(entry)) {
                 callback_error = true;
                 break;
             }
@@ -501,7 +501,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
 
         if (True(filter & DirEntryFilter::Directory) &&
             entry.status().type() == fs::file_type::directory) {
-            if (!callback(entry.path())) {
+            if (!callback(entry)) {
                 callback_error = true;
                 break;
             }
@@ -605,6 +605,12 @@ fs::file_type GetEntryType(const fs::path& path) {
 }
 
 u64 GetSize(const fs::path& path) {
+#ifdef ANDROID
+    if (Android::IsContentUri(path)) {
+        return Android::GetSize(path);
+    }
+#endif
+
     std::error_code ec;
 
     const auto file_size = fs::file_size(path, ec);

+ 1 - 1
src/common/fs/fs_types.h

@@ -66,6 +66,6 @@ DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter);
  * @returns A boolean value.
  *          Return true to indicate whether the callback is successful, false otherwise.
  */
-using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>;
+using DirEntryCallable = std::function<bool(const std::filesystem::directory_entry& entry)>;
 
 } // namespace Common::FS

+ 41 - 4
src/common/input.h

@@ -75,8 +75,10 @@ enum class DriverResult {
     ErrorWritingData,
     NoDeviceDetected,
     InvalidHandle,
+    InvalidParameters,
     NotSupported,
     Disabled,
+    Delayed,
     Unknown,
 };
 
@@ -86,7 +88,7 @@ enum class NfcState {
     NewAmiibo,
     WaitingForAmiibo,
     AmiiboRemoved,
-    NotAnAmiibo,
+    InvalidTagType,
     NotSupported,
     WrongDeviceState,
     WriteFailed,
@@ -218,8 +220,22 @@ struct CameraStatus {
 };
 
 struct NfcStatus {
-    NfcState state{};
-    std::vector<u8> data{};
+    NfcState state{NfcState::Unknown};
+    u8 uuid_length;
+    u8 protocol;
+    u8 tag_type;
+    std::array<u8, 10> uuid;
+};
+
+struct MifareData {
+    u8 command;
+    u8 sector;
+    std::array<u8, 0x6> key;
+    std::array<u8, 0x10> data;
+};
+
+struct MifareRequest {
+    std::array<MifareData, 0x10> data;
 };
 
 // List of buttons to be passed to Qt that can be translated
@@ -294,7 +310,7 @@ struct CallbackStatus {
     BatteryStatus battery_status{};
     VibrationStatus vibration_status{};
     CameraFormat camera_status{CameraFormat::None};
-    NfcState nfc_status{NfcState::Unknown};
+    NfcStatus nfc_status{};
     std::vector<u8> raw_data{};
 };
 
@@ -356,9 +372,30 @@ public:
         return NfcState::NotSupported;
     }
 
+    virtual NfcState StartNfcPolling() {
+        return NfcState::NotSupported;
+    }
+
+    virtual NfcState StopNfcPolling() {
+        return NfcState::NotSupported;
+    }
+
+    virtual NfcState ReadAmiiboData([[maybe_unused]] std::vector<u8>& out_data) {
+        return NfcState::NotSupported;
+    }
+
     virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
         return NfcState::NotSupported;
     }
+
+    virtual NfcState ReadMifareData([[maybe_unused]] const MifareRequest& request,
+                                    [[maybe_unused]] MifareRequest& out_data) {
+        return NfcState::NotSupported;
+    }
+
+    virtual NfcState WriteMifareData([[maybe_unused]] const MifareRequest& request) {
+        return NfcState::NotSupported;
+    }
 };
 
 /// An abstract class template for a factory that can create input devices.

+ 2 - 1
src/common/ring_buffer.h

@@ -9,6 +9,7 @@
 #include <cstddef>
 #include <cstring>
 #include <new>
+#include <span>
 #include <type_traits>
 #include <vector>
 
@@ -53,7 +54,7 @@ public:
         return push_count;
     }
 
-    std::size_t Push(const std::vector<T>& input) {
+    std::size_t Push(const std::span<T> input) {
         return Push(input.data(), input.size());
     }
 

+ 9 - 0
src/common/scratch_buffer.h

@@ -3,6 +3,9 @@
 
 #pragma once
 
+#include <iterator>
+
+#include "common/concepts.h"
 #include "common/make_unique_for_overwrite.h"
 
 namespace Common {
@@ -16,6 +19,12 @@ namespace Common {
 template <typename T>
 class ScratchBuffer {
 public:
+    using iterator = T*;
+    using const_iterator = const T*;
+    using value_type = T;
+    using element_type = T;
+    using iterator_category = std::contiguous_iterator_tag;
+
     ScratchBuffer() = default;
 
     explicit ScratchBuffer(size_t initial_capacity)

+ 16 - 3
src/common/settings.cpp

@@ -1,8 +1,11 @@
 // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <version>
 #if __cpp_lib_chrono >= 201907L
 #include <chrono>
+#include <exception>
+#include <stdexcept>
 #endif
 #include <string_view>
 
@@ -25,9 +28,19 @@ std::string GetTimeZoneString() {
     if (time_zone_index == 0) { // Auto
 #if __cpp_lib_chrono >= 201907L
         const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb();
-        const std::chrono::time_zone* current_zone = time_zone_data.current_zone();
-        std::string_view current_zone_name = current_zone->name();
-        location_name = current_zone_name;
+        try {
+            const std::chrono::time_zone* current_zone = time_zone_data.current_zone();
+            std::string_view current_zone_name = current_zone->name();
+            location_name = current_zone_name;
+        } catch (std::runtime_error& runtime_error) {
+            // VCRUNTIME will throw a runtime_error if the operating system's selected time zone
+            // cannot be found
+            location_name = Common::TimeZone::FindSystemTimeZone();
+            LOG_WARNING(Common,
+                        "Error occurred when trying to determine system time zone:\n{}\nFalling "
+                        "back to hour offset \"{}\"",
+                        runtime_error.what(), location_name);
+        }
 #else
         location_name = Common::TimeZone::FindSystemTimeZone();
 #endif

+ 10 - 2
src/common/settings.h

@@ -483,6 +483,7 @@ struct Values {
         AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
         "astc_recompression"};
     SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
+    SwitchableSetting<bool> barrier_feedback_loops{true, "barrier_feedback_loops"};
 
     SwitchableSetting<u8> bg_red{0, "bg_red"};
     SwitchableSetting<u8> bg_green{0, "bg_green"};
@@ -524,9 +525,16 @@ struct Values {
     Setting<bool> tas_loop{false, "tas_loop"};
 
     Setting<bool> mouse_panning{false, "mouse_panning"};
-    Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"};
-    Setting<bool> mouse_enabled{false, "mouse_enabled"};
+    Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
+    Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
+    Setting<u8, true> mouse_panning_deadzone_x_counterweight{
+        0, 0, 100, "mouse_panning_deadzone_x_counterweight"};
+    Setting<u8, true> mouse_panning_deadzone_y_counterweight{
+        0, 0, 100, "mouse_panning_deadzone_y_counterweight"};
+    Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"};
+    Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"};
 
+    Setting<bool> mouse_enabled{false, "mouse_enabled"};
     Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
     Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
 

+ 2 - 3
src/common/steady_clock.cpp

@@ -28,13 +28,12 @@ static s64 GetSystemTimeNS() {
     // GetSystemTimePreciseAsFileTime returns the file time in 100ns units.
     static constexpr s64 Multiplier = 100;
     // Convert Windows epoch to Unix epoch.
-    static constexpr s64 WindowsEpochToUnixEpochNS = 0x19DB1DED53E8000LL;
+    static constexpr s64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL;
 
     FILETIME filetime;
     GetSystemTimePreciseAsFileTime(&filetime);
     return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) +
-                         static_cast<s64>(filetime.dwLowDateTime)) -
-           WindowsEpochToUnixEpochNS;
+                         static_cast<s64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch);
 }
 #endif
 

+ 1 - 0
src/common/telemetry.cpp

@@ -93,6 +93,7 @@ void AppendCPUInfo(FieldCollection& fc) {
     add_field("CPU_Extension_x64_GFNI", caps.gfni);
     add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc);
     add_field("CPU_Extension_x64_LZCNT", caps.lzcnt);
+    add_field("CPU_Extension_x64_MONITORX", caps.monitorx);
     add_field("CPU_Extension_x64_MOVBE", caps.movbe);
     add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq);
     add_field("CPU_Extension_x64_POPCNT", caps.popcnt);

+ 1 - 1
src/common/thread.h

@@ -55,7 +55,7 @@ public:
         is_set = false;
     }
 
-    [[nodiscard]] bool IsSet() {
+    [[nodiscard]] bool IsSet() const {
         return is_set;
     }
 

+ 32 - 45
src/common/wall_clock.cpp

@@ -2,88 +2,75 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "common/steady_clock.h"
-#include "common/uint128.h"
 #include "common/wall_clock.h"
 
 #ifdef ARCHITECTURE_x86_64
 #include "common/x64/cpu_detect.h"
 #include "common/x64/native_clock.h"
+#include "common/x64/rdtsc.h"
 #endif
 
 namespace Common {
 
 class StandardWallClock final : public WallClock {
 public:
-    explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_)
-        : WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
-          start_time{SteadyClock::Now()} {}
+    explicit StandardWallClock() : start_time{SteadyClock::Now()} {}
 
-    std::chrono::nanoseconds GetTimeNS() override {
+    std::chrono::nanoseconds GetTimeNS() const override {
         return SteadyClock::Now() - start_time;
     }
 
-    std::chrono::microseconds GetTimeUS() override {
-        return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS());
+    std::chrono::microseconds GetTimeUS() const override {
+        return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den);
     }
 
-    std::chrono::milliseconds GetTimeMS() override {
-        return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS());
+    std::chrono::milliseconds GetTimeMS() const override {
+        return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den);
     }
 
-    u64 GetClockCycles() override {
-        const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency);
-        return Common::Divide128On32(temp, NS_RATIO).first;
+    u64 GetCNTPCT() const override {
+        return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
     }
 
-    u64 GetCPUCycles() override {
-        const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency);
-        return Common::Divide128On32(temp, NS_RATIO).first;
+    u64 GetGPUTick() const override {
+        return GetHostTicksElapsed() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
     }
 
-    void Pause([[maybe_unused]] bool is_paused) override {
-        // Do nothing in this clock type.
+    u64 GetHostTicksNow() const override {
+        return static_cast<u64>(SteadyClock::Now().time_since_epoch().count());
+    }
+
+    u64 GetHostTicksElapsed() const override {
+        return static_cast<u64>(GetTimeNS().count());
+    }
+
+    bool IsNative() const override {
+        return false;
     }
 
 private:
     SteadyClock::time_point start_time;
 };
 
+std::unique_ptr<WallClock> CreateOptimalClock() {
 #ifdef ARCHITECTURE_x86_64
-
-std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
-                                                   u64 emulated_clock_frequency) {
     const auto& caps = GetCPUCaps();
-    u64 rtsc_frequency = 0;
-    if (caps.invariant_tsc) {
-        rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency();
-    }
 
-    // Fallback to StandardWallClock if the hardware TSC does not have the precision greater than:
-    // - A nanosecond
-    // - The emulated CPU frequency
-    // - The emulated clock counter frequency (CNTFRQ)
-    if (rtsc_frequency <= WallClock::NS_RATIO || rtsc_frequency <= emulated_cpu_frequency ||
-        rtsc_frequency <= emulated_clock_frequency) {
-        return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
-                                                   emulated_clock_frequency);
+    if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::GPUTickFreq) {
+        return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
     } else {
-        return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
-                                                  rtsc_frequency);
+        // Fallback to StandardWallClock if the hardware TSC
+        // - Is not invariant
+        // - Is not more precise than GPUTickFreq
+        return std::make_unique<StandardWallClock>();
     }
-}
-
 #else
-
-std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
-                                                   u64 emulated_clock_frequency) {
-    return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
-}
-
+    return std::make_unique<StandardWallClock>();
 #endif
+}
 
-std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
-                                                   u64 emulated_clock_frequency) {
-    return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
+std::unique_ptr<WallClock> CreateStandardWallClock() {
+    return std::make_unique<StandardWallClock>();
 }
 
 } // namespace Common

+ 61 - 28
src/common/wall_clock.h

@@ -5,6 +5,7 @@
 
 #include <chrono>
 #include <memory>
+#include <ratio>
 
 #include "common/common_types.h"
 
@@ -12,50 +13,82 @@ namespace Common {
 
 class WallClock {
 public:
-    static constexpr u64 NS_RATIO = 1'000'000'000;
-    static constexpr u64 US_RATIO = 1'000'000;
-    static constexpr u64 MS_RATIO = 1'000;
+    static constexpr u64 CNTFRQ = 19'200'000;         // CNTPCT_EL0 Frequency = 19.2 MHz
+    static constexpr u64 GPUTickFreq = 614'400'000;   // GM20B GPU Tick Frequency = 614.4 MHz
+    static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
 
     virtual ~WallClock() = default;
 
-    /// Returns current wall time in nanoseconds
-    [[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0;
+    /// @returns The time in nanoseconds since the construction of this clock.
+    virtual std::chrono::nanoseconds GetTimeNS() const = 0;
 
-    /// Returns current wall time in microseconds
-    [[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0;
+    /// @returns The time in microseconds since the construction of this clock.
+    virtual std::chrono::microseconds GetTimeUS() const = 0;
 
-    /// Returns current wall time in milliseconds
-    [[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0;
+    /// @returns The time in milliseconds since the construction of this clock.
+    virtual std::chrono::milliseconds GetTimeMS() const = 0;
 
-    /// Returns current wall time in emulated clock cycles
-    [[nodiscard]] virtual u64 GetClockCycles() = 0;
+    /// @returns The guest CNTPCT ticks since the construction of this clock.
+    virtual u64 GetCNTPCT() const = 0;
 
-    /// Returns current wall time in emulated cpu cycles
-    [[nodiscard]] virtual u64 GetCPUCycles() = 0;
+    /// @returns The guest GPU ticks since the construction of this clock.
+    virtual u64 GetGPUTick() const = 0;
 
-    virtual void Pause(bool is_paused) = 0;
+    /// @returns The raw host timer ticks since an indeterminate epoch.
+    virtual u64 GetHostTicksNow() const = 0;
 
-    /// Tells if the wall clock, uses the host CPU's hardware clock
-    [[nodiscard]] bool IsNative() const {
-        return is_native;
+    /// @returns The raw host timer ticks since the construction of this clock.
+    virtual u64 GetHostTicksElapsed() const = 0;
+
+    /// @returns Whether the clock directly uses the host's hardware clock.
+    virtual bool IsNative() const = 0;
+
+    static inline u64 NSToCNTPCT(u64 ns) {
+        return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
+    }
+
+    static inline u64 NSToGPUTick(u64 ns) {
+        return ns * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
+    }
+
+    // Cycle Timing
+
+    static inline u64 CPUTickToNS(u64 cpu_tick) {
+        return cpu_tick * CPUTickToNsRatio::num / CPUTickToNsRatio::den;
+    }
+
+    static inline u64 CPUTickToUS(u64 cpu_tick) {
+        return cpu_tick * CPUTickToUsRatio::num / CPUTickToUsRatio::den;
+    }
+
+    static inline u64 CPUTickToCNTPCT(u64 cpu_tick) {
+        return cpu_tick * CPUTickToCNTPCTRatio::num / CPUTickToCNTPCTRatio::den;
+    }
+
+    static inline u64 CPUTickToGPUTick(u64 cpu_tick) {
+        return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den;
     }
 
 protected:
-    explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_)
-        : emulated_cpu_frequency{emulated_cpu_frequency_},
-          emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {}
+    using NsRatio = std::nano;
+    using UsRatio = std::micro;
+    using MsRatio = std::milli;
+
+    using NsToUsRatio = std::ratio_divide<std::nano, std::micro>;
+    using NsToMsRatio = std::ratio_divide<std::nano, std::milli>;
+    using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>;
+    using NsToGPUTickRatio = std::ratio<GPUTickFreq, std::nano::den>;
 
-    u64 emulated_cpu_frequency;
-    u64 emulated_clock_frequency;
+    // Cycle Timing
 
-private:
-    bool is_native;
+    using CPUTickToNsRatio = std::ratio<std::nano::den, CPUTickFreq>;
+    using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>;
+    using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>;
+    using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
 };
 
-[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
-                                                                 u64 emulated_clock_frequency);
+std::unique_ptr<WallClock> CreateOptimalClock();
 
-[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
-                                                                 u64 emulated_clock_frequency);
+std::unique_ptr<WallClock> CreateStandardWallClock();
 
 } // namespace Common

+ 4 - 0
src/common/x64/cpu_detect.cpp

@@ -14,6 +14,7 @@
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/x64/cpu_detect.h"
+#include "common/x64/rdtsc.h"
 
 #ifdef _WIN32
 #include <windows.h>
@@ -167,6 +168,7 @@ static CPUCaps Detect() {
         __cpuid(cpu_id, 0x80000001);
         caps.lzcnt = Common::Bit<5>(cpu_id[2]);
         caps.fma4 = Common::Bit<16>(cpu_id[2]);
+        caps.monitorx = Common::Bit<29>(cpu_id[2]);
     }
 
     if (max_ex_fn >= 0x80000007) {
@@ -187,6 +189,8 @@ static CPUCaps Detect() {
             caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
                                  caps.tsc_crystal_ratio_numerator /
                                  caps.tsc_crystal_ratio_denominator;
+        } else {
+            caps.tsc_frequency = X64::EstimateRDTSCFrequency();
         }
     }
 

+ 1 - 0
src/common/x64/cpu_detect.h

@@ -63,6 +63,7 @@ struct CPUCaps {
     bool gfni : 1;
     bool invariant_tsc : 1;
     bool lzcnt : 1;
+    bool monitorx : 1;
     bool movbe : 1;
     bool pclmulqdq : 1;
     bool popcnt : 1;

+ 38 - 32
src/common/x64/cpu_wait.cpp

@@ -9,58 +9,64 @@
 
 #include "common/x64/cpu_detect.h"
 #include "common/x64/cpu_wait.h"
+#include "common/x64/rdtsc.h"
 
 namespace Common::X64 {
 
+namespace {
+
+// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
+// For reference:
+// At 1 GHz, 100K cycles is 100us
+// At 2 GHz, 100K cycles is 50us
+// At 4 GHz, 100K cycles is 25us
+constexpr auto PauseCycles = 100'000U;
+
+} // Anonymous namespace
+
 #ifdef _MSC_VER
-__forceinline static u64 FencedRDTSC() {
-    _mm_lfence();
-    _ReadWriteBarrier();
-    const u64 result = __rdtsc();
-    _mm_lfence();
-    _ReadWriteBarrier();
-    return result;
+__forceinline static void TPAUSE() {
+    static constexpr auto RequestC02State = 0U;
+    _tpause(RequestC02State, FencedRDTSC() + PauseCycles);
 }
 
-__forceinline static void TPAUSE() {
-    // 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
-    // For reference:
-    // At 1 GHz, 100K cycles is 100us
-    // At 2 GHz, 100K cycles is 50us
-    // At 4 GHz, 100K cycles is 25us
-    static constexpr auto PauseCycles = 100'000;
-    _tpause(0, FencedRDTSC() + PauseCycles);
+__forceinline static void MWAITX() {
+    static constexpr auto EnableWaitTimeFlag = 1U << 1;
+    static constexpr auto RequestC1State = 0U;
+
+    // monitor_var should be aligned to a cache line.
+    alignas(64) u64 monitor_var{};
+    _mm_monitorx(&monitor_var, 0, 0);
+    _mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles);
 }
 #else
-static u64 FencedRDTSC() {
-    u64 eax;
-    u64 edx;
-    asm volatile("lfence\n\t"
-                 "rdtsc\n\t"
-                 "lfence\n\t"
-                 : "=a"(eax), "=d"(edx));
-    return (edx << 32) | eax;
-}
-
 static void TPAUSE() {
-    // 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
-    // For reference:
-    // At 1 GHz, 100K cycles is 100us
-    // At 2 GHz, 100K cycles is 50us
-    // At 4 GHz, 100K cycles is 25us
-    static constexpr auto PauseCycles = 100'000;
+    static constexpr auto RequestC02State = 0U;
     const auto tsc = FencedRDTSC() + PauseCycles;
     const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
     const auto edx = static_cast<u32>(tsc >> 32);
-    asm volatile("tpause %0" : : "r"(0), "d"(edx), "a"(eax));
+    asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax));
+}
+
+static void MWAITX() {
+    static constexpr auto EnableWaitTimeFlag = 1U << 1;
+    static constexpr auto RequestC1State = 0U;
+
+    // monitor_var should be aligned to a cache line.
+    alignas(64) u64 monitor_var{};
+    asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0));
+    asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag));
 }
 #endif
 
 void MicroSleep() {
     static const bool has_waitpkg = GetCPUCaps().waitpkg;
+    static const bool has_monitorx = GetCPUCaps().monitorx;
 
     if (has_waitpkg) {
         TPAUSE();
+    } else if (has_monitorx) {
+        MWAITX();
     } else {
         std::this_thread::yield();
     }

+ 26 - 140
src/common/x64/native_clock.cpp

@@ -1,164 +1,50 @@
 // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include <array>
-#include <chrono>
-#include <thread>
-
-#include "common/atomic_ops.h"
-#include "common/steady_clock.h"
 #include "common/uint128.h"
 #include "common/x64/native_clock.h"
+#include "common/x64/rdtsc.h"
 
-#ifdef _MSC_VER
-#include <intrin.h>
-#endif
-
-namespace Common {
+namespace Common::X64 {
 
-#ifdef _MSC_VER
-__forceinline static u64 FencedRDTSC() {
-    _mm_lfence();
-    _ReadWriteBarrier();
-    const u64 result = __rdtsc();
-    _mm_lfence();
-    _ReadWriteBarrier();
-    return result;
-}
-#else
-static u64 FencedRDTSC() {
-    u64 eax;
-    u64 edx;
-    asm volatile("lfence\n\t"
-                 "rdtsc\n\t"
-                 "lfence\n\t"
-                 : "=a"(eax), "=d"(edx));
-    return (edx << 32) | eax;
-}
-#endif
+NativeClock::NativeClock(u64 rdtsc_frequency_)
+    : start_ticks{FencedRDTSC()}, rdtsc_frequency{rdtsc_frequency_},
+      ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency)},
+      us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
+      ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
+      cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
+      gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
 
-template <u64 Nearest>
-static u64 RoundToNearest(u64 value) {
-    const auto mod = value % Nearest;
-    return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
+std::chrono::nanoseconds NativeClock::GetTimeNS() const {
+    return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)};
 }
 
-u64 EstimateRDTSCFrequency() {
-    // Discard the first result measuring the rdtsc.
-    FencedRDTSC();
-    std::this_thread::sleep_for(std::chrono::milliseconds{1});
-    FencedRDTSC();
-
-    // Get the current time.
-    const auto start_time = Common::RealTimeClock::Now();
-    const u64 tsc_start = FencedRDTSC();
-    // Wait for 250 milliseconds.
-    std::this_thread::sleep_for(std::chrono::milliseconds{250});
-    const auto end_time = Common::RealTimeClock::Now();
-    const u64 tsc_end = FencedRDTSC();
-    // Calculate differences.
-    const u64 timer_diff = static_cast<u64>(
-        std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
-    const u64 tsc_diff = tsc_end - tsc_start;
-    const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
-    return RoundToNearest<1000>(tsc_freq);
+std::chrono::microseconds NativeClock::GetTimeUS() const {
+    return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)};
 }
 
-namespace X64 {
-NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
-                         u64 rtsc_frequency_)
-    : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{
-                                                                               rtsc_frequency_} {
-    // Thread to re-adjust the RDTSC frequency after 10 seconds has elapsed.
-    time_sync_thread = std::jthread{[this](std::stop_token token) {
-        // Get the current time.
-        const auto start_time = Common::RealTimeClock::Now();
-        const u64 tsc_start = FencedRDTSC();
-        // Wait for 10 seconds.
-        if (!Common::StoppableTimedWait(token, std::chrono::seconds{10})) {
-            return;
-        }
-        const auto end_time = Common::RealTimeClock::Now();
-        const u64 tsc_end = FencedRDTSC();
-        // Calculate differences.
-        const u64 timer_diff = static_cast<u64>(
-            std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
-        const u64 tsc_diff = tsc_end - tsc_start;
-        const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
-        rtsc_frequency = tsc_freq;
-        CalculateAndSetFactors();
-    }};
-
-    time_point.inner.last_measure = FencedRDTSC();
-    time_point.inner.accumulated_ticks = 0U;
-    CalculateAndSetFactors();
+std::chrono::milliseconds NativeClock::GetTimeMS() const {
+    return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)};
 }
 
-u64 NativeClock::GetRTSC() {
-    TimePoint new_time_point{};
-    TimePoint current_time_point{};
-
-    current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
-    do {
-        const u64 current_measure = FencedRDTSC();
-        u64 diff = current_measure - current_time_point.inner.last_measure;
-        diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
-        new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure
-                                                ? current_measure
-                                                : current_time_point.inner.last_measure;
-        new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff;
-    } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
-                                           current_time_point.pack, current_time_point.pack));
-    return new_time_point.inner.accumulated_ticks;
+u64 NativeClock::GetCNTPCT() const {
+    return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor);
 }
 
-void NativeClock::Pause(bool is_paused) {
-    if (!is_paused) {
-        TimePoint current_time_point{};
-        TimePoint new_time_point{};
-
-        current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
-        do {
-            new_time_point.pack = current_time_point.pack;
-            new_time_point.inner.last_measure = FencedRDTSC();
-        } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
-                                               current_time_point.pack, current_time_point.pack));
-    }
+u64 NativeClock::GetGPUTick() const {
+    return MultiplyHigh(GetHostTicksElapsed(), gputick_rdtsc_factor);
 }
 
-std::chrono::nanoseconds NativeClock::GetTimeNS() {
-    const u64 rtsc_value = GetRTSC();
-    return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)};
+u64 NativeClock::GetHostTicksNow() const {
+    return FencedRDTSC();
 }
 
-std::chrono::microseconds NativeClock::GetTimeUS() {
-    const u64 rtsc_value = GetRTSC();
-    return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)};
+u64 NativeClock::GetHostTicksElapsed() const {
+    return FencedRDTSC() - start_ticks;
 }
 
-std::chrono::milliseconds NativeClock::GetTimeMS() {
-    const u64 rtsc_value = GetRTSC();
-    return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)};
+bool NativeClock::IsNative() const {
+    return true;
 }
 
-u64 NativeClock::GetClockCycles() {
-    const u64 rtsc_value = GetRTSC();
-    return MultiplyHigh(rtsc_value, clock_rtsc_factor);
-}
-
-u64 NativeClock::GetCPUCycles() {
-    const u64 rtsc_value = GetRTSC();
-    return MultiplyHigh(rtsc_value, cpu_rtsc_factor);
-}
-
-void NativeClock::CalculateAndSetFactors() {
-    ns_rtsc_factor = GetFixedPoint64Factor(NS_RATIO, rtsc_frequency);
-    us_rtsc_factor = GetFixedPoint64Factor(US_RATIO, rtsc_frequency);
-    ms_rtsc_factor = GetFixedPoint64Factor(MS_RATIO, rtsc_frequency);
-    clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency);
-    cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency);
-}
-
-} // namespace X64
-
-} // namespace Common
+} // namespace Common::X64

+ 20 - 39
src/common/x64/native_clock.h

@@ -3,58 +3,39 @@
 
 #pragma once
 
-#include "common/polyfill_thread.h"
 #include "common/wall_clock.h"
 
-namespace Common {
+namespace Common::X64 {
 
-namespace X64 {
 class NativeClock final : public WallClock {
 public:
-    explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
-                         u64 rtsc_frequency_);
+    explicit NativeClock(u64 rdtsc_frequency_);
 
-    std::chrono::nanoseconds GetTimeNS() override;
+    std::chrono::nanoseconds GetTimeNS() const override;
 
-    std::chrono::microseconds GetTimeUS() override;
+    std::chrono::microseconds GetTimeUS() const override;
 
-    std::chrono::milliseconds GetTimeMS() override;
+    std::chrono::milliseconds GetTimeMS() const override;
 
-    u64 GetClockCycles() override;
+    u64 GetCNTPCT() const override;
 
-    u64 GetCPUCycles() override;
+    u64 GetGPUTick() const override;
 
-    void Pause(bool is_paused) override;
+    u64 GetHostTicksNow() const override;
 
-private:
-    u64 GetRTSC();
-
-    void CalculateAndSetFactors();
-
-    union alignas(16) TimePoint {
-        TimePoint() : pack{} {}
-        u128 pack{};
-        struct Inner {
-            u64 last_measure{};
-            u64 accumulated_ticks{};
-        } inner;
-    };
-
-    TimePoint time_point;
+    u64 GetHostTicksElapsed() const override;
 
-    // factors
-    u64 clock_rtsc_factor{};
-    u64 cpu_rtsc_factor{};
-    u64 ns_rtsc_factor{};
-    u64 us_rtsc_factor{};
-    u64 ms_rtsc_factor{};
+    bool IsNative() const override;
 
-    u64 rtsc_frequency;
-
-    std::jthread time_sync_thread;
+private:
+    u64 start_ticks;
+    u64 rdtsc_frequency;
+
+    u64 ns_rdtsc_factor;
+    u64 us_rdtsc_factor;
+    u64 ms_rdtsc_factor;
+    u64 cntpct_rdtsc_factor;
+    u64 gputick_rdtsc_factor;
 };
-} // namespace X64
-
-u64 EstimateRDTSCFrequency();
 
-} // namespace Common
+} // namespace Common::X64

+ 39 - 0
src/common/x64/rdtsc.cpp

@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <thread>
+
+#include "common/steady_clock.h"
+#include "common/uint128.h"
+#include "common/x64/rdtsc.h"
+
+namespace Common::X64 {
+
+template <u64 Nearest>
+static u64 RoundToNearest(u64 value) {
+    const auto mod = value % Nearest;
+    return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
+}
+
+u64 EstimateRDTSCFrequency() {
+    // Discard the first result measuring the rdtsc.
+    FencedRDTSC();
+    std::this_thread::sleep_for(std::chrono::milliseconds{1});
+    FencedRDTSC();
+
+    // Get the current time.
+    const auto start_time = RealTimeClock::Now();
+    const u64 tsc_start = FencedRDTSC();
+    // Wait for 100 milliseconds.
+    std::this_thread::sleep_for(std::chrono::milliseconds{100});
+    const auto end_time = RealTimeClock::Now();
+    const u64 tsc_end = FencedRDTSC();
+    // Calculate differences.
+    const u64 timer_diff = static_cast<u64>(
+        std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
+    const u64 tsc_diff = tsc_end - tsc_start;
+    const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
+    return RoundToNearest<100'000>(tsc_freq);
+}
+
+} // namespace Common::X64

+ 37 - 0
src/common/x64/rdtsc.h

@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+#include "common/common_types.h"
+
+namespace Common::X64 {
+
+#ifdef _MSC_VER
+__forceinline static u64 FencedRDTSC() {
+    _mm_lfence();
+    _ReadWriteBarrier();
+    const u64 result = __rdtsc();
+    _mm_lfence();
+    _ReadWriteBarrier();
+    return result;
+}
+#else
+static inline u64 FencedRDTSC() {
+    u64 eax;
+    u64 edx;
+    asm volatile("lfence\n\t"
+                 "rdtsc\n\t"
+                 "lfence\n\t"
+                 : "=a"(eax), "=d"(edx));
+    return (edx << 32) | eax;
+}
+#endif
+
+u64 EstimateRDTSCFrequency();
+
+} // namespace Common::X64

+ 0 - 1
src/core/CMakeLists.txt

@@ -14,7 +14,6 @@ add_library(core STATIC
     core.h
     core_timing.cpp
     core_timing.h
-    core_timing_util.h
     cpu_manager.cpp
     cpu_manager.h
     crypto/aes_util.cpp

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff