From 1674ad5a2c50c78e02fe366c7bd7c660cd3cef8e Mon Sep 17 00:00:00 2001 From: shinra-electric <50119606+shinra-electric@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:36:41 +0100 Subject: [PATCH 01/17] macOS CI fixes (#587) * Update checkout to v4 * Update upload-artifact to v4 * don't try to reinstall python * Update to v4 in Qt_Build.yml too * remove python re-install in Qt_Build.yml too --- .github/workflows/MacOS_Build.yml | 6 +++--- .github/workflows/Qt_Build.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/MacOS_Build.yml b/.github/workflows/MacOS_Build.yml index 912c8568..76b75bd4 100644 --- a/.github/workflows/MacOS_Build.yml +++ b/.github/workflows/MacOS_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: macos-13 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -40,7 +40,7 @@ jobs: run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Install bundle dependencies - run: brew install --overwrite python@3.12 && brew install dylibbundler imagemagick + run: brew install dylibbundler imagemagick - name: Run bundle script run: ./.github/mac-bundle.sh @@ -52,7 +52,7 @@ jobs: run: zip -r Alber Alber.app - name: Upload MacOS App - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: MacOS Alber App Bundle path: 'Alber.zip' diff --git a/.github/workflows/Qt_Build.yml b/.github/workflows/Qt_Build.yml index 40141fb1..4485cc1c 100644 --- a/.github/workflows/Qt_Build.yml +++ b/.github/workflows/Qt_Build.yml @@ -54,7 +54,7 @@ jobs: runs-on: macos-13 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -67,7 +67,7 @@ jobs: - name: Install bundle dependencies run: | - brew install --overwrite python@3.12 && brew install dylibbundler imagemagick + brew install dylibbundler imagemagick - name: Install qt run: brew install qt && which macdeployqt @@ -90,7 +90,7 @@ jobs: run: zip -r Alber Alber.app - name: Upload MacOS App - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: MacOS Alber App Bundle path: 'Alber.zip' From e421f02500c01ec40bad2658059442f8e10ea5bf Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 27 Aug 2024 17:12:12 +0300 Subject: [PATCH 02/17] GLES <= 3.1 lacks fma, added a define --- src/core/PICA/shader_gen_glsl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/PICA/shader_gen_glsl.cpp b/src/core/PICA/shader_gen_glsl.cpp index 60887d56..aa605dd2 100644 --- a/src/core/PICA/shader_gen_glsl.cpp +++ b/src/core/PICA/shader_gen_glsl.cpp @@ -44,6 +44,7 @@ std::string FragmentGenerator::getDefaultVertexShader() { if (api == API::GLES) { ret += R"( #define USING_GLES 1 + #define fma(a, b, c) ((a) * (b) + (c)) precision mediump int; precision mediump float; From 2cffafff86cbab5596bd3b53760ce0a281e15263 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 27 Aug 2024 17:16:11 +0300 Subject: [PATCH 03/17] Update gles.patch --- .github/gles.patch | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/.github/gles.patch b/.github/gles.patch index 5a922fcf..c5cdb7d4 100644 --- a/.github/gles.patch +++ b/.github/gles.patch @@ -21,7 +21,7 @@ index 990e2f80..2e7842ac 100644 void main() { diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag -index b9f9fe4c..f1cf286f 100644 +index 9f07df0b..2ab623af 100644 --- a/src/host_shaders/opengl_fragment_shader.frag +++ b/src/host_shaders/opengl_fragment_shader.frag @@ -1,4 +1,5 @@ @@ -31,6 +31,17 @@ index b9f9fe4c..f1cf286f 100644 in vec4 v_quaternion; in vec4 v_colour; +@@ -41,8 +42,8 @@ vec3 normal; + const uint samplerEnabledBitfields[2] = uint[2](0x7170e645u, 0x7f013fefu); + + bool isSamplerEnabled(uint environment_id, uint lut_id) { +- uint index = 7 * environment_id + lut_id; +- uint arrayIndex = (index >> 5); ++ uint index = 7u * environment_id + lut_id; ++ uint arrayIndex = (index >> 5u); + return (samplerEnabledBitfields[arrayIndex] & (1u << (index & 31u))) != 0u; + } + @@ -166,11 +167,17 @@ float lutLookup(uint lut, int index) { return texelFetch(u_tex_luts, ivec2(index, int(lut)), 0).r; } @@ -50,6 +61,15 @@ index b9f9fe4c..f1cf286f 100644 } // Convert an arbitrary-width floating point literal to an f32 +@@ -201,7 +208,7 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light + // These are the spotlight attenuation LUTs + bit_in_config1 = 8 + int(light_id & 7u); + lut_index = 8u + light_id; +- } else if (lut_id <= 6) { ++ } else if (lut_id <= 6u) { + bit_in_config1 = 16 + int(lut_id); + lut_index = lut_id; + } else { @@ -210,16 +217,16 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light bool current_sampler_enabled = isSamplerEnabled(environment_id, lut_id); // 7 luts per environment @@ -70,19 +90,16 @@ index b9f9fe4c..f1cf286f 100644 switch (input_id) { case 0u: { delta = dot(normal, normalize(half_vector)); -@@ -241,11 +248,11 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light - int GPUREG_LIGHTi_SPOTDIR_LOW = int(readPicaReg(0x0146u + (light_id << 4u))); - int GPUREG_LIGHTi_SPOTDIR_HIGH = int(readPicaReg(0x0147u + (light_id << 4u))); +@@ -243,9 +250,9 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light -- // Sign extend them. Normally bitfieldExtract would do that but it's missing on some versions -+ // Sign extend them. Normally bitfieldExtractCompat would do that but it's missing on some versions + // Sign extend them. Normally bitfieldExtract would do that but it's missing on some versions // of GLSL so we do it manually - int se_x = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 13); - int se_y = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 13); - int se_z = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 13); -+ int se_x = bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 13); -+ int se_y = bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 13); -+ int se_z = bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 13); ++ int se_x = bitfieldExtract(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 0, 13); ++ int se_y = bitfieldExtract(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 16, 13); ++ int se_z = bitfieldExtract(uint(GPUREG_LIGHTi_SPOTDIR_HIGH), 0, 13); if ((se_x & 0x1000) == 0x1000) se_x |= 0xffffe000; if ((se_y & 0x1000) == 0x1000) se_y |= 0xffffe000; @@ -225,10 +242,10 @@ index 057f9a88..dc735ced 100644 v_quaternion = a_quaternion; } diff --git a/third_party/opengl/opengl.hpp b/third_party/opengl/opengl.hpp -index 4a08650a..21af37e3 100644 +index 607815fa..cbfcc096 100644 --- a/third_party/opengl/opengl.hpp +++ b/third_party/opengl/opengl.hpp -@@ -583,22 +583,22 @@ namespace OpenGL { +@@ -602,22 +602,22 @@ namespace OpenGL { static void disableScissor() { glDisable(GL_SCISSOR_TEST); } static void enableBlend() { glEnable(GL_BLEND); } static void disableBlend() { glDisable(GL_BLEND); } From 201edfb02df05025c3443fe067c4777915d9df6c Mon Sep 17 00:00:00 2001 From: Paris Oplopoios Date: Tue, 27 Aug 2024 19:47:27 +0300 Subject: [PATCH 04/17] I hate the gles.patch (#590) --- .github/gles.patch | 8 ++++---- src/core/PICA/shader_gen_glsl.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/gles.patch b/.github/gles.patch index c5cdb7d4..50721dac 100644 --- a/.github/gles.patch +++ b/.github/gles.patch @@ -21,7 +21,7 @@ index 990e2f80..2e7842ac 100644 void main() { diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag -index 9f07df0b..2ab623af 100644 +index 9f07df0b..75b708f7 100644 --- a/src/host_shaders/opengl_fragment_shader.frag +++ b/src/host_shaders/opengl_fragment_shader.frag @@ -1,4 +1,5 @@ @@ -97,9 +97,9 @@ index 9f07df0b..2ab623af 100644 - int se_x = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 13); - int se_y = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 13); - int se_z = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 13); -+ int se_x = bitfieldExtract(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 0, 13); -+ int se_y = bitfieldExtract(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 16, 13); -+ int se_z = bitfieldExtract(uint(GPUREG_LIGHTi_SPOTDIR_HIGH), 0, 13); ++ int se_x = bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 0, 13); ++ int se_y = bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 16, 13); ++ int se_z = bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_HIGH), 0, 13); if ((se_x & 0x1000) == 0x1000) se_x |= 0xffffe000; if ((se_y & 0x1000) == 0x1000) se_y |= 0xffffe000; diff --git a/src/core/PICA/shader_gen_glsl.cpp b/src/core/PICA/shader_gen_glsl.cpp index aa605dd2..154e403c 100644 --- a/src/core/PICA/shader_gen_glsl.cpp +++ b/src/core/PICA/shader_gen_glsl.cpp @@ -44,7 +44,6 @@ std::string FragmentGenerator::getDefaultVertexShader() { if (api == API::GLES) { ret += R"( #define USING_GLES 1 - #define fma(a, b, c) ((a) * (b) + (c)) precision mediump int; precision mediump float; @@ -108,6 +107,7 @@ std::string FragmentGenerator::generate(const FragmentConfig& config) { if (api == API::GLES) { ret += R"( #define USING_GLES 1 + #define fma(a, b, c) ((a) * (b) + (c)) precision mediump int; precision mediump float; From 595e4e0341e86c5f49befd168d675e032bbcb7f1 Mon Sep 17 00:00:00 2001 From: Paris Oplopoios Date: Wed, 28 Aug 2024 03:02:54 +0300 Subject: [PATCH 05/17] More implicit conversion fixes, hopefully the last ones this time (#591) * No implicit uint conversion * Update gles.patch --- .github/gles.patch | 8 ++++---- src/core/PICA/shader_gen_glsl.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/gles.patch b/.github/gles.patch index 50721dac..548b243d 100644 --- a/.github/gles.patch +++ b/.github/gles.patch @@ -21,7 +21,7 @@ index 990e2f80..2e7842ac 100644 void main() { diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag -index 9f07df0b..75b708f7 100644 +index 9f07df0b..96a35afa 100644 --- a/src/host_shaders/opengl_fragment_shader.frag +++ b/src/host_shaders/opengl_fragment_shader.frag @@ -1,4 +1,5 @@ @@ -97,9 +97,9 @@ index 9f07df0b..75b708f7 100644 - int se_x = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 13); - int se_y = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 13); - int se_z = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 13); -+ int se_x = bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 0, 13); -+ int se_y = bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 16, 13); -+ int se_z = bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_HIGH), 0, 13); ++ int se_x = int(bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 0, 13)); ++ int se_y = int(bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 16, 13)); ++ int se_z = int(bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_HIGH), 0, 13)); if ((se_x & 0x1000) == 0x1000) se_x |= 0xffffe000; if ((se_y & 0x1000) == 0x1000) se_y |= 0xffffe000; diff --git a/src/core/PICA/shader_gen_glsl.cpp b/src/core/PICA/shader_gen_glsl.cpp index 154e403c..69f74930 100644 --- a/src/core/PICA/shader_gen_glsl.cpp +++ b/src/core/PICA/shader_gen_glsl.cpp @@ -503,7 +503,7 @@ void FragmentGenerator::compileLights(std::string& shader, const PICA::FragmentC "].distanceAttenuationScale + lightSources[" + std::to_string(lightID) + "].distanceAttenuationBias, 0.0, 1.0);\n"; shader += "distance_attenuation = lutLookup(" + std::to_string(16 + lightID) + - ", int(clamp(floor(distance_att_delta * 256.0), 0.0, 255.0)));\n"; + "u, int(clamp(floor(distance_att_delta * 256.0), 0.0, 255.0)));\n"; } compileLUTLookup(shader, config, i, spotlightLutIndex); @@ -638,7 +638,7 @@ void FragmentGenerator::compileLUTLookup(std::string& shader, const PICA::Fragme if (absEnabled) { bool twoSidedDiffuse = config.lighting.lights[lightIndex].twoSidedDiffuse; shader += twoSidedDiffuse ? "lut_lookup_delta = abs(lut_lookup_delta);\n" : "lut_lookup_delta = max(lut_lookup_delta, 0.0);\n"; - shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + ", int(clamp(floor(lut_lookup_delta * 256.0), 0.0, 255.0)));\n"; + shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + "u, int(clamp(floor(lut_lookup_delta * 256.0), 0.0, 255.0)));\n"; if (scale != 0) { shader += "lut_lookup_result *= " + std::to_string(scales[scale]) + ";\n"; } @@ -646,7 +646,7 @@ void FragmentGenerator::compileLUTLookup(std::string& shader, const PICA::Fragme // Range is [-1, 1] so we need to map it to [0, 1] shader += "lut_lookup_index = int(clamp(floor(lut_lookup_delta * 128.0), -128.f, 127.f));\n"; shader += "if (lut_lookup_index < 0) lut_lookup_index += 256;\n"; - shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + ", lut_lookup_index);\n"; + shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + "u, lut_lookup_index);\n"; if (scale != 0) { shader += "lut_lookup_result *= " + std::to_string(scales[scale]) + ";\n"; } From 4adc50039cc22de2ee730dc83074760d72a8f3ce Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Wed, 28 Aug 2024 15:01:55 +0300 Subject: [PATCH 06/17] Add build option for opengl profile (#592) * Add opengl_profile build option on android the option is set to OpenGLES by default * Replace android checks with using_gles --- CMakeLists.txt | 14 ++++++++++++++ src/core/renderer_gl/renderer_gl.cpp | 2 +- src/hydra_core.cpp | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71c86578..107593d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-interference-size") endif() +if(ANDROID) + set(DEFAULT_OPENGL_PROFILE OpenGLES) +else() + set(DEFAULT_OPENGL_PROFILE OpenGL) +endif() + option(DISABLE_PANIC_DEV "Make a build with fewer and less intrusive asserts" ON) option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF) option(ENABLE_OPENGL "Enable OpenGL rendering backend" ON) @@ -49,6 +55,14 @@ option(BUILD_HYDRA_CORE "Build a Hydra core" OFF) option(BUILD_LIBRETRO_CORE "Build a Libretro core" OFF) option(ENABLE_RENDERDOC_API "Build with support for Renderdoc's capture API for graphics debugging" ON) +set(OPENGL_PROFILE ${DEFAULT_OPENGL_PROFILE} CACHE STRING "OpenGL profile to use if OpenGL is enabled. Valid values are 'OpenGL' and 'OpenGLES'.") +set_property(CACHE OPENGL_PROFILE PROPERTY STRINGS OpenGL OpenGLES) + +if(ENABLE_OPENGL AND (OPENGL_PROFILE STREQUAL "OpenGLES")) + message(STATUS "Building with OpenGLES support") + add_compile_definitions(USING_GLES) +endif() + if(BUILD_HYDRA_CORE) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index f8fc31e7..5146370a 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -49,7 +49,7 @@ void RendererGL::reset() { gl.useProgram(oldProgram); // Switch to old GL program } -#ifdef __ANDROID__ +#ifdef USING_GLES fragShaderGen.setTarget(PICA::ShaderGen::API::GLES, PICA::ShaderGen::Language::GLSL); #endif } diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 04fcdbdb..078b8a6c 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -114,7 +114,7 @@ hydra::Size HydraCore::getNativeSize() { return {400, 480}; } void HydraCore::setOutputSize(hydra::Size size) {} void HydraCore::resetContext() { -#ifdef __ANDROID__ +#ifdef USING_GLES if (!gladLoadGLES2Loader(reinterpret_cast(getProcAddress))) { Helpers::panic("OpenGL ES init failed"); } From 9055076d0d81d7b83fc88d652020a21f9a8f440d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:31:19 +0300 Subject: [PATCH 07/17] Add fdk-aac & AAC request structure --- .gitmodules | 3 +++ CMakeLists.txt | 3 ++- include/audio/aac.hpp | 9 +++++++++ third_party/fdk-aac | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) create mode 160000 third_party/fdk-aac diff --git a/.gitmodules b/.gitmodules index 656e1f41..981b4426 100644 --- a/.gitmodules +++ b/.gitmodules @@ -76,3 +76,6 @@ [submodule "third_party/metal-cpp"] path = third_party/metal-cpp url = https://github.com/Panda3DS-emu/metal-cpp +[submodule "third_party/fdk-aac"] + path = third_party/fdk-aac + url = https://github.com/mstorsjo/fdk-aac/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 107593d0..d1a67b79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,7 @@ else() endif() add_subdirectory(third_party/teakra EXCLUDE_FROM_ALL) +add_subdirectory(third_party/fdk-aac) set(CAPSTONE_ARCHITECTURE_DEFAULT OFF) set(CAPSTONE_ARM_SUPPORT ON) @@ -478,7 +479,7 @@ set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERN ${AUDIO_SOURCE_FILES} ${HEADER_FILES} ${FRONTEND_HEADER_FILES}) target_sources(AlberCore PRIVATE ${ALL_SOURCES}) -target_link_libraries(AlberCore PRIVATE dynarmic cryptopp glad resources_console_fonts teakra) +target_link_libraries(AlberCore PRIVATE dynarmic cryptopp glad resources_console_fonts teakra fdk-aac) target_link_libraries(AlberCore PUBLIC glad capstone) if(ENABLE_DISCORD_RPC AND NOT ANDROID) diff --git a/include/audio/aac.hpp b/include/audio/aac.hpp index afd2dbba..e59a006c 100644 --- a/include/audio/aac.hpp +++ b/include/audio/aac.hpp @@ -54,6 +54,13 @@ namespace Audio::AAC { u32_le sampleCount; }; + struct DecodeRequest { + u32_le address; // Address of input AAC stream + u32_le size; // Size of input AAC stream + u32_le destAddrLeft; // Output address for left channel samples + u32_le destAddrRight; // Output address for right channel samples + }; + struct Message { u16_le mode = Mode::None; // Encode or decode AAC? u16_le command = Command::Init; @@ -62,7 +69,9 @@ namespace Audio::AAC { // Info on the AAC request union { std::array commandData{}; + DecodeResponse decodeResponse; + DecodeRequest decodeRequest; }; }; diff --git a/third_party/fdk-aac b/third_party/fdk-aac new file mode 160000 index 00000000..716f4394 --- /dev/null +++ b/third_party/fdk-aac @@ -0,0 +1 @@ +Subproject commit 716f4394641d53f0d79c9ddac3fa93b03a49f278 From 545bbd5c45977e5d71acf76f7d792c5c1fad5e53 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 5 Sep 2024 00:06:48 +0300 Subject: [PATCH 08/17] HLE DSP: Implement AAC audio decoder --- CMakeLists.txt | 4 +- include/audio/aac_decoder.hpp | 24 ++++++ include/audio/hle_core.hpp | 8 +- src/core/audio/aac_decoder.cpp | 139 +++++++++++++++++++++++++++++++++ src/core/audio/hle_core.cpp | 8 +- 5 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 include/audio/aac_decoder.hpp create mode 100644 src/core/audio/aac_decoder.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d1a67b79..8407eccd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,7 +276,7 @@ set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selecto src/core/applets/error_applet.cpp ) set(AUDIO_SOURCE_FILES src/core/audio/dsp_core.cpp src/core/audio/null_core.cpp src/core/audio/teakra_core.cpp - src/core/audio/miniaudio_device.cpp src/core/audio/hle_core.cpp + src/core/audio/miniaudio_device.cpp src/core/audio/hle_core.cpp src/core/audio/aac_decoder.cpp ) set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) @@ -315,7 +315,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/audio/miniaudio_device.hpp include/ring_buffer.hpp include/bitfield.hpp include/audio/dsp_shared_mem.hpp include/audio/hle_core.hpp include/capstone.hpp include/audio/aac.hpp include/PICA/pica_frag_config.hpp include/PICA/pica_frag_uniforms.hpp include/PICA/shader_gen_types.hpp include/PICA/shader_decompiler.hpp - include/sdl_sensors.hpp include/renderdoc.hpp + include/sdl_sensors.hpp include/renderdoc.hpp include/audio/aac_decoder.hpp ) cmrc_add_resource_library( diff --git a/include/audio/aac_decoder.hpp b/include/audio/aac_decoder.hpp new file mode 100644 index 00000000..6538bce9 --- /dev/null +++ b/include/audio/aac_decoder.hpp @@ -0,0 +1,24 @@ +#pragma once +#include + +#include "audio/aac.hpp" +#include "helpers.hpp" + +struct AAC_DECODER_INSTANCE; + +namespace Audio::AAC { + class Decoder { + using DecoderHandle = AAC_DECODER_INSTANCE*; + using PaddrCallback = std::function; + + DecoderHandle decoderHandle = nullptr; + + bool isInitialized() { return decoderHandle != nullptr; } + void initialize(); + + public: + // Decode function. Takes in a reference to the AAC response & request, and a callback for paddr -> pointer conversions + void decode(AAC::Message& response, const AAC::Message& request, PaddrCallback paddrCallback); + ~Decoder(); + }; +} // namespace Audio::AAC \ No newline at end of file diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index c0e0896f..c36f0500 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -2,10 +2,12 @@ #include #include #include +#include #include #include #include "audio/aac.hpp" +#include "audio/aac_decoder.hpp" #include "audio/dsp_core.hpp" #include "audio/dsp_shared_mem.hpp" #include "memory.hpp" @@ -33,8 +35,8 @@ namespace Audio { SampleFormat format; SourceType sourceType; - bool fromQueue = false; // Is this buffer from the buffer queue or an embedded buffer? - bool hasPlayedOnce = false; // Has the buffer been played at least once before? + bool fromQueue = false; // Is this buffer from the buffer queue or an embedded buffer? + bool hasPlayedOnce = false; // Has the buffer been played at least once before? bool operator<(const Buffer& other) const { // Lower ID = Higher priority @@ -129,6 +131,8 @@ namespace Audio { std::array sources; // DSP voices Audio::HLE::DspMemory dspRam; + std::unique_ptr aacDecoder; + void resetAudioPipe(); bool loaded = false; // Have we loaded a component? diff --git a/src/core/audio/aac_decoder.cpp b/src/core/audio/aac_decoder.cpp new file mode 100644 index 00000000..281539d8 --- /dev/null +++ b/src/core/audio/aac_decoder.cpp @@ -0,0 +1,139 @@ +#include "audio/aac_decoder.hpp" + +#include + +#include +using namespace Audio; + +void AAC::Decoder::decode(AAC::Message& response, const AAC::Message& request, AAC::Decoder::PaddrCallback paddrCallback) { + // Copy the command and mode fields of the request to the response + response.command = request.command; + response.mode = request.mode; + response.decodeResponse.size = request.decodeRequest.size; + + // Write a dummy response at first. We'll be overwriting it later if decoding goes well + response.resultCode = AAC::ResultCode::Success; + response.decodeResponse.channelCount = 2; + response.decodeResponse.sampleCount = 1024; + response.decodeResponse.sampleRate = AAC::SampleRate::Rate48000; + + if (!isInitialized()) { + initialize(); + + // AAC decoder failed to initialize, return dummy data and return without decoding + if (!isInitialized()) { + Helpers::warn("Failed to initialize AAC decoder"); + return; + } + } + + u8* input = paddrCallback(request.decodeRequest.address); + const u8* inputEnd = paddrCallback(request.decodeRequest.address + request.decodeRequest.size); + u8* outputLeft = paddrCallback(request.decodeRequest.destAddrLeft); + u8* outputRight = nullptr; + + if (input == nullptr || inputEnd == nullptr || outputLeft == nullptr) { + Helpers::warn("Invalid pointers passed to AAC decoder"); + return; + } + + u32 bytesValid = request.decodeRequest.size; + u32 bufferSize = request.decodeRequest.size; + + // Each frame is 2048 samples with 2 channels + static constexpr usize frameSize = 2048 * 2; + std::array frame; + std::array, 2> audioStreams; + + bool queriedStreamInfo = false; + + while (bytesValid != 0) { + if (aacDecoder_Fill(decoderHandle, &input, &bufferSize, &bytesValid) != AAC_DEC_OK) { + Helpers::warn("Failed to fill AAC decoder with samples"); + return; + } + + auto decodeResult = aacDecoder_DecodeFrame(decoderHandle, frame.data(), frameSize, 0); + + if (decodeResult == AAC_DEC_TRANSPORT_SYNC_ERROR) { + // https://android.googlesource.com/platform/external/aac/+/2ddc922/libAACdec/include/aacdecoder_lib.h#362 + // According to the above, if we get a sync error, we're not meant to stop decoding, but rather just continue feeding data + } else if (decodeResult == AAC_DEC_OK) { + auto getSampleRate = [](u32 rate) { + switch (rate) { + case 8000: return AAC::SampleRate::Rate8000; + case 11025: return AAC::SampleRate::Rate11025; + case 12000: return AAC::SampleRate::Rate12000; + case 16000: return AAC::SampleRate::Rate16000; + case 22050: return AAC::SampleRate::Rate22050; + case 24000: return AAC::SampleRate::Rate24000; + case 32000: return AAC::SampleRate::Rate32000; + case 44100: return AAC::SampleRate::Rate44100; + case 48000: + default: return AAC::SampleRate::Rate48000; + } + }; + + auto info = aacDecoder_GetStreamInfo(decoderHandle); + response.decodeResponse.sampleCount = info->frameSize; + response.decodeResponse.channelCount = info->numChannels; + response.decodeResponse.sampleRate = getSampleRate(info->sampleRate); + + int channels = info->numChannels; + // Reserve space in our output stream vectors so push_back doesn't do allocations + for (int i = 0; i < channels; i++) { + audioStreams[i].reserve(audioStreams[i].size() + info->frameSize); + } + + // Fetch output pointer for right output channel if we've got > 1 channel + if (channels > 1 && outputRight == nullptr) { + outputRight = paddrCallback(request.decodeRequest.destAddrRight); + // If the right output channel doesn't point to a proper padddr, return + if (outputRight == nullptr) { + Helpers::warn("Right AAC output channel doesn't point to valid physical address"); + return; + } + } + + for (int sample = 0; sample < info->frameSize; sample++) { + for (int stream = 0; stream < channels; stream++) { + audioStreams[stream].push_back(frame[(sample * channels) + stream]); + } + } + } else { + Helpers::warn("Failed to decode AAC frame"); + return; + } + } + + for (int i = 0; i < 2; i++) { + auto& stream = audioStreams[i]; + u8* pointer = (i == 0) ? outputLeft : outputRight; + + if (!stream.empty() && pointer != nullptr) { + std::memcpy(pointer, stream.data(), stream.size() * sizeof(s16)); + } + } +} + +void AAC::Decoder::initialize() { + decoderHandle = aacDecoder_Open(TRANSPORT_TYPE::TT_MP4_ADTS, 1); + + if (decoderHandle == nullptr) [[unlikely]] { + return; + } + + // Cap output channel count to 2 + if (aacDecoder_SetParam(decoderHandle, AAC_PCM_MAX_OUTPUT_CHANNELS, 2) != AAC_DEC_OK) [[unlikely]] { + aacDecoder_Close(decoderHandle); + decoderHandle = nullptr; + return; + } +} + +AAC::Decoder::~Decoder() { + if (isInitialized()) { + aacDecoder_Close(decoderHandle); + decoderHandle = nullptr; + } +} \ No newline at end of file diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index 83271a43..b4f9ab02 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -6,6 +6,7 @@ #include #include +#include "audio/aac_decoder.hpp" #include "services/dsp.hpp" namespace Audio { @@ -23,6 +24,8 @@ namespace Audio { for (int i = 0; i < sources.size(); i++) { sources[i].index = i; } + + aacDecoder.reset(new Audio::AAC::Decoder()); } void HLE_DSP::resetAudioPipe() { @@ -584,7 +587,6 @@ namespace Audio { switch (request.command) { case AAC::Command::EncodeDecode: // Dummy response to stop games from hanging - // TODO: Fix this when implementing AAC response.resultCode = AAC::ResultCode::Success; response.decodeResponse.channelCount = 2; response.decodeResponse.sampleCount = 1024; @@ -593,6 +595,10 @@ namespace Audio { response.command = request.command; response.mode = request.mode; + + // We've already got an AAC decoder but it's currently disabled until mixing & output is properly implemented + // TODO: Uncomment this when the time comes + // aacDecoder->decode(response, request, [this](u32 paddr) { return getPointerPhys(paddr); }); break; case AAC::Command::Init: From 4b21d601e2b71a564cf09475bd8a57c8afa744d0 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 5 Sep 2024 00:35:31 +0300 Subject: [PATCH 09/17] Vendor fdk-aac --- .gitmodules | 2 +- third_party/fdk-aac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 981b4426..97bc129c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -78,4 +78,4 @@ url = https://github.com/Panda3DS-emu/metal-cpp [submodule "third_party/fdk-aac"] path = third_party/fdk-aac - url = https://github.com/mstorsjo/fdk-aac/ + url = https://github.com/Panda3DS-emu/fdk-aac/ diff --git a/third_party/fdk-aac b/third_party/fdk-aac index 716f4394..5559136b 160000 --- a/third_party/fdk-aac +++ b/third_party/fdk-aac @@ -1 +1 @@ -Subproject commit 716f4394641d53f0d79c9ddac3fa93b03a49f278 +Subproject commit 5559136bb53ce38f6f07dac4f47674dd4f032d03 From 656b7da5e43c2b2110657f9354339f7574beafec Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 5 Sep 2024 01:55:44 +0300 Subject: [PATCH 10/17] Properly pad AAC request struct --- include/audio/aac.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/audio/aac.hpp b/include/audio/aac.hpp index e59a006c..389ecc04 100644 --- a/include/audio/aac.hpp +++ b/include/audio/aac.hpp @@ -59,6 +59,8 @@ namespace Audio::AAC { u32_le size; // Size of input AAC stream u32_le destAddrLeft; // Output address for left channel samples u32_le destAddrRight; // Output address for right channel samples + u32_le unknown1; + u32_le unknown2; }; struct Message { From f1b7830952e98299a62d325333cbe83b7bf81e83 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Fri, 6 Sep 2024 00:43:41 +0300 Subject: [PATCH 11/17] Libretro: Complete code formatting (#594) * Libretro: Optimize range settings, fix default values * Libretro: More code formatting * Libretro: Fix loading of archived roms --- src/libretro_core.cpp | 165 +++++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 75 deletions(-) diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index cd0e9747..3f92cddd 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -8,13 +8,13 @@ #include #include -static retro_environment_t envCallbacks; -static retro_video_refresh_t videoCallbacks; +static retro_environment_t envCallback; +static retro_video_refresh_t videoCallback; static retro_audio_sample_batch_t audioBatchCallback; static retro_input_poll_t inputPollCallback; static retro_input_state_t inputStateCallback; -static retro_hw_render_callback hw_render; +static retro_hw_render_callback hwRender; static std::filesystem::path savePath; static bool screenTouched; @@ -30,17 +30,17 @@ std::filesystem::path Emulator::getAppDataRoot() { return std::filesystem::path(savePath / "Emulator Files"); } -static void* GetGLProcAddress(const char* name) { - return (void*)hw_render.get_proc_address(name); +static void* getGLProcAddress(const char* name) { + return (void*)hwRender.get_proc_address(name); } -static void VideoResetContext() { +static void videoResetContext() { #ifdef USING_GLES - if (!gladLoadGLES2Loader(reinterpret_cast(GetGLProcAddress))) { + if (!gladLoadGLES2Loader(reinterpret_cast(getGLProcAddress))) { Helpers::panic("OpenGL ES init failed"); } #else - if (!gladLoadGLLoader(reinterpret_cast(GetGLProcAddress))) { + if (!gladLoadGLLoader(reinterpret_cast(getGLProcAddress))) { Helpers::panic("OpenGL init failed"); } #endif @@ -48,31 +48,31 @@ static void VideoResetContext() { emulator->initGraphicsContext(nullptr); } -static void VideoDestroyContext() { - emulator->deinitGraphicsContext(); +static void videoDestroyContext() { + emulator->deinitGraphicsContext(); } -static bool SetHWRender(retro_hw_context_type type) { - hw_render.context_type = type; - hw_render.context_reset = VideoResetContext; - hw_render.context_destroy = VideoDestroyContext; - hw_render.bottom_left_origin = true; +static bool setHWRender(retro_hw_context_type type) { + hwRender.context_type = type; + hwRender.context_reset = videoResetContext; + hwRender.context_destroy = videoDestroyContext; + hwRender.bottom_left_origin = true; switch (type) { case RETRO_HW_CONTEXT_OPENGL_CORE: - hw_render.version_major = 4; - hw_render.version_minor = 1; + hwRender.version_major = 4; + hwRender.version_minor = 1; - if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) { + if (envCallback(RETRO_ENVIRONMENT_SET_HW_RENDER, &hwRender)) { return true; } break; case RETRO_HW_CONTEXT_OPENGLES3: case RETRO_HW_CONTEXT_OPENGL: - hw_render.version_major = 3; - hw_render.version_minor = 1; + hwRender.version_major = 3; + hwRender.version_minor = 1; - if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) { + if (envCallback(RETRO_ENVIRONMENT_SET_HW_RENDER, &hwRender)) { return true; } break; @@ -84,18 +84,18 @@ static bool SetHWRender(retro_hw_context_type type) { static void videoInit() { retro_hw_context_type preferred = RETRO_HW_CONTEXT_NONE; - envCallbacks(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred); + envCallback(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred); - if (preferred && SetHWRender(preferred)) return; - if (SetHWRender(RETRO_HW_CONTEXT_OPENGL_CORE)) return; - if (SetHWRender(RETRO_HW_CONTEXT_OPENGL)) return; - if (SetHWRender(RETRO_HW_CONTEXT_OPENGLES3)) return; + if (preferred && setHWRender(preferred)) return; + if (setHWRender(RETRO_HW_CONTEXT_OPENGL_CORE)) return; + if (setHWRender(RETRO_HW_CONTEXT_OPENGL)) return; + if (setHWRender(RETRO_HW_CONTEXT_OPENGLES3)) return; - hw_render.context_type = RETRO_HW_CONTEXT_NONE; + hwRender.context_type = RETRO_HW_CONTEXT_NONE; } -static bool GetButtonState(uint id) { return inputStateCallback(0, RETRO_DEVICE_JOYPAD, 0, id); } -static float GetAxisState(uint index, uint id) { return inputStateCallback(0, RETRO_DEVICE_ANALOG, index, id); } +static bool getButtonState(uint id) { return inputStateCallback(0, RETRO_DEVICE_JOYPAD, 0, id); } +static float getAxisState(uint index, uint id) { return inputStateCallback(0, RETRO_DEVICE_ANALOG, index, id); } static void inputInit() { static const retro_controller_description controllers[] = { @@ -108,7 +108,7 @@ static void inputInit() { {NULL, 0}, }; - envCallbacks(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); + envCallback(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); retro_input_descriptor desc[] = { {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"}, @@ -128,14 +128,14 @@ static void inputInit() { {0}, }; - envCallbacks(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &desc); + envCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &desc); } -static std::string FetchVariable(std::string key, std::string def) { +static std::string fetchVariable(std::string key, std::string def) { retro_variable var = {nullptr}; var.key = key.c_str(); - if (!envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value == nullptr) { + if (!envCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value == nullptr) { Helpers::warn("Fetching variable %s failed.", key.c_str()); return def; } @@ -143,13 +143,28 @@ static std::string FetchVariable(std::string key, std::string def) { return std::string(var.value); } -static bool FetchVariableBool(std::string key, bool def) { - return FetchVariable(key, def ? "enabled" : "disabled") == "enabled"; +static int fetchVariableInt(std::string key, int def) { + std::string value = fetchVariable(key, std::to_string(def)); + + if (!value.empty() && std::isdigit(value[0])) { + return std::stoi(value); + } + + return 0; +} + +static bool fetchVariableBool(std::string key, bool def) { + return fetchVariable(key, def ? "enabled" : "disabled") == "enabled"; +} + +static int fetchVariableRange(std::string key, int min, int max) { + return std::clamp(fetchVariableInt(key, min), min, max); } static void configInit() { static const retro_variable values[] = { - {"panda3ds_use_shader_jit", "Enable shader JIT; enabled|disabled"}, + {"panda3ds_use_shader_jit", EmulatorConfig::shaderJitDefault ? "Enable shader JIT; enabled|disabled" + : "Enable shader JIT; disabled|enabled"}, {"panda3ds_accurate_shader_mul", "Enable accurate shader multiplication; disabled|enabled"}, {"panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault ? "Use ubershaders (No stutter, maybe slower); enabled|disabled" : "Use ubershaders (No stutter, maybe slower); disabled|enabled"}, @@ -165,33 +180,33 @@ static void configInit() { {nullptr, nullptr}, }; - envCallbacks(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)values); + envCallback(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)values); } static void configUpdate() { EmulatorConfig& config = emulator->getConfig(); config.rendererType = RendererType::OpenGL; - config.vsyncEnabled = FetchVariableBool("panda3ds_use_vsync", true); - config.shaderJitEnabled = FetchVariableBool("panda3ds_use_shader_jit", true); - config.chargerPlugged = FetchVariableBool("panda3ds_use_charger", true); - config.batteryPercentage = std::clamp(std::stoi(FetchVariable("panda3ds_battery_level", "5")), 0, 100); - config.dspType = Audio::DSPCore::typeFromString(FetchVariable("panda3ds_dsp_emulation", "null")); - config.audioEnabled = FetchVariableBool("panda3ds_use_audio", false); - config.sdCardInserted = FetchVariableBool("panda3ds_use_virtual_sd", true); - config.sdWriteProtected = FetchVariableBool("panda3ds_write_protect_virtual_sd", false); - config.accurateShaderMul = FetchVariableBool("panda3ds_accurate_shader_mul", false); - config.useUbershaders = FetchVariableBool("panda3ds_use_ubershader", true); - config.forceShadergenForLights = FetchVariableBool("panda3ds_ubershader_lighting_override", true); - config.lightShadergenThreshold = std::clamp(std::stoi(FetchVariable("panda3ds_ubershader_lighting_override_threshold", "1")), 1, 8); + config.vsyncEnabled = fetchVariableBool("panda3ds_use_vsync", true); + config.shaderJitEnabled = fetchVariableBool("panda3ds_use_shader_jit", EmulatorConfig::shaderJitDefault); + config.chargerPlugged = fetchVariableBool("panda3ds_use_charger", true); + config.batteryPercentage = fetchVariableRange("panda3ds_battery_level", 5, 100); + config.dspType = Audio::DSPCore::typeFromString(fetchVariable("panda3ds_dsp_emulation", "null")); + config.audioEnabled = fetchVariableBool("panda3ds_use_audio", false); + config.sdCardInserted = fetchVariableBool("panda3ds_use_virtual_sd", true); + config.sdWriteProtected = fetchVariableBool("panda3ds_write_protect_virtual_sd", false); + config.accurateShaderMul = fetchVariableBool("panda3ds_accurate_shader_mul", false); + config.useUbershaders = fetchVariableBool("panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault); + config.forceShadergenForLights = fetchVariableBool("panda3ds_ubershader_lighting_override", true); + config.lightShadergenThreshold = fetchVariableRange("panda3ds_ubershader_lighting_override_threshold", 1, 8); config.discordRpcEnabled = false; config.save(); } -static void ConfigCheckVariables() { +static void configCheckVariables() { bool updated = false; - envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated); + envCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated); if (updated) { configUpdate(); @@ -203,7 +218,7 @@ void retro_get_system_info(retro_system_info* info) { info->valid_extensions = "3ds|3dsx|elf|axf|cci|cxi|app"; info->library_version = PANDA3DS_VERSION; info->library_name = "Panda3DS"; - info->block_extract = true; + info->block_extract = false; } void retro_get_system_av_info(retro_system_av_info* info) { @@ -219,11 +234,11 @@ void retro_get_system_av_info(retro_system_av_info* info) { } void retro_set_environment(retro_environment_t cb) { - envCallbacks = cb; + envCallback = cb; } void retro_set_video_refresh(retro_video_refresh_t cb) { - videoCallbacks = cb; + videoCallback = cb; } void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { @@ -242,15 +257,15 @@ void retro_set_input_state(retro_input_state_t cb) { void retro_init() { enum retro_pixel_format xrgb888 = RETRO_PIXEL_FORMAT_XRGB8888; - envCallbacks(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &xrgb888); + envCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &xrgb888); - char* save_dir = nullptr; + char* saveDir = nullptr; - if (!envCallbacks(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_dir) || save_dir == nullptr) { + if (!envCallback(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &saveDir) || saveDir == nullptr) { Helpers::warn("No save directory provided by LibRetro."); savePath = std::filesystem::current_path(); } else { - savePath = std::filesystem::path(save_dir); + savePath = std::filesystem::path(saveDir); } emulator = std::make_unique(); @@ -289,31 +304,31 @@ void retro_reset() { } void retro_run() { - ConfigCheckVariables(); + configCheckVariables(); - renderer->setFBO(hw_render.get_current_framebuffer()); + renderer->setFBO(hwRender.get_current_framebuffer()); renderer->resetStateManager(); inputPollCallback(); HIDService& hid = emulator->getServiceManager().getHID(); - hid.setKey(HID::Keys::A, GetButtonState(RETRO_DEVICE_ID_JOYPAD_A)); - hid.setKey(HID::Keys::B, GetButtonState(RETRO_DEVICE_ID_JOYPAD_B)); - hid.setKey(HID::Keys::X, GetButtonState(RETRO_DEVICE_ID_JOYPAD_X)); - hid.setKey(HID::Keys::Y, GetButtonState(RETRO_DEVICE_ID_JOYPAD_Y)); - hid.setKey(HID::Keys::L, GetButtonState(RETRO_DEVICE_ID_JOYPAD_L)); - hid.setKey(HID::Keys::R, GetButtonState(RETRO_DEVICE_ID_JOYPAD_R)); - hid.setKey(HID::Keys::Start, GetButtonState(RETRO_DEVICE_ID_JOYPAD_START)); - hid.setKey(HID::Keys::Select, GetButtonState(RETRO_DEVICE_ID_JOYPAD_SELECT)); - hid.setKey(HID::Keys::Up, GetButtonState(RETRO_DEVICE_ID_JOYPAD_UP)); - hid.setKey(HID::Keys::Down, GetButtonState(RETRO_DEVICE_ID_JOYPAD_DOWN)); - hid.setKey(HID::Keys::Left, GetButtonState(RETRO_DEVICE_ID_JOYPAD_LEFT)); - hid.setKey(HID::Keys::Right, GetButtonState(RETRO_DEVICE_ID_JOYPAD_RIGHT)); + hid.setKey(HID::Keys::A, getButtonState(RETRO_DEVICE_ID_JOYPAD_A)); + hid.setKey(HID::Keys::B, getButtonState(RETRO_DEVICE_ID_JOYPAD_B)); + hid.setKey(HID::Keys::X, getButtonState(RETRO_DEVICE_ID_JOYPAD_X)); + hid.setKey(HID::Keys::Y, getButtonState(RETRO_DEVICE_ID_JOYPAD_Y)); + hid.setKey(HID::Keys::L, getButtonState(RETRO_DEVICE_ID_JOYPAD_L)); + hid.setKey(HID::Keys::R, getButtonState(RETRO_DEVICE_ID_JOYPAD_R)); + hid.setKey(HID::Keys::Start, getButtonState(RETRO_DEVICE_ID_JOYPAD_START)); + hid.setKey(HID::Keys::Select, getButtonState(RETRO_DEVICE_ID_JOYPAD_SELECT)); + hid.setKey(HID::Keys::Up, getButtonState(RETRO_DEVICE_ID_JOYPAD_UP)); + hid.setKey(HID::Keys::Down, getButtonState(RETRO_DEVICE_ID_JOYPAD_DOWN)); + hid.setKey(HID::Keys::Left, getButtonState(RETRO_DEVICE_ID_JOYPAD_LEFT)); + hid.setKey(HID::Keys::Right, getButtonState(RETRO_DEVICE_ID_JOYPAD_RIGHT)); // Get analog values for the left analog stick (Right analog stick is N3DS-only and unimplemented) - float xLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X); - float yLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y); + float xLeft = getAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X); + float yLeft = getAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y); hid.setCirclepadX((xLeft / +32767) * 0x9C); hid.setCirclepadY((yLeft / -32767) * 0x9C); @@ -351,7 +366,7 @@ void retro_run() { hid.updateInputs(emulator->getTicks()); emulator->runFrame(); - videoCallbacks(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0); + videoCallback(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0); } void retro_set_controller_port_device(uint port, uint device) {} From c12c3bce8d9c660ba09443cb7df6d1ab8178e4a7 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Sat, 7 Sep 2024 16:20:41 +0300 Subject: [PATCH 12/17] Prevent app crash when miniaudio samples bigger than capacity (#596) * Prevent app crash when miniaudio samples bigger than capacity * Update miniaudio_device.cpp --------- Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> --- src/core/audio/miniaudio_device.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/audio/miniaudio_device.cpp b/src/core/audio/miniaudio_device.cpp index fa36cb84..dd5cfa85 100644 --- a/src/core/audio/miniaudio_device.cpp +++ b/src/core/audio/miniaudio_device.cpp @@ -90,16 +90,17 @@ void MiniAudioDevice::init(Samples& samples, bool safe) { deviceConfig.dataCallback = [](ma_device* device, void* out, const void* input, ma_uint32 frameCount) { auto self = reinterpret_cast(device->pUserData); s16* output = reinterpret_cast(out); + const usize maxSamples = std::min(self->samples->Capacity(), usize(frameCount * channelCount)); // Wait until there's enough samples to pop - while (self->samples->size() < frameCount * channelCount) { + while (self->samples->size() < maxSamples) { // If audio output is disabled from the emulator thread, make sure that this callback will return and not hang if (!self->running) { return; } } - self->samples->pop(output, frameCount * channelCount); + self->samples->pop(output, maxSamples); }; if (ma_device_init(&context, &deviceConfig, &device) != MA_SUCCESS) { From c0c8545dc204d9edffc8d8427d35613d28ea36fe Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:39:05 +0300 Subject: [PATCH 13/17] Qt: Fix Linguist issues and format files --- include/panda_qt/cheats_window.hpp | 64 +++++++++++++++++++++++ include/panda_qt/main_window.hpp | 2 +- src/panda_qt/about_window.cpp | 4 +- src/panda_qt/cheats_window.cpp | 84 +++++------------------------- src/panda_qt/mappings.cpp | 4 +- src/panda_qt/shader_editor.cpp | 3 +- 6 files changed, 84 insertions(+), 77 deletions(-) diff --git a/include/panda_qt/cheats_window.hpp b/include/panda_qt/cheats_window.hpp index c82b2bd8..93228d5e 100644 --- a/include/panda_qt/cheats_window.hpp +++ b/include/panda_qt/cheats_window.hpp @@ -1,6 +1,13 @@ #pragma once #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -24,3 +31,60 @@ class CheatsWindow final : public QWidget { std::filesystem::path cheatPath; Emulator* emu; }; + +struct CheatMetadata { + u32 handle = Cheats::badCheatHandle; + std::string name = "New cheat"; + std::string code; + bool enabled = true; +}; + +class CheatEntryWidget : public QWidget { + Q_OBJECT + + public: + CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent); + + void Update() { + name->setText(metadata.name.c_str()); + enabled->setChecked(metadata.enabled); + update(); + } + + void Remove() { + emu->getCheats().removeCheat(metadata.handle); + cheatList->takeItem(cheatList->row(listItem)); + deleteLater(); + } + + const CheatMetadata& getMetadata() { return metadata; } + void setMetadata(const CheatMetadata& metadata) { this->metadata = metadata; } + + private: + void checkboxChanged(int state); + void editClicked(); + + Emulator* emu; + CheatMetadata metadata; + u32 handle; + QLabel* name; + QCheckBox* enabled; + QListWidget* cheatList; + QListWidgetItem* listItem; +}; + +class CheatEditDialog : public QDialog { + Q_OBJECT + + public: + CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry); + + void accepted(); + void rejected(); + + private: + Emulator* emu; + CheatEntryWidget& cheatEntry; + QTextEdit* codeEdit; + QLineEdit* nameEdit; +}; \ No newline at end of file diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 3ff16a1d..fff99d20 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -140,7 +140,7 @@ class MainWindow : public QMainWindow { MainWindow(QApplication* app, QWidget* parent = nullptr); ~MainWindow(); - void closeEvent(QCloseEvent *event) override; + void closeEvent(QCloseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void mousePressEvent(QMouseEvent* event) override; diff --git a/src/panda_qt/about_window.cpp b/src/panda_qt/about_window.cpp index 60a91272..a388dad3 100644 --- a/src/panda_qt/about_window.cpp +++ b/src/panda_qt/about_window.cpp @@ -1,11 +1,13 @@ #include "panda_qt/about_window.hpp" -#include "version.hpp" +#include #include #include #include #include +#include "version.hpp" + // Based on https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/DolphinQt/AboutDialog.cpp AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) { diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index dbd251cc..cc2c94f6 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -1,15 +1,9 @@ #include "panda_qt/cheats_window.hpp" -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include +#include #include #include "cheats.hpp" @@ -18,71 +12,17 @@ MainWindow* mainWindow = nullptr; -struct CheatMetadata { - u32 handle = Cheats::badCheatHandle; - std::string name = "New cheat"; - std::string code; - bool enabled = true; -}; - void dispatchToMainThread(std::function callback) { - QTimer* timer = new QTimer(); - timer->moveToThread(qApp->thread()); - timer->setSingleShot(true); - QObject::connect(timer, &QTimer::timeout, [=]() - { - callback(); - timer->deleteLater(); - }); - QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); + QTimer* timer = new QTimer(); + timer->moveToThread(qApp->thread()); + timer->setSingleShot(true); + QObject::connect(timer, &QTimer::timeout, [=]() { + callback(); + timer->deleteLater(); + }); + QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); } -class CheatEntryWidget : public QWidget { - public: - CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent); - - void Update() { - name->setText(metadata.name.c_str()); - enabled->setChecked(metadata.enabled); - update(); - } - - void Remove() { - emu->getCheats().removeCheat(metadata.handle); - cheatList->takeItem(cheatList->row(listItem)); - deleteLater(); - } - - const CheatMetadata& getMetadata() { return metadata; } - void setMetadata(const CheatMetadata& metadata) { this->metadata = metadata; } - - private: - void checkboxChanged(int state); - void editClicked(); - - Emulator* emu; - CheatMetadata metadata; - u32 handle; - QLabel* name; - QCheckBox* enabled; - QListWidget* cheatList; - QListWidgetItem* listItem; -}; - -class CheatEditDialog : public QDialog { - public: - CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry); - - void accepted(); - void rejected(); - - private: - Emulator* emu; - CheatEntryWidget& cheatEntry; - QTextEdit* codeEdit; - QLineEdit* nameEdit; -}; - CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent) : QWidget(), emu(emu), metadata(metadata), cheatList(parent) { QHBoxLayout* layout = new QHBoxLayout; @@ -219,7 +159,7 @@ void CheatEditDialog::rejected() { CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent) : QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) { - mainWindow = static_cast(parent); + mainWindow = static_cast(parent); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(6, 6, 6, 6); @@ -265,4 +205,4 @@ void CheatsWindow::removeClicked() { CheatEntryWidget* entry = static_cast(cheatList->itemWidget(item)); entry->Remove(); -} +} \ No newline at end of file diff --git a/src/panda_qt/mappings.cpp b/src/panda_qt/mappings.cpp index 22741a73..d41b0a31 100644 --- a/src/panda_qt/mappings.cpp +++ b/src/panda_qt/mappings.cpp @@ -1,7 +1,7 @@ -#include "input_mappings.hpp" - #include +#include "input_mappings.hpp" + InputMappings InputMappings::defaultKeyboardMappings() { InputMappings mappings; mappings.setMapping(Qt::Key_L, HID::Keys::A); diff --git a/src/panda_qt/shader_editor.cpp b/src/panda_qt/shader_editor.cpp index 122d841f..4ca41e22 100644 --- a/src/panda_qt/shader_editor.cpp +++ b/src/panda_qt/shader_editor.cpp @@ -1,8 +1,9 @@ +#include "panda_qt/shader_editor.hpp" + #include #include #include "panda_qt/main_window.hpp" -#include "panda_qt/shader_editor.hpp" using namespace Zep; From 3567a4bc55ffa3ace89078f29e5abd07c980828e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:51:33 +0300 Subject: [PATCH 14/17] Update Github Actions checkout/upload-artifact versions --- .github/workflows/HTTP_Build.yml | 2 +- .github/workflows/Hydra_Build.yml | 8 ++++---- .github/workflows/Linux_AppImage_Build.yml | 4 ++-- .github/workflows/Linux_Build.yml | 4 ++-- .github/workflows/Qt_Build.yml | 8 ++++---- .github/workflows/Windows_Build.yml | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/HTTP_Build.yml b/.github/workflows/HTTP_Build.yml index 7bfe9c7f..0bdaa4f7 100644 --- a/.github/workflows/HTTP_Build.yml +++ b/.github/workflows/HTTP_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive diff --git a/.github/workflows/Hydra_Build.yml b/.github/workflows/Hydra_Build.yml index a269e839..e2c2004b 100644 --- a/.github/workflows/Hydra_Build.yml +++ b/.github/workflows/Hydra_Build.yml @@ -15,7 +15,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -58,7 +58,7 @@ jobs: runs-on: macos-13 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -101,7 +101,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -154,7 +154,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive diff --git a/.github/workflows/Linux_AppImage_Build.yml b/.github/workflows/Linux_AppImage_Build.yml index 7d198b9c..f32a7d38 100644 --- a/.github/workflows/Linux_AppImage_Build.yml +++ b/.github/workflows/Linux_AppImage_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -52,7 +52,7 @@ jobs: run: ./.github/linux-appimage.sh - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Linux executable path: './Alber-x86_64.AppImage' diff --git a/.github/workflows/Linux_Build.yml b/.github/workflows/Linux_Build.yml index 78e5cc5a..9cb05303 100644 --- a/.github/workflows/Linux_Build.yml +++ b/.github/workflows/Linux_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -49,7 +49,7 @@ jobs: run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Linux executable path: './build/Alber' diff --git a/.github/workflows/Qt_Build.yml b/.github/workflows/Qt_Build.yml index 4485cc1c..d3a09866 100644 --- a/.github/workflows/Qt_Build.yml +++ b/.github/workflows/Qt_Build.yml @@ -15,7 +15,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -45,7 +45,7 @@ jobs: windeployqt --dir upload upload/Alber.exe - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Windows executable path: upload @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -135,7 +135,7 @@ jobs: ./.github/linux-appimage-qt.sh - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Linux executable path: './Alber-x86_64.AppImage' diff --git a/.github/workflows/Windows_Build.yml b/.github/workflows/Windows_Build.yml index a06889eb..5497c3ef 100644 --- a/.github/workflows/Windows_Build.yml +++ b/.github/workflows/Windows_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -40,7 +40,7 @@ jobs: run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Windows executable path: './build/${{ env.BUILD_TYPE }}/Alber.exe' From 683b4186d8b34d414bd5418721dd831cf63922c6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 16 Sep 2024 23:36:42 +0300 Subject: [PATCH 15/17] Qt: Add CMake target for generating translations --- CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8407eccd..88a92f8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -519,6 +519,9 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) if(NOT ENABLE_OPENGL) message(FATAL_ERROR "Qt frontend requires OpenGL") endif() + + option(GENERATE_QT_TRANSLATION "Generate Qt translation file" OFF) + set(QT_LANGUAGES docs/translations) set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp @@ -556,6 +559,17 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) endif() endif() + # Generates an en.ts file for translations + # To update the file, use cmake --build --target Alber_lupdate + if(GENERATE_QT_TRANSLATION) + find_package(Qt6 REQUIRED COMPONENTS LinguistTools) + qt_add_lupdate(Alber TS_FILES ${QT_LANGUAGES}/en.ts + SOURCES ${FRONTEND_SOURCE_FILES} + INCLUDE_DIRECTORIES ${FRONTEND_HEADER_FILES} + NO_GLOBAL_TARGET + ) + endif() + qt_add_resources(AlberCore "app_images" PREFIX "/" FILES From 1c8b7a61b07a40f8d0657101e1ec467e7c6fca69 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 28 Sep 2024 22:56:26 +0300 Subject: [PATCH 16/17] Lua: Expose read/write functions for floats and doubles --- src/lua.cpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/lua.cpp b/src/lua.cpp index 6a16ab5b..5b78cec2 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -130,6 +130,32 @@ MAKE_MEMORY_FUNCTIONS(32) MAKE_MEMORY_FUNCTIONS(64) #undef MAKE_MEMORY_FUNCTIONS +static int readFloatThunk(lua_State* L) { + const u32 vaddr = (u32)lua_tonumber(L, 1); + lua_pushnumber(L, (lua_Number)Helpers::bit_cast(LuaManager::g_emulator->getMemory().read32(vaddr))); + return 1; +} + +static int writeFloatThunk(lua_State* L) { + const u32 vaddr = (u32)lua_tonumber(L, 1); + const float value = (float)lua_tonumber(L, 2); + LuaManager::g_emulator->getMemory().write32(vaddr, Helpers::bit_cast(value)); + return 0; +} + +static int readDoubleThunk(lua_State* L) { + const u32 vaddr = (u32)lua_tonumber(L, 1); + lua_pushnumber(L, (lua_Number)Helpers::bit_cast(LuaManager::g_emulator->getMemory().read64(vaddr))); + return 1; +} + +static int writeDoubleThunk(lua_State* L) { + const u32 vaddr = (u32)lua_tonumber(L, 1); + const double value = (double)lua_tonumber(L, 2); + LuaManager::g_emulator->getMemory().write64(vaddr, Helpers::bit_cast(value)); + return 0; +} + static int getAppIDThunk(lua_State* L) { std::optional id = LuaManager::g_emulator->getMemory().getProgramID(); @@ -248,10 +274,14 @@ static constexpr luaL_Reg functions[] = { { "__read16", read16Thunk }, { "__read32", read32Thunk }, { "__read64", read64Thunk }, + { "__readFloat", readFloatThunk }, + { "__readDouble", readDoubleThunk }, { "__write8", write8Thunk} , { "__write16", write16Thunk }, { "__write32", write32Thunk }, { "__write64", write64Thunk }, + { "__writeFloat", writeFloatThunk }, + { "__writeDouble", writeDoubleThunk }, { "__getAppID", getAppIDThunk }, { "__pause", pauseThunk }, { "__resume", resumeThunk }, @@ -273,10 +303,15 @@ void LuaManager::initializeThunks() { read16 = function(addr) return GLOBALS.__read16(addr) end, read32 = function(addr) return GLOBALS.__read32(addr) end, read64 = function(addr) return GLOBALS.__read64(addr) end, + readFloat = function(addr) return GLOBALS.__readFloat(addr) end, + readDouble = function(addr) return GLOBALS.__readDouble(addr) end, + write8 = function(addr, value) GLOBALS.__write8(addr, value) end, write16 = function(addr, value) GLOBALS.__write16(addr, value) end, write32 = function(addr, value) GLOBALS.__write32(addr, value) end, write64 = function(addr, value) GLOBALS.__write64(addr, value) end, + writeFloat = function(addr, value) GLOBALS.__writeFloat(addr, value) end, + writeDouble = function(addr, value) GLOBALS.__writeDouble(addr, value) end, getAppID = function() local ffi = require("ffi") From 3e72323653cdfd31b0d1428f919847b5d20077fc Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 29 Sep 2024 00:32:51 +0300 Subject: [PATCH 17/17] HLE DSP: Initial mixer work --- include/audio/dsp_shared_mem.hpp | 8 +++--- include/audio/hle_core.hpp | 46 +++++++++++++++++++++++++++++-- src/core/audio/hle_core.cpp | 47 ++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/include/audio/dsp_shared_mem.hpp b/include/audio/dsp_shared_mem.hpp index e776211d..272edf7e 100644 --- a/include/audio/dsp_shared_mem.hpp +++ b/include/audio/dsp_shared_mem.hpp @@ -324,8 +324,8 @@ namespace Audio::HLE { BitField<15, 1, u32> outputBufferCountDirty; BitField<16, 1, u32> masterVolumeDirty; - BitField<24, 1, u32> auxReturnVolume0Dirty; - BitField<25, 1, u32> auxReturnVolume1Dirty; + BitField<24, 1, u32> auxVolume0Dirty; + BitField<25, 1, u32> auxVolume1Dirty; BitField<26, 1, u32> outputFormatDirty; BitField<27, 1, u32> clippingModeDirty; BitField<28, 1, u32> headphonesConnectedDirty; @@ -337,7 +337,7 @@ namespace Audio::HLE { /// The DSP has three intermediate audio mixers. This controls the volume level (0.0-1.0) for /// each at the final mixer. float_le masterVolume; - std::array auxReturnVolume; + std::array auxVolumes; u16_le outputBufferCount; u16 pad1[2]; @@ -422,7 +422,7 @@ namespace Audio::HLE { struct DspStatus { u16_le unknown; - u16_le dropped_frames; + u16_le droppedFrames; u16 pad0[0xE]; }; ASSERT_DSP_STRUCT(DspStatus, 32); diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index c36f0500..bd717237 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -95,8 +95,7 @@ namespace Audio { DSPSource() { reset(); } }; - class HLE_DSP : public DSPCore { - // The audio frame types are public in case we want to use them for unit tests + class DSPMixer { public: template using Sample = std::array; @@ -113,6 +112,43 @@ namespace Audio { template using QuadFrame = Frame; + private: + using ChannelFormat = HLE::DspConfiguration::OutputFormat; + // The audio from each DSP voice is converted to quadraphonic and then fed into 3 intermediate mixing stages + // Two of these intermediate mixers (second and third) are used for effects, including custom effects done on the CPU + static constexpr usize mixerStageCount = 3; + + public: + ChannelFormat channelFormat = ChannelFormat::Stereo; + std::array volumes; + std::array enableAuxStages; + + void reset() { + channelFormat = ChannelFormat::Stereo; + + volumes.fill(0.0); + enableAuxStages.fill(false); + } + }; + + class HLE_DSP : public DSPCore { + // The audio frame types are public in case we want to use them for unit tests + public: + template + using Sample = DSPMixer::Sample; + + template + using Frame = DSPMixer::Frame; + + template + using MonoFrame = DSPMixer::MonoFrame; + + template + using StereoFrame = DSPMixer::StereoFrame; + + template + using QuadFrame = DSPMixer::QuadFrame; + using Source = Audio::DSPSource; using SampleBuffer = Source::SampleBuffer; @@ -131,6 +167,7 @@ namespace Audio { std::array sources; // DSP voices Audio::HLE::DspMemory dspRam; + Audio::DSPMixer mixer; std::unique_ptr aacDecoder; void resetAudioPipe(); @@ -175,10 +212,13 @@ namespace Audio { void handleAACRequest(const AAC::Message& request); void updateSourceConfig(Source& source, HLE::SourceConfiguration::Configuration& config, s16_le* adpcmCoefficients); + void updateMixerConfig(HLE::SharedMemory& sharedMem); void generateFrame(StereoFrame& frame); void generateFrame(DSPSource& source); void outputFrame(); - + // Perform the final mix, mixing the quadraphonic samples from all voices into the output audio frame + void performMix(Audio::HLE::SharedMemory& readRegion, Audio::HLE::SharedMemory& writeRegion); + // Decode an entire buffer worth of audio void decodeBuffer(DSPSource& source); diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index b4f9ab02..a616f317 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -76,6 +76,7 @@ namespace Audio { source.reset(); } + mixer.reset(); // Note: Reset audio pipe AFTER resetting all pipes, otherwise the new data will be yeeted resetAudioPipe(); } @@ -250,6 +251,8 @@ namespace Audio { source.isBufferIDDirty = false; } + + performMix(read, write); } void HLE_DSP::updateSourceConfig(Source& source, HLE::SourceConfiguration::Configuration& config, s16_le* adpcmCoefficients) { @@ -465,6 +468,50 @@ namespace Audio { } } + void HLE_DSP::performMix(Audio::HLE::SharedMemory& readRegion, Audio::HLE::SharedMemory& writeRegion) { + updateMixerConfig(readRegion); + // TODO: Do the actual audio mixing + + auto& dspStatus = writeRegion.dspStatus; + // Stub the DSP status. It's unknown what the "unknown" field is but Citra sets it to 0, so we do too to be safe + dspStatus.droppedFrames = 0; + dspStatus.unknown = 0; + } + + void HLE_DSP::updateMixerConfig(Audio::HLE::SharedMemory& sharedMem) { + auto& config = sharedMem.dspConfiguration; + // No configs have been changed, so there's nothing to update + if (config.dirtyRaw == 0) { + return; + } + + if (config.outputFormatDirty) { + mixer.channelFormat = config.outputFormat; + } + + if (config.masterVolumeDirty) { + mixer.volumes[0] = config.masterVolume; + } + + if (config.auxVolume0Dirty) { + mixer.volumes[1] = config.auxVolumes[0]; + } + + if (config.auxVolume1Dirty) { + mixer.volumes[2] = config.auxVolumes[1]; + } + + if (config.auxBusEnable0Dirty) { + mixer.enableAuxStages[0] = config.auxBusEnable[0] != 0; + } + + if (config.auxBusEnable1Dirty) { + mixer.enableAuxStages[1] = config.auxBusEnable[1] != 0; + } + + config.dirtyRaw = 0; + } + HLE_DSP::SampleBuffer HLE_DSP::decodePCM8(const u8* data, usize sampleCount, Source& source) { SampleBuffer decodedSamples(sampleCount);