mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-04-24 19:55:58 -06:00
libretro core: address review feedback
This commit is contained in:
parent
6f5318369b
commit
2831ddf805
6
.github/workflows/libretro.yml
vendored
6
.github/workflows/libretro.yml
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
16
externals/CMakeLists.txt
vendored
16
externals/CMakeLists.txt
vendored
@ -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()
|
||||
|
||||
|
||||
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
16
externals/libretro-common/CMakeLists.txt
vendored
Normal file
@ -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
|
||||
)
|
||||
@ -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<s16, 2> 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()) {
|
||||
|
||||
@ -2,41 +2,22 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <list>
|
||||
#include <numeric>
|
||||
#include <libretro.h>
|
||||
#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<void(s16*, std::size_t)> cb;
|
||||
};
|
||||
LibRetroSink::LibRetroSink(std::string) {}
|
||||
|
||||
LibRetroSink::LibRetroSink(std::string target_device_name) : impl(std::make_unique<Impl>()) {}
|
||||
|
||||
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<void(s16*, std::size_t)> cb) {
|
||||
this->impl->cb = cb;
|
||||
}
|
||||
|
||||
void LibRetroSink::OnAudioSubmission(std::size_t frames) {
|
||||
std::vector<s16> 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<const s16*>(data), num_samples);
|
||||
}
|
||||
|
||||
std::vector<std::string> ListLibretroSinkDevices() {
|
||||
@ -44,11 +25,3 @@ std::vector<std::string> 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;
|
||||
}
|
||||
|
||||
@ -5,15 +5,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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<void(s16*, std::size_t)> cb) override;
|
||||
// Not used for immediate submission sinks
|
||||
void SetCallback(std::function<void(s16*, std::size_t)> cb) override {};
|
||||
|
||||
void OnAudioSubmission(std::size_t frames) override;
|
||||
bool ImmediateSubmission() override { return true; }
|
||||
|
||||
struct Impl;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
void PushSamples(const void* data, std::size_t num_samples) override;
|
||||
};
|
||||
|
||||
void audio_callback();
|
||||
|
||||
void audio_set_state(bool new_state);
|
||||
|
||||
std::vector<std::string> ListLibretroSinkDevices();
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include "common/common_types.h"
|
||||
#include "audio_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
@ -31,8 +31,22 @@ public:
|
||||
*/
|
||||
virtual void SetCallback(std::function<void(s16*, std::size_t)> 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
|
||||
|
||||
@ -1,21 +1,34 @@
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$<CONFIG>)
|
||||
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
|
||||
$<$<BOOL:${ENABLE_VULKAN}>: 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
|
||||
$<TARGET_OBJECTS:azahar_libretro_common>)
|
||||
|
||||
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")
|
||||
|
||||
@ -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<retro_memory_descriptor> 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<u8*>(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<unsigned>(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<int>(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<std::vector<u8>> 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<u8> 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<u8> buffer(static_cast<const u8*>(data), static_cast<const u8*>(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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -13,7 +13,7 @@ enum CStickFunction { Both, CStick, Touchscreen };
|
||||
|
||||
struct CoreSettings {
|
||||
|
||||
::std::string file_path;
|
||||
std::string file_path;
|
||||
|
||||
float deadzone = 1.f;
|
||||
|
||||
|
||||
@ -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<unsigned>(width),
|
||||
static_cast<unsigned>(height), 0);
|
||||
ResetGLState();
|
||||
current_state.Apply();
|
||||
#endif
|
||||
break;
|
||||
|
||||
@ -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<u32, u32> minimal_size) override;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#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
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
@ -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<int>(layout.bottom_screen.left),
|
||||
std::min(newX, static_cast<int>(layout.bottom_screen.right))) -
|
||||
x = std::clamp(newX, static_cast<int>(layout.bottom_screen.left),
|
||||
static_cast<int>(layout.bottom_screen.right)) -
|
||||
layout.bottom_screen.left;
|
||||
y = std::max(static_cast<int>(layout.bottom_screen.top),
|
||||
std::min(newY, static_cast<int>(layout.bottom_screen.bottom))) -
|
||||
y = std::clamp(newY, static_cast<int>(layout.bottom_screen.top),
|
||||
static_cast<int>(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<int>(layout.bottom_screen.left),
|
||||
std::min(newX, static_cast<int>(layout.bottom_screen.right))) -
|
||||
x = std::clamp(newX, static_cast<int>(layout.bottom_screen.left),
|
||||
static_cast<int>(layout.bottom_screen.right)) -
|
||||
layout.bottom_screen.left;
|
||||
y = std::max(static_cast<int>(layout.bottom_screen.top),
|
||||
std::min(newY, static_cast<int>(layout.bottom_screen.bottom))) -
|
||||
y = std::clamp(newY, static_cast<int>(layout.bottom_screen.top),
|
||||
static_cast<int>(layout.bottom_screen.bottom)) -
|
||||
layout.bottom_screen.top;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 <cstddef>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
@ -491,8 +491,8 @@ private:
|
||||
lambda(static_cast<Backend&>(file_backend));
|
||||
#ifdef ANDROID
|
||||
lambda(static_cast<Backend&>(lc_backend));
|
||||
#endif
|
||||
#endif
|
||||
#endif // ANDROID
|
||||
#endif // HAVE_LIBRETRO
|
||||
}
|
||||
|
||||
static void Deleter(Impl* ptr) {
|
||||
|
||||
@ -362,11 +362,10 @@ public:
|
||||
|
||||
void LoadState(u32 slot);
|
||||
|
||||
#ifdef HAVE_LIBRETRO
|
||||
std::vector<u8> SaveStateBuffer() const;
|
||||
|
||||
bool LoadStateBuffer(std::vector<u8> buffer);
|
||||
#endif
|
||||
|
||||
/// Self delete ncch
|
||||
bool SetSelfDelete(const std::string& file) {
|
||||
if (m_filepath == file) {
|
||||
|
||||
@ -2308,7 +2308,9 @@ std::optional<SOC_U::InterfaceInfo> 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) {
|
||||
|
||||
@ -217,7 +217,6 @@ void System::LoadState(u32 slot) {
|
||||
ia&* this;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBRETRO
|
||||
std::vector<u8> System::SaveStateBuffer() const {
|
||||
std::ostringstream sstream{std::ios_base::binary};
|
||||
// Serialize
|
||||
@ -290,6 +289,5 @@ bool System::LoadStateBuffer(std::vector<u8> buffer) {
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@ -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 $<TARGET_OBJECTS:azahar_libretro_common>)
|
||||
endif()
|
||||
|
||||
add_test(NAME tests COMMAND tests)
|
||||
|
||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||
|
||||
@ -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<SwRenderer::SwBlitter>(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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<u32>(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<f32, 1> 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() {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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++;
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user