diff --git a/.github/workflows/Android_Build.yml b/.github/workflows/Android_Build.yml index b7e64f5f..3ddcffd5 100644 --- a/.github/workflows/Android_Build.yml +++ b/.github/workflows/Android_Build.yml @@ -23,6 +23,9 @@ jobs: - name: Fetch submodules run: git submodule update --init --recursive + - name: Setup CCache + uses: hendrikmuhs/ccache-action@v1.2 + - name: Set up gradle caches uses: actions/cache@v4 with: @@ -36,7 +39,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -47,7 +50,7 @@ jobs: java-version: '17' - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DENABLE_VULKAN=0 -DENABLE_USER_BUILD=ON + run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DENABLE_VULKAN=0 -DENABLE_USER_BUILD=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: | @@ -88,6 +91,9 @@ jobs: - name: Fetch submodules run: git submodule update --init --recursive + - name: Setup CCache + uses: hendrikmuhs/ccache-action@v1.2 + - name: Set up gradle caches uses: actions/cache@v4 with: @@ -101,7 +107,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -112,7 +118,7 @@ jobs: java-version: '17' - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DENABLE_VULKAN=0 -DENABLE_USER_BUILD=ON -DCMAKE_CXX_FLAGS="-march=armv8-a+crypto" + run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DENABLE_VULKAN=0 -DENABLE_USER_BUILD=ON -DCMAKE_CXX_FLAGS="-march=armv8-a+crypto" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: | diff --git a/.github/workflows/HTTP_Build.yml b/.github/workflows/HTTP_Build.yml index c4f7cfee..1f95ce0c 100644 --- a/.github/workflows/HTTP_Build.yml +++ b/.github/workflows/HTTP_Build.yml @@ -32,7 +32,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang diff --git a/.github/workflows/Hydra_Build.yml b/.github/workflows/Hydra_Build.yml index 1b31edf7..e040a1f8 100644 --- a/.github/workflows/Hydra_Build.yml +++ b/.github/workflows/Hydra_Build.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -65,7 +65,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -118,7 +118,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -165,7 +165,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -180,3 +180,36 @@ jobs: with: name: Android Hydra core path: '${{github.workspace}}/build/libAlber.so' + + ARM-Libretro: + runs-on: ubuntu-24.04-arm + + steps: + - uses: actions/checkout@v4 + - name: Fetch submodules + run: git submodule update --init --recursive + + - name: Install misc packages + run: | + sudo apt-get update && sudo apt install libx11-dev libxext-dev libgl1 libglx-mesa0 mesa-common-dev libfuse2 libwayland-dev + + - name: Install newer Clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 17 + + - name: Configure CMake + run: | + cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DBUILD_LIBRETRO_CORE=ON -DENABLE_VULKAN=OFF -DCRYPTOPP_OPT_DISABLE_ASM=ON + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Upload Libretro core + uses: actions/upload-artifact@v4 + with: + name: Linux arm64 Libretro core + path: | + ${{github.workspace}}/build/panda3ds_libretro.so + ${{github.workspace}}/docs/libretro/panda3ds_libretro.info diff --git a/.github/workflows/Linux_AppImage_Build.yml b/.github/workflows/Linux_AppImage_Build.yml index 3c5af88a..be21a31f 100644 --- a/.github/workflows/Linux_AppImage_Build.yml +++ b/.github/workflows/Linux_AppImage_Build.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang diff --git a/.github/workflows/Linux_Build.yml b/.github/workflows/Linux_Build.yml index 61f7eafa..5dac55d1 100644 --- a/.github/workflows/Linux_Build.yml +++ b/.github/workflows/Linux_Build.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang diff --git a/.github/workflows/MacOS_Build.yml b/.github/workflows/MacOS_Build.yml index ab8702fd..8100079c 100644 --- a/.github/workflows/MacOS_Build.yml +++ b/.github/workflows/MacOS_Build.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang diff --git a/.github/workflows/Qt_Build.yml b/.github/workflows/Qt_Build.yml index fc4072da..bc545caf 100644 --- a/.github/workflows/Qt_Build.yml +++ b/.github/workflows/Qt_Build.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -66,7 +66,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang @@ -164,7 +164,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang diff --git a/.github/workflows/Windows_Build.yml b/.github/workflows/Windows_Build.yml index 5497c3ef..a4de2e60 100644 --- a/.github/workflows/Windows_Build.yml +++ b/.github/workflows/Windows_Build.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 with: - vulkan-query-version: latest + vulkan-query-version: 1.3.296.0 vulkan-use-cache: true vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang diff --git a/CMakeLists.txt b/CMakeLists.txt index be70036c..92bff3d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ 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) option(DISABLE_SSE4 "Build with SSE4 instructions disabled, may reduce performance" OFF) +option(USE_LIBRETRO_AUDIO "Enable to use the LR audio device with the LR core. Otherwise our own device is used" OFF) 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) @@ -80,6 +81,10 @@ endif() if(BUILD_LIBRETRO_CORE) set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_compile_definitions(__LIBRETRO__) + + if(USE_LIBRETRO_AUDIO) + add_compile_definitions(USE_LIBRETRO_AUDIO_DEVICE) + endif() endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND ENABLE_USER_BUILD) @@ -157,7 +162,6 @@ if(ENABLE_DISCORD_RPC AND NOT ANDROID) include_directories(third_party/discord-rpc/include) endif() - if (NOT ANDROID) if (USE_SYSTEM_SDL2) find_package(SDL2 CONFIG REQUIRED) @@ -402,7 +406,8 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp include/audio/audio_interpolation.hpp include/audio/hle_mixer.hpp include/audio/dsp_simd.hpp include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/fs/archive_twl_photo.hpp - include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp + include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp + include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp ) cmrc_add_resource_library( diff --git a/include/audio/audio_device.hpp b/include/audio/audio_device.hpp new file mode 100644 index 00000000..966fd667 --- /dev/null +++ b/include/audio/audio_device.hpp @@ -0,0 +1,9 @@ +#pragma once + +#if defined(__LIBRETRO__) && defined(USE_LIBRETRO_AUDIO_DEVICE) +#include "audio/libretro_audio_device.hpp" +using AudioDevice = LibretroAudioDevice; +#else +#include "audio/miniaudio_device.hpp" +using AudioDevice = MiniAudioDevice; +#endif \ No newline at end of file diff --git a/include/audio/audio_device_interface.hpp b/include/audio/audio_device_interface.hpp new file mode 100644 index 00000000..de70c77a --- /dev/null +++ b/include/audio/audio_device_interface.hpp @@ -0,0 +1,36 @@ +#pragma once +#include + +#include "config.hpp" +#include "helpers.hpp" +#include "ring_buffer.hpp" + +class AudioDeviceInterface { + protected: + static constexpr usize maxFrameCount = 0x2000; + + using Samples = Common::RingBuffer; + using RenderBatchCallback = usize (*)(const s16*, usize); + + Samples* samples = nullptr; + + const AudioDeviceConfig& audioSettings; + // Store the last stereo sample we output. We play this when underruning to avoid pops. + std::array lastStereoSample{}; + + public: + AudioDeviceInterface(Samples* samples, const AudioDeviceConfig& audioSettings) : samples(samples), audioSettings(audioSettings) {} + + bool running = false; + Samples* getSamples() { return samples; } + + // If safe is on, we create a null audio device + virtual void init(Samples& samples, bool safe = false) = 0; + virtual void close() = 0; + + virtual void start() = 0; + virtual void stop() = 0; + + // Only used for audio devices that render multiple audio frames in one go, eg the libretro audio device. + virtual void renderBatch(RenderBatchCallback callback) {} +}; \ No newline at end of file diff --git a/include/audio/libretro_audio_device.hpp b/include/audio/libretro_audio_device.hpp new file mode 100644 index 00000000..9c6d19f8 --- /dev/null +++ b/include/audio/libretro_audio_device.hpp @@ -0,0 +1,61 @@ +#pragma once +#include + +#include "audio/audio_device_interface.hpp" + +class LibretroAudioDevice final : public AudioDeviceInterface { + bool initialized = false; + + public: + LibretroAudioDevice(const AudioDeviceConfig& audioSettings) : AudioDeviceInterface(nullptr, audioSettings), initialized(false) { + running = false; + } + + void init(Samples& samples, bool safe = false) override { + this->samples = &samples; + + initialized = true; + running = false; + } + + void close() override { + initialized = false; + running = false; + }; + + void start() override { running = true; } + void stop() override { running = false; }; + + void renderBatch(RenderBatchCallback callback) override { + if (running) { + static constexpr usize sampleRate = 32768; // 3DS samples per second + static constexpr usize frameCount = sampleRate / 60; // 3DS samples per video frame + static constexpr usize channelCount = 2; + static s16 audioBuffer[frameCount * channelCount]; + + usize samplesWritten = 0; + samplesWritten += samples->pop(audioBuffer, frameCount * channelCount); + + // Get the last sample for underrun handling + if (samplesWritten != 0) { + std::memcpy(&lastStereoSample[0], &audioBuffer[(samplesWritten - 1) * 2], sizeof(lastStereoSample)); + } + + // If underruning, copy the last output sample + { + s16* pointer = &audioBuffer[samplesWritten * 2]; + s16 l = lastStereoSample[0]; + s16 r = lastStereoSample[1]; + + for (usize i = samplesWritten; i < frameCount; i++) { + *pointer++ = l; + *pointer++ = r; + } + } + + callback(audioBuffer, sizeof(audioBuffer) / (channelCount * sizeof(s16))); + } + } + + bool isInitialized() const { return initialized; } +}; diff --git a/include/audio/miniaudio_device.hpp b/include/audio/miniaudio_device.hpp index 0363aa44..5cf7c801 100644 --- a/include/audio/miniaudio_device.hpp +++ b/include/audio/miniaudio_device.hpp @@ -3,39 +3,31 @@ #include #include -#include "config.hpp" -#include "helpers.hpp" +#include "audio/audio_device_interface.hpp" #include "miniaudio.h" -#include "ring_buffer.hpp" -class MiniAudioDevice { - using Samples = Common::RingBuffer; +class MiniAudioDevice final : public AudioDeviceInterface { static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate static constexpr ma_uint32 channelCount = 2; // Audio output is stereo + bool initialized = false; + ma_device device; ma_context context; ma_device_config deviceConfig; - Samples* samples = nullptr; - - const AudioDeviceConfig& audioSettings; - - bool initialized = false; - bool running = false; // Store the last stereo sample we output. We play this when underruning to avoid pops. - std::array lastStereoSample; std::vector audioDevices; public: MiniAudioDevice(const AudioDeviceConfig& audioSettings); // If safe is on, we create a null audio device - void init(Samples& samples, bool safe = false); - void close(); + void init(Samples& samples, bool safe = false) override; + void close() override; - void start(); - void stop(); + void start() override; + void stop() override; bool isInitialized() const { return initialized; } }; \ No newline at end of file diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 43f31d30..24358533 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -181,5 +181,7 @@ class CPU { void addTicks(u64 ticks) { env.AddTicks(ticks); } void clearCache() { jit->ClearCache(); } + void clearCacheRange(u32 start, u32 size) { jit->InvalidateCacheRange(start, size); } + void runFrame(); }; \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index a222a021..326eb232 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -7,8 +7,8 @@ #include #include "PICA/gpu.hpp" +#include "audio/audio_device.hpp" #include "audio/dsp_core.hpp" -#include "audio/miniaudio_device.hpp" #include "cheats.hpp" #include "config.hpp" #include "cpu.hpp" @@ -48,14 +48,14 @@ class Emulator { Scheduler scheduler; Crypto::AESEngine aesEngine; - MiniAudioDevice audioDevice; + AudioDevice audioDevice; Cheats cheats; public: static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens ROMType romType = ROMType::None; - bool running = false; // Is the emulator running a game? + bool running = false; // Is the emulator running a game? private: #ifdef PANDA3DS_ENABLE_HTTP_SERVER @@ -126,6 +126,7 @@ class Emulator { LuaManager& getLua() { return lua; } Scheduler& getScheduler() { return scheduler; } Memory& getMemory() { return memory; } + AudioDeviceInterface& getAudioDevice() { return audioDevice; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index abc508ac..3f20b5e1 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -175,6 +175,8 @@ public: void svcSignalEvent(); void svcSetTimer(); void svcSleepThread(); + void svcInvalidateInstructionCacheRange(); + void svcInvalidateEntireInstructionCache(); void connectToPort(); void outputDebugString(); void waitSynchronization1(); @@ -250,4 +252,6 @@ public: void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); } void clearInstructionCache(); + void clearInstructionCacheRange(u32 start, u32 size); + u32 getSharedFontVaddr(); }; \ No newline at end of file diff --git a/include/kernel/resource_limits.hpp b/include/kernel/resource_limits.hpp index 85f1a59b..fe1154ff 100644 --- a/include/kernel/resource_limits.hpp +++ b/include/kernel/resource_limits.hpp @@ -19,7 +19,7 @@ struct ResourceLimitValues { // APPLICATION resource limit static constexpr ResourceLimitValues appResourceLimits = { .maxPriority = 0x18, - .maxCommit = 0x4000000, + .maxCommit = 64_MB + 16_MB, // We're currently giving 80MB to all apps. TODO: Implement extended memory properly .maxThreads = 0x20, .maxEvents = 0x20, .maxMutexes = 0x20, @@ -33,7 +33,7 @@ static constexpr ResourceLimitValues appResourceLimits = { // SYS_APPLET resource limit static constexpr ResourceLimitValues sysAppletResourceLimits = { .maxPriority = 0x4, - .maxCommit = 0x5E00000, + .maxCommit = 0x5E00000 - 16_MB, .maxThreads = 0x1D, .maxEvents = 0xB, .maxMutexes = 0x8, diff --git a/include/memory.hpp b/include/memory.hpp index bd002c54..b1dd09de 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -132,7 +132,7 @@ public: static constexpr u32 totalPageCount = 1 << (32 - pageShift); static constexpr u32 FCRAM_SIZE = u32(128_MB); - static constexpr u32 FCRAM_APPLICATION_SIZE = u32(64_MB); + static constexpr u32 FCRAM_APPLICATION_SIZE = u32(80_MB); static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize; static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize; diff --git a/include/renderer.hpp b/include/renderer.hpp index b458ecce..ca28455f 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -53,6 +53,7 @@ class Renderer { EmulatorConfig* emulatorConfig = nullptr; + void doSoftwareTextureCopy(u32 inputAddr, u32 outputAddr, u32 copySize, u32 inputWidth, u32 inputGap, u32 outputWidth, u32 outputGap); public: Renderer(GPU& gpu, const std::array& internalRegs, const std::array& externalRegs); virtual ~Renderer(); diff --git a/readme.md b/readme.md index 7ffb7384..f58f316c 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ Panda3DS is still in the early stages of development. Many games boot, many don' For documenting game compatibility, make sure to visit the [games list repository](https://github.com/Panda3DS-emu/Panda3DS-Games-List). For miscellaneous issues or more technical issues, feel free to use this repo's issues tab. # Why? -The 3DS emulation scene is already pretty mature, with offerings such as [Citra](https://github.com/citra-emu/citra) which can offer a great playing experience for most games in the library, [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an innovative LLE emulator, or [Mikage](https://mikage.app/). However, there's always room for more emulators! While Panda3DS was initially a mere curiosity, there's many different concepts I would like to explore with it in the future, such as: +The 3DS emulation scene is already pretty mature, with offerings such as Citra which can offer a great playing experience for most games in the library, [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an innovative LLE emulator, or [Mikage](https://mikage.app/). However, there's always room for more emulators! While Panda3DS was initially a mere curiosity, there's many different concepts I would like to explore with it in the future, such as: - Virtualization. What motivated the creation of this emulator was actually a discussion on whether it is possible to get fast 3DS emulation on low-end hardware such as the Raspberry Pi 4, using the KVM API. At the moment, Panda3DS is powered by Dynarmic rather than using virtualization, but this is definitely a concept I want to explore in the future. diff --git a/src/core/audio/miniaudio_device.cpp b/src/core/audio/miniaudio_device.cpp index 550fb039..8e2a1e71 100644 --- a/src/core/audio/miniaudio_device.cpp +++ b/src/core/audio/miniaudio_device.cpp @@ -7,8 +7,9 @@ #include "helpers.hpp" -MiniAudioDevice::MiniAudioDevice(const AudioDeviceConfig& audioSettings) - : initialized(false), running(false), samples(nullptr), audioSettings(audioSettings) {} +MiniAudioDevice::MiniAudioDevice(const AudioDeviceConfig& audioSettings) : AudioDeviceInterface(nullptr, audioSettings), initialized(false) { + running = false; +} void MiniAudioDevice::init(Samples& samples, bool safe) { this->samples = &samples; @@ -212,4 +213,4 @@ void MiniAudioDevice::close() { ma_device_uninit(&device); ma_context_uninit(&context); } -} +} \ No newline at end of file diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp index d4229b55..e097fab6 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -69,6 +69,10 @@ void Kernel::serviceSVC(u32 svc) { case 0x3A: getResourceLimitCurrentValues(); break; case 0x3B: getThreadContext(); break; case 0x3D: outputDebugString(); break; + + // Luma SVCs + case 0x93: svcInvalidateInstructionCacheRange(); break; + case 0x94: svcInvalidateEntireInstructionCache(); break; default: Helpers::panic("Unimplemented svc: %X @ %08X", svc, regs[15]); break; } @@ -298,6 +302,23 @@ void Kernel::duplicateHandle() { } void Kernel::clearInstructionCache() { cpu.clearCache(); } +void Kernel::clearInstructionCacheRange(u32 start, u32 size) { cpu.clearCacheRange(start, size); } + +void Kernel::svcInvalidateInstructionCacheRange() { + const u32 start = regs[0]; + const u32 size = regs[1]; + logSVC("svcInvalidateInstructionCacheRange(start = %08X, size = %08X)\n", start, size); + + clearInstructionCacheRange(start, size); + regs[0] = Result::Success; +} + +void Kernel::svcInvalidateEntireInstructionCache() { + logSVC("svcInvalidateEntireInstructionCache()\n"); + + clearInstructionCache(); + regs[0] = Result::Success; +} namespace SystemInfoType { enum : u32 { diff --git a/src/core/kernel/memory_management.cpp b/src/core/kernel/memory_management.cpp index 26f50023..18038d2e 100644 --- a/src/core/kernel/memory_management.cpp +++ b/src/core/kernel/memory_management.cpp @@ -122,7 +122,10 @@ void Kernel::mapMemoryBlock() { } if (KernelHandles::isSharedMemHandle(block)) { - if (block == KernelHandles::FontSharedMemHandle && addr == 0) addr = 0x18000000; + if (block == KernelHandles::FontSharedMemHandle && addr == 0) { + addr = getSharedFontVaddr(); + } + u8* ptr = mem.mapSharedMemory(block, addr, myPerms, otherPerms); // Map shared memory block // Pass pointer to shared memory to the appropriate service @@ -216,3 +219,8 @@ void Kernel::unmapMemoryBlock() { Helpers::warn("Stubbed svcUnmapMemoryBlock!"); regs[0] = Result::Success; } + +u32 Kernel::getSharedFontVaddr() { + // Place shared font at the very beginning of system FCRAM + return mem.getLinearHeapVaddr() + Memory::FCRAM_APPLICATION_SIZE; +} \ No newline at end of file diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index c1899655..2d54e586 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -792,6 +792,8 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 shutUpCounter++; printf("RendererGL::TextureCopy failed to locate src framebuffer!\n"); } + + doSoftwareTextureCopy(inputAddr, outputAddr, copySize, inputWidth, inputGap, outputWidth, outputGap); return; } diff --git a/src/core/renderer_mtl/renderer_mtl.cpp b/src/core/renderer_mtl/renderer_mtl.cpp index 9cf58716..14bca4d2 100644 --- a/src/core/renderer_mtl/renderer_mtl.cpp +++ b/src/core/renderer_mtl/renderer_mtl.cpp @@ -426,7 +426,7 @@ void RendererMTL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 // Find the source surface. auto srcFramebuffer = getColorRenderTarget(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false); if (!srcFramebuffer) { - Helpers::warn("RendererMTL::TextureCopy failed to locate src framebuffer!\n"); + doSoftwareTextureCopy(inputAddr, outputAddr, copySize, inputWidth, inputGap, outputWidth, outputGap); return; } nextRenderPassName = "Clear before texture copy"; diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index d05a070f..57533bde 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -885,10 +885,17 @@ void RendererVK::display() { } } +// DynamicLoader is in a different namespace in different versions of Vulkan-Hpp +#if VK_HEADER_VERSION >= 301 +using VulkanDynamicLoader = vk::detail::DynamicLoader; +#else +using VulkanDynamicLoader = vk::DynamicLoader; +#endif + void RendererVK::initGraphicsContext(SDL_Window* window) { targetWindow = window; // Resolve all instance function pointers - static vk::DynamicLoader dl; + static VulkanDynamicLoader dl; VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress("vkGetInstanceProcAddr")); // Create Instance @@ -1588,4 +1595,4 @@ void RendererVK::deinitGraphicsContext() { // TODO: Make it so that depth and colour buffers get written back to 3DS memory printf("RendererVK::DeinitGraphicsContext called\n"); -} \ No newline at end of file +} diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index ddeb18de..b1e1d460 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -391,7 +391,7 @@ void APTService::setScreencapPostPermission(u32 messagePointer) { void APTService::getSharedFont(u32 messagePointer) { log("APT::GetSharedFont\n"); - constexpr u32 fontVaddr = 0x18000000; + const u32 fontVaddr = kernel.getSharedFontVaddr(); mem.write32(messagePointer, IPC::responseHeader(0x44, 2, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, fontVaddr); diff --git a/src/core/services/ldr_ro.cpp b/src/core/services/ldr_ro.cpp index a6114729..48621986 100644 --- a/src/core/services/ldr_ro.cpp +++ b/src/core/services/ldr_ro.cpp @@ -22,6 +22,7 @@ namespace CROHeader { NameOffset = 0x084, NextCRO = 0x088, PrevCRO = 0x08C, + FixedSize = 0x98, OnUnresolved = 0x0AC, CodeOffset = 0x0B0, DataOffset = 0x0B8, @@ -167,6 +168,10 @@ public: return mem.read32(croPointer + CROHeader::PrevCRO); } + u32 getFixedSize() { + return mem.read32(croPointer + CROHeader::FixedSize); + } + void setNextCRO(u32 nextCRO) { mem.write32(croPointer + CROHeader::NextCRO, nextCRO); } @@ -1248,8 +1253,7 @@ void LDRService::initialize(u32 messagePointer) { Helpers::panic("Failed to rebase CRS"); } - kernel.clearInstructionCache(); - + kernel.clearInstructionCacheRange(mapVaddr, size); loadedCRS = mapVaddr; mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0)); @@ -1278,8 +1282,6 @@ void LDRService::linkCRO(u32 messagePointer) { Helpers::panic("Failed to link CRO"); } - kernel.clearInstructionCache(); - mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -1346,8 +1348,7 @@ void LDRService::loadCRO(u32 messagePointer, bool isNew) { // TODO: add fixing cro.fix(fixLevel); - - kernel.clearInstructionCache(); + kernel.clearInstructionCacheRange(mapVaddr, size); if (isNew) { mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0)); @@ -1377,7 +1378,6 @@ void LDRService::unloadCRO(u32 messagePointer) { } CRO cro(mem, mapVaddr, true); - cro.unregisterCRO(loadedCRS); if (!cro.unlink(loadedCRS)) { @@ -1388,8 +1388,7 @@ void LDRService::unloadCRO(u32 messagePointer) { Helpers::panic("Failed to unrebase CRO"); } - kernel.clearInstructionCache(); - + kernel.clearInstructionCacheRange(mapVaddr, cro.getFixedSize()); mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index 727da8d2..a9783320 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -389,6 +389,8 @@ void retro_run() { emulator->runFrame(); videoCallback(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0); + // Call audio batch callback + emulator->getAudioDevice().renderBatch(audioBatchCallback); } void retro_set_controller_port_device(uint port, uint device) {} diff --git a/src/renderer.cpp b/src/renderer.cpp index 6a18df85..390a3fa6 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -3,6 +3,8 @@ #include #include +#include "PICA/gpu.hpp" + Renderer::Renderer(GPU& gpu, const std::array& internalRegs, const std::array& externalRegs) : gpu(gpu), regs(internalRegs), externalRegs(externalRegs) {} Renderer::~Renderer() {} @@ -39,3 +41,39 @@ const char* Renderer::typeToString(RendererType rendererType) { default: return "Invalid"; } } + +void Renderer::doSoftwareTextureCopy(u32 inputAddr, u32 outputAddr, u32 copySize, u32 inputWidth, u32 inputGap, u32 outputWidth, u32 outputGap) { + u8* inputPointer = gpu.getPointerPhys(inputAddr); + u8* outputPointer = gpu.getPointerPhys(outputAddr); + + if (inputPointer == nullptr || outputPointer == nullptr) { + return; + } + + u32 inputBytesLeft = inputWidth; + u32 outputBytesLeft = outputWidth; + u32 copyBytesLeft = copySize; + + while (copyBytesLeft > 0) { + const u32 bytes = std::min({inputBytesLeft, outputBytesLeft, copyBytesLeft}); + std::memcpy(outputPointer, inputPointer, bytes); + + inputPointer += bytes; + outputPointer += bytes; + + inputBytesLeft -= bytes; + outputBytesLeft -= bytes; + copyBytesLeft -= bytes; + + // Apply input and output gap when an input or output line ends + if (inputBytesLeft == 0) { + inputBytesLeft = inputWidth; + inputPointer += inputGap; + } + + if (outputBytesLeft == 0) { + outputBytesLeft = outputWidth; + outputPointer += outputGap; + } + } +}