From 2831ddf805cd53983921daccad897920361dbc4b Mon Sep 17 00:00:00 2001 From: Eric Warmenhoven Date: Wed, 4 Feb 2026 23:37:31 -0500 Subject: [PATCH] libretro core: address review feedback --- .github/workflows/libretro.yml | 6 +- .gitlab-ci.yml | 20 +- .gitmodules | 2 +- CMakeLists.txt | 44 ++++- externals/CMakeLists.txt | 16 +- externals/libretro-common/CMakeLists.txt | 16 ++ .../{ => libretro-common}/libretro-common | 0 src/audio_core/dsp_interface.cpp | 14 +- src/audio_core/libretro_sink.cpp | 41 +--- src/audio_core/libretro_sink.h | 24 +-- src/audio_core/sink.h | 20 +- src/citra_libretro/CMakeLists.txt | 27 ++- src/citra_libretro/citra_libretro.cpp | 186 +++++++++++++++--- src/citra_libretro/core_settings.cpp | 2 +- src/citra_libretro/core_settings.h | 2 +- .../emu_window/libretro_window.cpp | 3 +- .../emu_window/libretro_window.h | 3 + src/citra_libretro/environment.cpp | 23 ++- src/citra_libretro/environment.h | 7 +- src/citra_libretro/input/mouse_tracker.cpp | 21 +- src/citra_libretro/libretro_vk.cpp | 2 +- src/common/error.cpp | 4 + src/common/logging/backend.cpp | 4 +- src/core/core.h | 3 +- src/core/hle/service/soc/soc_u.cpp | 4 +- src/core/savestate.cpp | 2 - src/tests/CMakeLists.txt | 4 + src/video_core/gpu.cpp | 22 ++- .../renderer_opengl/gl_shader_disk_cache.cpp | 10 + .../renderer_vulkan/vk_instance.cpp | 21 +- src/video_core/renderer_vulkan/vk_instance.h | 2 +- .../renderer_vulkan/vk_resource_pool.cpp | 6 + .../renderer_vulkan/vk_texture_runtime.cpp | 2 +- 33 files changed, 399 insertions(+), 164 deletions(-) create mode 100644 externals/libretro-common/CMakeLists.txt rename externals/{ => libretro-common}/libretro-common (100%) diff --git a/.github/workflows/libretro.yml b/.github/workflows/libretro.yml index d9c2cf972..22c800839 100644 --- a/.github/workflows/libretro.yml +++ b/.github/workflows/libretro.yml @@ -9,10 +9,11 @@ on: workflow_dispatch: env: - CORE_ARGS: -DENABLE_LIBRETRO=ON -DENABLE_SDL2=OFF -DENABLE_QT=OFF -DENABLE_TESTS=OFF -DENABLE_ROOM=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_SCRIPTING=OFF -DENABLE_CUBEB=OFF -DENABLE_OPENAL=OFF -DENABLE_LIBUSB=OFF -DCITRA_WARNINGS_AS_ERRORS=OFF + CORE_ARGS: -DENABLE_LIBRETRO=ON jobs: android: + if: github.event_name != 'pull_request' runs-on: ubuntu-22.04 env: OS: android @@ -68,6 +69,7 @@ jobs: name: ${{ env.OS }}-${{ env.TARGET }} path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.so windows: + if: github.event_name != 'pull_request' runs-on: ubuntu-latest env: OS: windows @@ -103,6 +105,7 @@ jobs: name: ${{ env.OS }}-${{ env.TARGET }} path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.dll macos: + if: github.event_name != 'pull_request' runs-on: macos-14 strategy: matrix: @@ -150,6 +153,7 @@ jobs: name: ${{ env.OS }}-${{ env.TARGET }} path: ${{ env.BUILD_DIR }}/${{ env.EXTRA_PATH }}/azahar_libretro.dylib tvos: + if: github.event_name != 'pull_request' runs-on: macos-14 env: OS: tvos diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 610464dcf..7f1bd9580 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ JNI_PATH: . CORENAME: azahar API_LEVEL: 21 - BASE_CORE_ARGS: -DENABLE_LIBRETRO=ON -DENABLE_SDL2=OFF -DENABLE_QT=OFF -DENABLE_TESTS=OFF -DENABLE_ROOM=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_SCRIPTING=OFF -DENABLE_CUBEB=OFF -DENABLE_OPENAL=OFF -DENABLE_LIBUSB=OFF -DCITRA_WARNINGS_AS_ERRORS=OFF + BASE_CORE_ARGS: -DENABLE_LIBRETRO=ON -DENABLE_TESTS=OFF CORE_ARGS: ${BASE_CORE_ARGS} EXTRA_PATH: bin/Release @@ -22,11 +22,11 @@ include: - project: 'libretro-infrastructure/ci-templates' file: '/linux-cmake.yml' - # MacOS 64-bit + # MacOS x86_64 - project: 'libretro-infrastructure/ci-templates' file: '/osx-cmake-x86.yml' - # MacOS arm64 + # MacOS ARM64 - project: 'libretro-infrastructure/ci-templates' file: '/osx-cmake-arm64.yml' @@ -61,17 +61,9 @@ libretro-build-windows-x64: extends: - .core-defs - .libretro-windows-cmake-x86_64 + image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12 variables: - EXTRA_PATH: bin/Release CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF -G Ninja - before_script: - - export NUMPROC=$(($(nproc)/5)) - - sudo apt-get update -qy - - sudo apt-get install -qy software-properties-common - - sudo add-apt-repository -y ppa:savoury1/build-tools - - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - - sudo apt-get update -qy - - sudo apt-get install -qy glslang-tools # Linux 64-bit libretro-build-linux-x64: @@ -82,7 +74,7 @@ libretro-build-linux-x64: variables: CORE_ARGS: ${BASE_CORE_ARGS} -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DENABLE_OPT=OFF -# MacOS 64-bit +# MacOS x86_64 libretro-build-osx-x64: tags: - mac-apple-silicon @@ -93,7 +85,7 @@ libretro-build-osx-x64: - .core-defs - .libretro-osx-cmake-x86_64 -# MacOS 64-bit +# MacOS ARM64 libretro-build-osx-arm64: extends: - .core-defs diff --git a/.gitmodules b/.gitmodules index 5f2a7e842..18c5cc79a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -104,5 +104,5 @@ path = externals/xxHash url = https://github.com/Cyan4973/xxHash.git [submodule "externals/libretro-common"] - path = externals/libretro-common + path = externals/libretro-common/libretro-common url = https://github.com/libretro/libretro-common.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e65cf0c3b..e4d40c112 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,6 @@ # CMake >=3.12 required for 20 to be a valid value for CXX_STANDARD, # and >=3.25 required to make LTO work on Android. -if(ANDROID) - cmake_minimum_required(VERSION 3.25) -else() - cmake_minimum_required(VERSION 3.23) -endif() +cmake_minimum_required(VERSION 3.25) # Don't override the warning flags in MSVC: cmake_policy(SET CMP0092 NEW) @@ -29,7 +25,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS") enable_language(OBJC OBJCXX) endif() -option(ENABLE_LIBRETRO "Enable the LibRetro frontend" OFF) +option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF) # Some submodules like to pick their own default build type if not specified. # Make sure we default to Release build type always, unless the generator has custom types. @@ -97,6 +93,17 @@ else() set(DEFAULT_ENABLE_OPENGL ON) endif() +# Track which options were explicitly set by the user (for libretro conflict detection) +set(_LIBRETRO_INCOMPATIBLE_OPTIONS + ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING + ENABLE_OPENAL ENABLE_ROOM ENABLE_CUBEB ENABLE_LIBUSB) +set(_USER_SET_OPTIONS "") +foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS) + if(DEFINED ${_opt}) + list(APPEND _USER_SET_OPTIONS ${_opt}) + endif() +endforeach() + option(ENABLE_SDL2 "Enable using SDL2" ON) CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" OFF "ENABLE_SDL2;NOT ANDROID AND NOT IOS" OFF) option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF) @@ -137,6 +144,31 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON) +# Handle incompatible options for libretro builds +if(ENABLE_LIBRETRO) + # Check for explicitly-set conflicting options + set(_CONFLICTS "") + foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS) + list(FIND _USER_SET_OPTIONS ${_opt} _idx) + if(NOT _idx EQUAL -1 AND ${_opt}) + list(APPEND _CONFLICTS ${_opt}) + endif() + endforeach() + + if(_CONFLICTS) + string(REPLACE ";" ", " _CONFLICTS_STR "${_CONFLICTS}") + message(FATAL_ERROR + "ENABLE_LIBRETRO is incompatible with: ${_CONFLICTS_STR}\n" + "These options were explicitly enabled but are not supported for libretro builds.\n" + "Remove these options or set them to OFF.") + endif() + + # Force disable incompatible options (handles defaulted-on options) + foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS) + set(${_opt} OFF CACHE BOOL "Disabled for libretro" FORCE) + endforeach() +endif() + # Pass the following values to C++ land if (ENABLE_QT) add_definitions(-DENABLE_QT) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index bc9006023..d7cf35f3b 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -295,21 +295,9 @@ endif() # LibRetro if (ENABLE_LIBRETRO) add_library(libretro INTERFACE) - target_include_directories(libretro INTERFACE ./libretro-common/include) + target_include_directories(libretro INTERFACE ./libretro-common/libretro-common/include) if (ANDROID) - add_library(libretro_common STATIC - libretro-common/compat/compat_posix_string.c - libretro-common/compat/fopen_utf8.c - libretro-common/encodings/encoding_utf.c - libretro-common/compat/compat_strl.c - libretro-common/file/file_path.c - libretro-common/streams/file_stream.c - libretro-common/streams/file_stream_transforms.c - libretro-common/string/stdstring.c - libretro-common/time/rtime.c - libretro-common/vfs/vfs_implementation.c - ) - target_include_directories(libretro_common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libretro-common ${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/include) + add_subdirectory(libretro-common EXCLUDE_FROM_ALL) endif() endif() diff --git a/externals/libretro-common/CMakeLists.txt b/externals/libretro-common/CMakeLists.txt new file mode 100644 index 000000000..0bb4e6b5e --- /dev/null +++ b/externals/libretro-common/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libretro_common STATIC + libretro-common/compat/compat_posix_string.c + libretro-common/compat/fopen_utf8.c + libretro-common/encodings/encoding_utf.c + libretro-common/compat/compat_strl.c + libretro-common/file/file_path.c + libretro-common/streams/file_stream.c + libretro-common/streams/file_stream_transforms.c + libretro-common/string/stdstring.c + libretro-common/time/rtime.c + libretro-common/vfs/vfs_implementation.c +) +target_include_directories(libretro_common PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/libretro-common + ${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/include +) diff --git a/externals/libretro-common b/externals/libretro-common/libretro-common similarity index 100% rename from externals/libretro-common rename to externals/libretro-common/libretro-common diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp index 83065ea9d..52e781ca4 100644 --- a/src/audio_core/dsp_interface.cpp +++ b/src/audio_core/dsp_interface.cpp @@ -41,9 +41,11 @@ void DspInterface::OutputFrame(StereoFrame16 frame) { return; } - fifo.Push(frame.data(), frame.size()); - - GetSink().OnAudioSubmission(frame.size()); + if (sink->ImmediateSubmission()) { + sink->PushSamples(frame.data(), frame.size()); + } else { + fifo.Push(frame.data(), frame.size()); + } auto video_dumper = system.GetVideoDumper(); if (video_dumper && video_dumper->IsDumping()) { @@ -56,7 +58,11 @@ void DspInterface::OutputSample(std::array sample) { return; } - fifo.Push(&sample, 1); + if (sink->ImmediateSubmission()) { + sink->PushSamples(&sample, 1); + } else { + fifo.Push(&sample, 1); + } auto video_dumper = system.GetVideoDumper(); if (video_dumper && video_dumper->IsDumping()) { diff --git a/src/audio_core/libretro_sink.cpp b/src/audio_core/libretro_sink.cpp index 95b08cba6..d3bcf0ca5 100644 --- a/src/audio_core/libretro_sink.cpp +++ b/src/audio_core/libretro_sink.cpp @@ -2,41 +2,22 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include -#include #include "audio_core/libretro_sink.h" -#include "audio_types.h" -#include "common/settings.h" - -namespace LibRetro { -static retro_audio_sample_batch_t audio_batch_cb; -} +#include "citra_libretro/environment.h" namespace AudioCore { -struct LibRetroSink::Impl { - std::function cb; -}; +LibRetroSink::LibRetroSink(std::string) {} -LibRetroSink::LibRetroSink(std::string target_device_name) : impl(std::make_unique()) {} - -LibRetroSink::~LibRetroSink() {} +LibRetroSink::~LibRetroSink() = default; unsigned int LibRetroSink::GetNativeSampleRate() const { - return native_sample_rate; // We specify this. + return native_sample_rate; } -void LibRetroSink::SetCallback(std::function cb) { - this->impl->cb = cb; -} - -void LibRetroSink::OnAudioSubmission(std::size_t frames) { - std::vector buffer(frames * 2); - - this->impl->cb(buffer.data(), buffer.size() / 2); - - LibRetro::SubmitAudio(buffer.data(), buffer.size() / 2); +void LibRetroSink::PushSamples(const void* data, std::size_t num_samples) { + // libretro calls stereo pairs "frames", Azahar calls them "samples" + LibRetro::SubmitAudio(static_cast(data), num_samples); } std::vector ListLibretroSinkDevices() { @@ -44,11 +25,3 @@ std::vector ListLibretroSinkDevices() { } } // namespace AudioCore - -void LibRetro::SubmitAudio(const int16_t* data, size_t frames) { - LibRetro::audio_batch_cb(data, frames); -} - -void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { - LibRetro::audio_batch_cb = cb; -} diff --git a/src/audio_core/libretro_sink.h b/src/audio_core/libretro_sink.h index 320d00794..ac7b3f9e9 100644 --- a/src/audio_core/libretro_sink.h +++ b/src/audio_core/libretro_sink.h @@ -5,15 +5,9 @@ #pragma once #include -#include +#include +#include #include "audio_core/sink.h" -#include "libretro.h" - -namespace LibRetro { - -void SubmitAudio(const int16_t* data, size_t frames); - -} // namespace LibRetro namespace AudioCore { @@ -24,20 +18,14 @@ public: unsigned int GetNativeSampleRate() const override; - void SetCallback(std::function cb) override; + // Not used for immediate submission sinks + void SetCallback(std::function cb) override {}; - void OnAudioSubmission(std::size_t frames) override; + bool ImmediateSubmission() override { return true; } - struct Impl; - -private: - std::unique_ptr impl; + void PushSamples(const void* data, std::size_t num_samples) override; }; -void audio_callback(); - -void audio_set_state(bool new_state); - std::vector ListLibretroSinkDevices(); } // namespace AudioCore diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 5a428c1c1..d0d0ad02a 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -5,7 +5,7 @@ #pragma once #include -#include "common/common_types.h" +#include "audio_types.h" namespace AudioCore { @@ -31,8 +31,22 @@ public: */ virtual void SetCallback(std::function cb) = 0; - /// Optional callback to signify that a buffer has been written. - virtual void OnAudioSubmission(std::size_t frames) {} + /** + * Override and set this to true if the sink wants audio data submitted + * immediately rather than requesting audio on demand + * @return true if audio data should be pushed to the sink + */ + virtual bool ImmediateSubmission() { + return false; + } + + /** + * Push audio samples directly to the sink, bypassing the FIFO. + * Only called when ImmediateSubmission() returns true. + * @param data Pointer to stereo PCM16 samples (each sample is L+R pair) + * @param num_samples Number of stereo samples + */ + virtual void PushSamples(const void* data, std::size_t num_samples) {} }; } // namespace AudioCore diff --git a/src/citra_libretro/CMakeLists.txt b/src/citra_libretro/CMakeLists.txt index 07c44e53a..8ea9b7b10 100644 --- a/src/citra_libretro/CMakeLists.txt +++ b/src/citra_libretro/CMakeLists.txt @@ -1,21 +1,34 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) -add_library(azahar_libretro SHARED +# Object library for libretro code (can be linked into both shared lib and tests) +add_library(azahar_libretro_common OBJECT emu_window/libretro_window.cpp emu_window/libretro_window.h input/input_factory.cpp input/input_factory.h input/mouse_tracker.cpp input/mouse_tracker.h - citra_libretro.cpp - citra_libretro.h $<$: libretro_vk.cpp libretro_vk.h> environment.cpp environment.h core_settings.cpp core_settings.h) +target_compile_definitions(azahar_libretro_common PRIVATE HAVE_LIBRETRO) +target_link_libraries(azahar_libretro_common PRIVATE citra_common citra_core video_core libretro robin_map) +if(ENABLE_OPENGL) + target_link_libraries(azahar_libretro_common PRIVATE glad) +endif() +if(ENABLE_VULKAN) + target_link_libraries(azahar_libretro_common PRIVATE sirit vulkan-headers vma) +endif() + +add_library(azahar_libretro SHARED + citra_libretro.cpp + citra_libretro.h + $) + create_target_directory_groups(azahar_libretro) target_link_libraries(citra_common PRIVATE libretro) @@ -49,10 +62,12 @@ if(ANDROID) target_compile_definitions(citra_common PRIVATE HAVE_LIBRETRO_VFS) target_compile_definitions(citra_core PRIVATE HAVE_LIBRETRO_VFS) target_compile_definitions(video_core PRIVATE HAVE_LIBRETRO_VFS) + target_compile_definitions(azahar_libretro_common PRIVATE USING_GLES HAVE_LIBRETRO_VFS) target_compile_definitions(azahar_libretro PRIVATE USING_GLES HAVE_LIBRETRO_VFS) target_link_libraries(citra_common PRIVATE libretro_common) target_link_libraries(citra_core PRIVATE libretro_common) target_link_libraries(video_core PRIVATE libretro_common) + target_link_libraries(azahar_libretro_common PRIVATE libretro_common) target_link_libraries(azahar_libretro PRIVATE libretro_common) # Link Android log library for __android_log_print target_link_libraries(azahar_libretro PRIVATE log) @@ -64,9 +79,15 @@ if(MINGW) endif() if(IOS) + target_compile_definitions(azahar_libretro_common PRIVATE IOS) + target_compile_definitions(azahar_libretro PRIVATE IOS) target_link_libraries(azahar_libretro PRIVATE "-framework CoreFoundation" "-framework Foundation") endif() +if (SSE42_COMPILE_OPTION) + target_compile_definitions(azahar_libretro PRIVATE CITRA_HAS_SSE42) +endif() + if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "tvOS") diff --git a/src/citra_libretro/citra_libretro.cpp b/src/citra_libretro/citra_libretro.cpp index 2883ac2b0..e416e9704 100644 --- a/src/citra_libretro/citra_libretro.cpp +++ b/src/citra_libretro/citra_libretro.cpp @@ -32,6 +32,10 @@ #include "citra_libretro/environment.h" #include "citra_libretro/input/input_factory.h" +#include "common/arch.h" +#if CITRA_ARCH(x86_64) +#include "common/x64/cpu_detect.h" +#endif #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/settings.h" @@ -39,7 +43,9 @@ #include "core/core.h" #include "core/frontend/applets/default_applets.h" #include "core/frontend/image_interface.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" #include "core/loader/loader.h" #include "core/memory.h" @@ -235,6 +241,8 @@ void retro_run() { // Check to see if we actually have any config updates to process. if (LibRetro::HasUpdatedConfig()) { LibRetro::ParseCoreOptions(); + Core::System::GetInstance().ApplySettings(); + emu_instance->emu_window->UpdateLayout(); } // Check if the screen swap button is pressed @@ -300,6 +308,68 @@ void retro_run() { } } +static void setup_memory_maps() { + auto process = Core::System::GetInstance().Kernel().GetCurrentProcess(); + if (!process) + return; + + std::vector descs; + + for (const auto& [addr, vma] : process->vm_manager.vma_map) { + if (vma.type != Kernel::VMAType::BackingMemory) + continue; + if (vma.size == 0 || !vma.backing_memory) + continue; + + // Only expose the well-known user-accessible memory regions + uint64_t flags = 0; + if (vma.base >= Memory::HEAP_VADDR && vma.base < Memory::HEAP_VADDR_END) { + flags = RETRO_MEMDESC_SYSTEM_RAM; + } else if (vma.base >= Memory::LINEAR_HEAP_VADDR && + vma.base < Memory::LINEAR_HEAP_VADDR_END) { + flags = RETRO_MEMDESC_SYSTEM_RAM; + } else if (vma.base >= Memory::NEW_LINEAR_HEAP_VADDR && + vma.base < Memory::NEW_LINEAR_HEAP_VADDR_END) { + flags = RETRO_MEMDESC_SYSTEM_RAM; + } else if (vma.base >= Memory::VRAM_VADDR && vma.base < Memory::VRAM_VADDR_END) { + flags = RETRO_MEMDESC_VIDEO_RAM; + } else { + continue; + } + + retro_memory_descriptor desc = {}; + desc.flags = flags; + desc.ptr = const_cast(vma.backing_memory.GetPtr()); + desc.start = vma.base; + desc.len = vma.size; + + // select=0 requires power-of-2 len AND start aligned to len. + // When that doesn't hold, compute a select mask instead. + bool need_select = (vma.size & (vma.size - 1)) != 0; + if (!need_select && (vma.base & (vma.size - 1)) != 0) + need_select = true; + + if (need_select) { + uint64_t np2 = 1; + while (np2 < vma.size) + np2 <<= 1; + if (vma.base & (np2 - 1)) { + LOG_WARNING(Frontend, "VMA at 0x{:08X} size 0x{:X} not aligned, skipping", vma.base, + vma.size); + continue; + } + desc.select = ~(np2 - 1); + } + + descs.push_back(desc); + } + + if (!descs.empty()) { + retro_memory_map map = {descs.data(), static_cast(descs.size())}; + LibRetro::SetMemoryMaps(&map); + } +} + static bool do_load_game() { const Core::System::ResultStatus load_result{ Core::System::GetInstance().Load(*emu_instance->emu_window, LibRetro::settings.file_path)}; @@ -331,7 +401,8 @@ static bool do_load_game() { LibRetro::DisplayMessage("Failed to determine system mode!"); return false; default: - LibRetro::DisplayMessage("Unknown error"); + LibRetro::DisplayMessage( + ("Unknown error: " + std::to_string(static_cast(load_result))).c_str()); return false; } @@ -344,6 +415,8 @@ static bool do_load_game() { false, nullptr); } + setup_memory_maps(); + return true; } @@ -405,10 +478,6 @@ static void context_reset() { // Game is already loaded, just recreate the renderer for the new GL context if (Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::OpenGL) { Core::System::GetInstance().GPU().RecreateRenderer(*emu_instance->emu_window, nullptr); - if (Settings::values.use_disk_shader_cache) { - Core::System::GetInstance().GPU().Renderer().Rasterizer()->LoadDefaultDiskResources( - false, nullptr); - } } } } @@ -426,10 +495,7 @@ static void context_destroy() { void retro_reset() { LOG_DEBUG(Frontend, "retro_reset"); Core::System::GetInstance().Shutdown(); - if (Core::System::GetInstance().Load(*emu_instance->emu_window, LibRetro::settings.file_path) != - Core::System::ResultStatus::Success) { - LOG_ERROR(Frontend, "Unable lo load on retro_reset"); - } + emu_instance->game_loaded = do_load_game(); } /** @@ -438,6 +504,14 @@ void retro_reset() { bool retro_load_game(const struct retro_game_info* info) { LOG_INFO(Frontend, "Starting Azahar RetroArch game..."); +#if CITRA_ARCH(x86_64) && CITRA_HAS_SSE42 + if (!Common::GetCPUCaps().sse4_2) { + LOG_CRITICAL(Frontend, "This CPU does not support SSE4.2, which is required by this build"); + LibRetro::DisplayMessage("This build requires a CPU with SSE4.2 support."); + return false; + } +#endif + UpdateSettings(); // If using HW rendering, don't actually load the game here. azahar wants @@ -502,9 +576,15 @@ bool retro_load_game(const struct retro_game_info* info) { break; case Settings::GraphicsAPI::Software: emu_instance->game_loaded = do_load_game(); - return emu_instance->game_loaded; + if (!emu_instance->game_loaded) + return false; + break; } + uint64_t quirks = + RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE | RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE; + LibRetro::SetSerializationQuirks(quirks); + return true; } @@ -522,14 +602,64 @@ bool retro_load_game_special(unsigned game_type, const struct retro_game_info* i return retro_load_game(info); } +/// Drain any pending async kernel operations by running the emulation loop. +/// +/// Savestates are unsafe to create while RunAsync operations (file I/O, network, etc.) +/// are in flight. The Qt frontend handles this by deferring serialization inside +/// System::RunLoop(): it sets a request flag via SendSignal(Signal::Save), and RunLoop +/// only performs the save when !kernel->AreAsyncOperationsPending() (see core.cpp). +/// +/// The Qt frontend needs that indirection because its UI and emulation run on separate +/// threads. In libretro, the frontend calls API entry points (retro_run, retro_serialize, +/// etc.) sequentially, so we can call RunLoop() directly from here to drain pending ops, +/// then call SaveStateBuffer()/LoadStateBuffer() ourselves. +/// +/// Note: RunLoop() can itself start new async operations (CPU executes HLE service calls), +/// so the pending count may not decrease monotonically. In practice games reach quiescent +/// points between frames; the 5-second timeout (matching RunLoop's existing handler) +/// covers the pathological case. +static bool DrainAsyncOperations(Core::System& system) { + if (!system.KernelRunning() || !system.Kernel().AreAsyncOperationsPending()) { + return true; + } + + emu_instance->emu_window->suppressPresentation = true; + auto start = std::chrono::steady_clock::now(); + + while (system.Kernel().AreAsyncOperationsPending()) { + if (std::chrono::steady_clock::now() - start > std::chrono::seconds(5)) { + LOG_ERROR(Frontend, "Timed out waiting for async operations to complete"); + emu_instance->emu_window->suppressPresentation = false; + return false; + } + auto result = system.RunLoop(); + if (result != Core::System::ResultStatus::Success) { + emu_instance->emu_window->suppressPresentation = false; + return false; + } + } + + emu_instance->emu_window->suppressPresentation = false; + return true; +} + std::optional> savestate = {}; size_t retro_serialize_size() { + auto& system = Core::System::GetInstance(); + if (!system.IsPoweredOn()) + return 0; + + if (!DrainAsyncOperations(system)) { + savestate.reset(); + return 0; + } + try { - savestate = Core::System::GetInstance().SaveStateBuffer(); - return savestate.value().size(); + savestate = system.SaveStateBuffer(); + return savestate->size(); } catch (const std::exception& e) { - LOG_ERROR(Core, "Error saving savestate: {}", e.what()); + LOG_ERROR(Frontend, "Error saving state: {}", e.what()); savestate.reset(); return 0; } @@ -538,36 +668,38 @@ size_t retro_serialize_size() { bool retro_serialize(void* data, size_t size) { if (!savestate.has_value()) return false; - - memcpy(data, (*savestate).data(), size); + if (size < savestate->size()) + return false; + memcpy(data, savestate->data(), savestate->size()); savestate.reset(); - return true; } bool retro_unserialize(const void* data, size_t size) { - try { - const std::vector buffer((const u8*)data, (const u8*)data + size); + auto& system = Core::System::GetInstance(); + if (!system.IsPoweredOn()) + return false; - return Core::System::GetInstance().LoadStateBuffer(buffer); + if (!DrainAsyncOperations(system)) { + return false; + } + + std::vector buffer(static_cast(data), static_cast(data) + size); + try { + return system.LoadStateBuffer(std::move(buffer)); } catch (const std::exception& e) { - LOG_ERROR(Core, "Error loading savestate: {}", e.what()); + LOG_ERROR(Frontend, "Error loading state: {}", e.what()); return false; } } void* retro_get_memory_data(unsigned id) { - if (id == RETRO_MEMORY_SYSTEM_RAM) - return Core::System::GetInstance().Memory().GetFCRAMPointer( - Core::System::GetInstance().Kernel().memory_regions[0]->base); - + // Memory is exposed via RETRO_ENVIRONMENT_SET_MEMORY_MAPS instead, + // using virtual addresses for stable cheat/achievement support. return NULL; } size_t retro_get_memory_size(unsigned id) { - if (id == RETRO_MEMORY_SYSTEM_RAM) - return Core::System::GetInstance().Kernel().memory_regions[0]->size; - return 0; } diff --git a/src/citra_libretro/core_settings.cpp b/src/citra_libretro/core_settings.cpp index fc9e31b09..f31191acd 100644 --- a/src/citra_libretro/core_settings.cpp +++ b/src/citra_libretro/core_settings.cpp @@ -994,7 +994,7 @@ static void ParseInputOptions(void) { void ParseCoreOptions(void) { // Override default values that aren't user-selectable and aren't correct for the core Settings::values.enable_audio_stretching = false; - Settings::values.frame_limit = 10000; + Settings::values.frame_limit = 0; #if defined(USING_GLES) Settings::values.use_gles = true; #else diff --git a/src/citra_libretro/core_settings.h b/src/citra_libretro/core_settings.h index 3f2d58016..78bcff236 100644 --- a/src/citra_libretro/core_settings.h +++ b/src/citra_libretro/core_settings.h @@ -13,7 +13,7 @@ enum CStickFunction { Both, CStick, Touchscreen }; struct CoreSettings { - ::std::string file_path; + std::string file_path; float deadzone = 1.f; diff --git a/src/citra_libretro/emu_window/libretro_window.cpp b/src/citra_libretro/emu_window/libretro_window.cpp index 61cc1519d..22b75af0e 100644 --- a/src/citra_libretro/emu_window/libretro_window.cpp +++ b/src/citra_libretro/emu_window/libretro_window.cpp @@ -65,6 +65,8 @@ EmuWindow_LibRetro::EmuWindow_LibRetro() { EmuWindow_LibRetro::~EmuWindow_LibRetro() {} void EmuWindow_LibRetro::SwapBuffers() { + if (suppressPresentation) + return; submittedFrame = true; switch (Settings::values.graphics_api.GetValue()) { @@ -77,7 +79,6 @@ void EmuWindow_LibRetro::SwapBuffers() { } LibRetro::UploadVideoFrame(RETRO_HW_FRAME_BUFFER_VALID, static_cast(width), static_cast(height), 0); - ResetGLState(); current_state.Apply(); #endif break; diff --git a/src/citra_libretro/emu_window/libretro_window.h b/src/citra_libretro/emu_window/libretro_window.h index aea3d357c..d724f4b71 100644 --- a/src/citra_libretro/emu_window/libretro_window.h +++ b/src/citra_libretro/emu_window/libretro_window.h @@ -45,6 +45,9 @@ public: /// Destroys a currently running OpenGL context. void DestroyContext(); + /// When true, SwapBuffers() is suppressed (used during savestate drain loops) + bool suppressPresentation = false; + private: /// Called when a configuration change affects the minimal size of the window void OnMinimalClientAreaChangeRequest(std::pair minimal_size) override; diff --git a/src/citra_libretro/environment.cpp b/src/citra_libretro/environment.cpp index c83eb1aee..38b9afeb8 100644 --- a/src/citra_libretro/environment.cpp +++ b/src/citra_libretro/environment.cpp @@ -20,7 +20,7 @@ namespace LibRetro { namespace { static retro_video_refresh_t video_cb; -// static retro_audio_sample_t audio_cb; +static retro_audio_sample_batch_t audio_batch_cb; static retro_environment_t environ_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; @@ -100,6 +100,10 @@ bool GetCoreOptionsVersion(unsigned* version) { return environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, version); } +bool SetMemoryMaps(const retro_memory_map* map) { + return environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, (void*)map); +} + bool SetControllerInfo(const retro_controller_info info[]) { return environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)info); } @@ -148,13 +152,16 @@ bool Shutdown() { /// Displays the specified message to the screen. bool DisplayMessage(const char* sg) { - LOG_CRITICAL(Frontend, "{}", sg); retro_message msg; msg.msg = sg; msg.frames = 60 * 10; return environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } +bool SetSerializationQuirks(uint64_t quirks) { + return environ_cb(RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS, &quirks); +} + std::string FetchVariable(std::string key, std::string def) { struct retro_variable var = {nullptr}; var.key = key.c_str(); @@ -217,15 +224,23 @@ bool CanUseJIT() { void retro_get_system_info(struct retro_system_info* info) { memset(info, 0, sizeof(*info)); info->library_name = "Azahar"; - info->library_version = Common::g_scm_desc; + info->library_version = Common::g_build_fullname; info->need_fullpath = true; - info->valid_extensions = "3ds|3dsx|cia|elf"; + info->valid_extensions = "3ds|3dsx|z3dsx|elf|axf|cci|zcci|cxi|zcxi|app"; +} + +void LibRetro::SubmitAudio(const int16_t* data, size_t frames) { + audio_batch_cb(data, frames); } void retro_set_audio_sample(retro_audio_sample_t cb) { // We don't need single audio sample callbacks. } +void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { + LibRetro::audio_batch_cb = cb; +} + void retro_set_input_poll(retro_input_poll_t cb) { LibRetro::input_poll_cb = cb; } diff --git a/src/citra_libretro/environment.h b/src/citra_libretro/environment.h index e7348f0cd..c93071c0d 100644 --- a/src/citra_libretro/environment.h +++ b/src/citra_libretro/environment.h @@ -5,7 +5,6 @@ #pragma once #include -#include "citra_libretro.h" #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -64,6 +63,9 @@ Settings::GraphicsAPI GetPreferredRenderer(); /// Displays information about the kinds of controllers that this Citra recreates. bool SetControllerInfo(const retro_controller_info info[]); +/// Sets the memory maps for the core. +bool SetMemoryMaps(const retro_memory_map* map); + /// Sets the framebuffer pixel format. bool SetPixelFormat(const retro_pixel_format fmt); @@ -110,6 +112,9 @@ bool Shutdown(); /// Displays the specified message to the screen. bool DisplayMessage(const char* sg); +/// Sets serialization quirks for the core. +bool SetSerializationQuirks(uint64_t quirks); + #ifdef HAVE_LIBRETRO_VFS void SetVFSCallback(struct retro_vfs_interface_info* vfs_iface_info); #endif diff --git a/src/citra_libretro/input/mouse_tracker.cpp b/src/citra_libretro/input/mouse_tracker.cpp index 5bafbefa6..6829aad2d 100644 --- a/src/citra_libretro/input/mouse_tracker.cpp +++ b/src/citra_libretro/input/mouse_tracker.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include @@ -121,8 +122,8 @@ void MouseTracker::OnMouseMove(int deltaX, int deltaY) { } void MouseTracker::Restrict(int minX, int minY, int maxX, int maxY) { - x = std::min(std::max(minX, x), maxX); - y = std::min(std::max(minY, y), maxY); + x = std::clamp(x, minX, maxX); + y = std::clamp(y, minY, maxY); } void MouseTracker::Update(int bufferWidth, int bufferHeight, @@ -146,11 +147,11 @@ void MouseTracker::Update(int bufferWidth, int bufferHeight, // Use layout system to validate and map coordinates if (IsWithinTouchscreen(layout, newX, newY)) { - x = std::max(static_cast(layout.bottom_screen.left), - std::min(newX, static_cast(layout.bottom_screen.right))) - + x = std::clamp(newX, static_cast(layout.bottom_screen.left), + static_cast(layout.bottom_screen.right)) - layout.bottom_screen.left; - y = std::max(static_cast(layout.bottom_screen.top), - std::min(newY, static_cast(layout.bottom_screen.bottom))) - + y = std::clamp(newY, static_cast(layout.bottom_screen.top), + static_cast(layout.bottom_screen.bottom)) - layout.bottom_screen.top; } } @@ -173,11 +174,11 @@ void MouseTracker::Update(int bufferWidth, int bufferHeight, // Use layout system to validate and map coordinates if (IsWithinTouchscreen(layout, newX, newY)) { - x = std::max(static_cast(layout.bottom_screen.left), - std::min(newX, static_cast(layout.bottom_screen.right))) - + x = std::clamp(newX, static_cast(layout.bottom_screen.left), + static_cast(layout.bottom_screen.right)) - layout.bottom_screen.left; - y = std::max(static_cast(layout.bottom_screen.top), - std::min(newY, static_cast(layout.bottom_screen.bottom))) - + y = std::clamp(newY, static_cast(layout.bottom_screen.top), + static_cast(layout.bottom_screen.bottom)) - layout.bottom_screen.top; } } diff --git a/src/citra_libretro/libretro_vk.cpp b/src/citra_libretro/libretro_vk.cpp index 9410fe09d..eed09d5bc 100644 --- a/src/citra_libretro/libretro_vk.cpp +++ b/src/citra_libretro/libretro_vk.cpp @@ -274,7 +274,7 @@ LibRetroVKInstance::LibRetroVKInstance(Frontend::EmuWindow& window, VULKAN_HPP_DEFAULT_DISPATCHER.init(vk::Device{vulkan_intf->device}); // Now run device capability detection with dispatcher initialized - CreateDevice(true); + CreateDevice(); // LibRetro-specific: Validate function pointers are actually available // LibRetro's device may not have loaded all extension functions even if extensions are diff --git a/src/common/error.cpp b/src/common/error.cpp index 5b9246054..def285115 100644 --- a/src/common/error.cpp +++ b/src/common/error.cpp @@ -2,6 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + #include #ifdef _WIN32 #include diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index ef6ff05e5..e1b8621bd 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -491,8 +491,8 @@ private: lambda(static_cast(file_backend)); #ifdef ANDROID lambda(static_cast(lc_backend)); -#endif -#endif +#endif // ANDROID +#endif // HAVE_LIBRETRO } static void Deleter(Impl* ptr) { diff --git a/src/core/core.h b/src/core/core.h index 15a9a9e29..d493e8491 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -362,11 +362,10 @@ public: void LoadState(u32 slot); -#ifdef HAVE_LIBRETRO std::vector SaveStateBuffer() const; bool LoadStateBuffer(std::vector buffer); -#endif + /// Self delete ncch bool SetSelfDelete(const std::string& file) { if (m_filepath == file) { diff --git a/src/core/hle/service/soc/soc_u.cpp b/src/core/hle/service/soc/soc_u.cpp index 9cd745625..d4ae261bf 100644 --- a/src/core/hle/service/soc/soc_u.cpp +++ b/src/core/hle/service/soc/soc_u.cpp @@ -2308,7 +2308,9 @@ std::optional SOC_U::GetDefaultInterfaceInfo() { break; } } -#elif !defined(HAVE_LIBRETRO) +#elif !(defined(ANDROID) && defined(HAVE_LIBRETRO)) + // Libretro Android builds target API 21, but getifaddrs() requires API 24+. + // Standalone Android (minSdk 29) and other platforms have getifaddrs(). struct ifaddrs* ifaddr; struct ifaddrs* ifa; if (getifaddrs(&ifaddr) == -1) { diff --git a/src/core/savestate.cpp b/src/core/savestate.cpp index e3adcd59f..573862d81 100644 --- a/src/core/savestate.cpp +++ b/src/core/savestate.cpp @@ -217,7 +217,6 @@ void System::LoadState(u32 slot) { ia&* this; } -#ifdef HAVE_LIBRETRO std::vector System::SaveStateBuffer() const { std::ostringstream sstream{std::ios_base::binary}; // Serialize @@ -290,6 +289,5 @@ bool System::LoadStateBuffer(std::vector buffer) { return true; } -#endif } // namespace Core diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 4ac9368b1..f248ece4b 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -26,6 +26,10 @@ create_target_directory_groups(tests) target_link_libraries(tests PRIVATE citra_common citra_core video_core audio_core) target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch2 nihstro-headers Threads::Threads) +if (ENABLE_LIBRETRO) + target_link_libraries(tests PRIVATE $) +endif() + add_test(NAME tests COMMAND tests) if (CITRA_USE_PRECOMPILED_HEADERS) diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index c44437e9c..9a76f4859 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -9,6 +9,7 @@ #include "core/core_timing.h" #include "core/hle/service/gsp/gsp_gpu.h" #include "core/hle/service/plgldr/plgldr.h" +#include "core/loader/loader.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" #include "video_core/gpu_debugger.h" @@ -436,10 +437,25 @@ void GPU::RecreateRenderer(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* // Update the sw_blitter with the new rasterizer impl->sw_blitter = std::make_unique(impl->memory, impl->rasterizer); - // Mark ALL GPU registers as dirty so current state gets uploaded to new renderer - impl->pica.dirty_regs.qwords.fill(~0ULL); + // Re-apply per-game configuration and reload disk shader cache + u64 program_id{}; + impl->system.GetAppLoader().ReadProgramId(program_id); + ApplyPerProgramSettings(program_id); + if (Settings::values.use_disk_shader_cache) { + impl->renderer->Rasterizer()->LoadDefaultDiskResources(false, nullptr); + } - // Also mark all cached state in pica as dirty + // Mark ALL GPU registers as dirty so current state gets uploaded to new renderer + impl->pica.dirty_regs.SetAllDirty(); + + // Also mark shader setups as dirty so uniforms get re-uploaded and + // stale pointers to the old rasterizer's JIT cache are cleared. + impl->pica.vs_setup.uniforms_dirty = true; + impl->pica.vs_setup.cached_shader = nullptr; + impl->pica.gs_setup.uniforms_dirty = true; + impl->pica.gs_setup.cached_shader = nullptr; + + // Mark all cached LUT/table state in pica as dirty impl->pica.lighting.lut_dirty = impl->pica.lighting.LutAllDirty; impl->pica.fog.lut_dirty = true; impl->pica.proctex.table_dirty = impl->pica.proctex.TableAllDirty; diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index e9201ef68..6969faa88 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -458,6 +458,8 @@ FileUtil::IOFile ShaderDiskCache::AppendTransferableFile() { const bool existed = FileUtil::Exists(transferable_path); #ifdef HAVE_LIBRETRO_VFS + // LibRetro's VFS maps "ab+" to RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, which + // uses "r+b" internally and fails if the file doesn't exist. Pre-create it. if (!existed) { FileUtil::CreateEmptyFile(transferable_path); } @@ -486,6 +488,14 @@ FileUtil::IOFile ShaderDiskCache::AppendPrecompiledFile(bool write_header) { const auto precompiled_path{GetPrecompiledPath()}; const bool existed = FileUtil::Exists(precompiled_path); +#ifdef HAVE_LIBRETRO_VFS + // LibRetro's VFS maps "ab+" to RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, which + // uses "r+b" internally and fails if the file doesn't exist. Pre-create it. + if (!existed) { + FileUtil::CreateEmptyFile(precompiled_path); + } +#endif + FileUtil::IOFile file(precompiled_path, "ab+"); if (!file.IsOpen()) { LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 91dfb0f1f..3f968e4c9 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -159,7 +159,7 @@ Instance::Instance(Frontend::EmuWindow& window, u32 physical_device_index) VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion))); } - CreateDevice(false); + CreateDevice(); CreateFormatTable(); CollectToolingInfo(); CreateCustomFormatTable(); @@ -394,7 +394,7 @@ void Instance::CreateAttribTable() { } } -bool Instance::CreateDevice(bool libretro) { +bool Instance::CreateDevice() { const vk::StructureChain feature_chain = physical_device.getFeatures2< vk::PhysicalDeviceFeatures2, vk::PhysicalDevicePortabilitySubsetFeaturesKHR, vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT, @@ -483,8 +483,11 @@ bool Instance::CreateDevice(bool libretro) { return false; } - bool graphics_queue_found = libretro; - for (std::size_t i = 0; !libretro && i < family_properties.size(); i++) { +#ifndef HAVE_LIBRETRO + // Find graphics queue family. LibRetro builds skip this since queue_family_index + // is already set by LibRetroVKInstance from the frontend-provided context. + bool graphics_queue_found = false; + for (std::size_t i = 0; i < family_properties.size(); i++) { const u32 index = static_cast(i); if (family_properties[i].queueFlags & vk::QueueFlagBits::eGraphics) { queue_family_index = index; @@ -496,6 +499,7 @@ bool Instance::CreateDevice(bool libretro) { LOG_CRITICAL(Render_Vulkan, "Unable to find graphics and/or present queues."); return false; } +#endif static constexpr std::array queue_priorities = {1.0f}; @@ -614,10 +618,10 @@ bool Instance::CreateDevice(bool libretro) { #undef PROP_GET #undef FEAT_SET - if (libretro) { - return true; - } - +#ifdef HAVE_LIBRETRO + // LibRetro builds: device already created by frontend, just return after feature detection + return true; +#else try { device = physical_device.createDeviceUnique(device_chain.get()); } catch (vk::ExtensionNotPresentError& err) { @@ -632,6 +636,7 @@ bool Instance::CreateDevice(bool libretro) { CreateAllocator(); return true; +#endif } void Instance::CreateAllocator() { diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 21747bb6e..e1969ad33 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -287,7 +287,7 @@ protected: void CreateAttribTable(); /// Creates the logical device opportunistically enabling extensions - bool CreateDevice(bool libretro); + bool CreateDevice(); /// Creates the VMA allocator handle void CreateAllocator(); diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index 71e4f46ec..03b644ea2 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -1,3 +1,7 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + // Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -159,6 +163,8 @@ void DescriptorHeap::Allocate(std::size_t begin, std::size_t end) { if (result == vk::Result::eSuccess) { break; } + // eErrorFragmentedPool: pool has space but is too fragmented to allocate. + // MoltenVK on iOS/tvOS returns this more frequently than native Vulkan drivers. if (result == vk::Result::eErrorOutOfPoolMemory || result == vk::Result::eErrorFragmentedPool) { current_pool++; diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index ebb37029f..cfcb199a1 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included.