Started transition to SDL3 (audio working)

This commit is contained in:
Jarrod Norwell 2024-05-27 01:06:51 +08:00
parent 1723651511
commit 0471d11909
35 changed files with 1223 additions and 527 deletions

View File

@ -12,12 +12,12 @@ include(DownloadExternals)
include(CMakeDependentOption)
include(CTest)
# Set bundled sdl2/qt as dependent options.
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
CMAKE_DEPENDENT_OPTION(SUDACHI_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON "ENABLE_SDL2;MSVC" OFF)
# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
CMAKE_DEPENDENT_OPTION(SUDACHI_USE_EXTERNAL_SDL2 "Compile external SDL2" ON "ENABLE_SDL2;NOT MSVC" OFF)
# Set bundled sdl3/qt as dependent options.
# OFF by default, but if ENABLE_SDL3 and MSVC are true then ON
option(ENABLE_SDL3 "Enable the SDL3 frontend" ON)
CMAKE_DEPENDENT_OPTION(SUDACHI_USE_BUNDLED_SDL3 "Download bundled SDL3 binaries" ON "ENABLE_SDL3;MSVC" OFF)
# On Linux system SDL3 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
CMAKE_DEPENDENT_OPTION(SUDACHI_USE_EXTERNAL_SDL3 "Compile external SDL3" ON "ENABLE_SDL3;NOT MSVC" OFF)
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF)
@ -527,32 +527,32 @@ if(ENABLE_QT)
endif()
# find SDL2 exports a bunch of variables that are needed, so its easier to do this outside of the sudachi_find_package
if (ENABLE_SDL2)
if (SUDACHI_USE_BUNDLED_SDL2)
# find SDL3 exports a bunch of variables that are needed, so its easier to do this outside of the sudachi_find_package
if (ENABLE_SDL3)
if (SUDACHI_USE_BUNDLED_SDL3)
# Detect toolchain and platform
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
set(SDL2_VER "SDL2-2.28.2")
set(SDL3_VER "SDL3-3.1.2")
else()
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable SUDACHI_USE_BUNDLED_SDL2 and provide your own.")
message(FATAL_ERROR "No bundled SDL3 binaries for your toolchain. Disable SUDACHI_USE_BUNDLED_SDL3 and provide your own.")
endif()
if (DEFINED SDL2_VER)
download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX)
if (DEFINED SDL3_VER)
download_bundled_external("sdl3/" ${SDL3_VER} SDL3_PREFIX)
endif()
set(SDL2_FOUND YES)
set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers")
set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library")
set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll")
set(SDL3_FOUND YES)
set(SDL3_INCLUDE_DIR "${SDL3_PREFIX}/include" CACHE PATH "Path to SDL3 headers")
set(SDL3_LIBRARY "${SDL3_PREFIX}/lib/x64/SDL3.lib" CACHE PATH "Path to SDL3 library")
set(SDL3_DLL_DIR "${SDL3_PREFIX}/lib/x64/" CACHE PATH "Path to SDL3.dll")
add_library(SDL2::SDL2 INTERFACE IMPORTED)
target_link_libraries(SDL2::SDL2 INTERFACE "${SDL2_LIBRARY}")
target_include_directories(SDL2::SDL2 INTERFACE "${SDL2_INCLUDE_DIR}")
elseif (SUDACHI_USE_EXTERNAL_SDL2)
message(STATUS "Using SDL2 from externals.")
add_library(SDL3::SDL3 INTERFACE IMPORTED)
target_link_libraries(SDL3::SDL3 INTERFACE "${SDL3_LIBRARY}")
target_include_directories(SDL3::SDL3 INTERFACE "${SDL3_INCLUDE_DIR}")
elseif (SUDACHI_USE_EXTERNAL_SDL3)
message(STATUS "Using SDL3 from externals.")
else()
find_package(SDL2 2.26.4 REQUIRED)
find_package(SDL3 3.1.2 REQUIRED)
endif()
endif()

View File

@ -4,5 +4,5 @@
function(copy_sudachi_SDL_deps target_dir)
include(WindowsCopyFiles)
set(DLL_DEST "$<TARGET_FILE_DIR:${target_dir}>/")
windows_copy_files(${target_dir} ${SDL2_DLL_DIR} ${DLL_DEST} SDL2.dll)
windows_copy_files(${target_dir} ${SDL3_DLL_DIR} ${DLL_DEST} SDL3.dll)
endfunction(copy_sudachi_SDL_deps)

View File

@ -6,7 +6,7 @@ set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX})
set(SDL2_PATH ${MINGW_PREFIX})
set(SDL3_PATH ${MINGW_PREFIX})
set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-)
# Specify the cross compiler

View File

@ -9,7 +9,7 @@ set(CMAKE_HOST_WIN32 TRUE)
set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX})
set(SDL2_PATH ${MINGW_PREFIX})
set(SDL3_PATH ${MINGW_PREFIX})
set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-)
# Specify the cross compiler

View File

@ -62,8 +62,8 @@ if (ENABLE_LIBUSB AND NOT TARGET libusb::usb)
add_subdirectory(libusb)
endif()
# SDL2
if (SUDACHI_USE_EXTERNAL_SDL2)
# SDL3
if (SUDACHI_USE_EXTERNAL_SDL3)
if (NOT WIN32)
# Sudachi itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
# Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)

2
externals/SDL vendored

@ -1 +1 @@
Subproject commit cc016b0046d563287f0aa9f09b958b5e70d43696
Subproject commit 84cb065da2fcae9482d2ab7702d8bc4218ff80ed

View File

@ -201,7 +201,7 @@ if (SUDACHI_TESTS)
add_subdirectory(tests)
endif()
if (ENABLE_SDL2)
if (ENABLE_SDL3)
add_subdirectory(sudachi_cmd)
endif()

View File

@ -169,7 +169,7 @@ android {
cmake {
arguments(
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
"-DENABLE_SDL3=0", // Don't use SDL
"-DENABLE_WEB_SERVICE=0", // Don't use telemetry
"-DBUNDLE_SPEEX=ON",
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work

View File

@ -382,7 +382,7 @@ bg_green =
# Which audio output engine to use.
# auto (default): Auto-select
# cubeb: Cubeb audio engine (if available)
# sdl2: SDL2 audio engine (if available)
# sdl3: SDL3 audio engine (if available)
# null: No audio output
output_engine =

View File

@ -241,14 +241,14 @@ if (ENABLE_CUBEB)
target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
endif()
if (ENABLE_SDL2)
if (ENABLE_SDL3)
target_sources(audio_core PRIVATE
sink/sdl2_sink.cpp
sink/sdl2_sink.h
sink/sdl3_sink.cpp
sink/sdl3_sink.h
)
target_link_libraries(audio_core PRIVATE SDL2::SDL2)
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
target_link_libraries(audio_core PRIVATE SDL3::SDL3)
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
endif()
if (ANDROID)

View File

@ -6,7 +6,7 @@
#include <SDL.h>
#include "audio_core/common/common.h"
#include "audio_core/sink/sdl2_sink.h"
#include "audio_core/sink/sdl3_sink.h"
#include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
@ -35,6 +35,29 @@ public:
system_channels = system_channels_;
device_channels = device_channels_;
SDL_AudioSpec spec{.format = SDL_AUDIO_S16,
.channels = static_cast<u8>(device_channels),
.freq = TargetSampleRate};
std::string device_name{output_device};
SDL_AudioDeviceID devid = SDL_AUDIO_DEVICE_DEFAULT_OUTPUT;
if (type == StreamType::In) {
device_name = input_device;
devid = SDL_AUDIO_DEVICE_DEFAULT_OUTPUT;
}
device = SDL_OpenAudioDevice(devid, &spec);
if (device == 0) {
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
return;
}
LOG_INFO(Service_Audio, "Opening SDL stream for: {}", SDL_GetAudioDeviceName(device));
stream = SDL_OpenAudioDeviceStream(device, &spec, &SDLSinkStream::DataCallback, this);
/*
SDL_AudioSpec spec;
spec.freq = TargetSampleRate;
spec.channels = static_cast<u8>(device_channels);
@ -66,6 +89,7 @@ public:
"Opening SDL stream {} with: rate {} channels {} (system channels {}) "
" samples {}",
device, obtained.freq, obtained.channels, system_channels, obtained.samples);
*/
}
/**
@ -85,7 +109,7 @@ public:
}
Stop();
SDL_ClearQueuedAudio(device);
SDL_ClearAudioStream(stream);
SDL_CloseAudioDevice(device);
}
@ -101,7 +125,7 @@ public:
}
paused = false;
SDL_PauseAudioDevice(device, 0);
SDL_ResumeAudioDevice(device);
}
/**
@ -112,7 +136,7 @@ public:
return;
}
SignalPause();
SDL_PauseAudioDevice(device, 1);
SDL_PauseAudioDevice(device);
}
private:
@ -124,7 +148,8 @@ private:
* @param stream - Buffer of samples to be filled or read.
* @param len - Length of the stream in bytes.
*/
static void DataCallback(void* userdata, Uint8* stream, int len) {
static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
int total_amount) {
auto* impl = static_cast<SDLSinkStream*>(userdata);
if (!impl) {
@ -133,7 +158,7 @@ private:
const std::size_t num_channels = impl->GetDeviceChannels();
const std::size_t frame_size = num_channels;
const std::size_t num_frames{len / num_channels / sizeof(s16)};
const std::size_t num_frames{total_amount / num_channels / sizeof(s16)};
if (impl->type == StreamType::In) {
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
@ -147,6 +172,7 @@ private:
/// SDL device id of the opened input/output device
SDL_AudioDeviceID device{};
SDL_AudioStream* stream = nullptr;
};
SDLSink::SDLSink(std::string_view target_device_name) {
@ -220,18 +246,34 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
}
}
const int device_count = SDL_GetNumAudioDevices(capture);
for (int i = 0; i < device_count; ++i) {
if (const char* name = SDL_GetAudioDeviceName(i, capture)) {
device_list.emplace_back(name);
switch (capture) {
case false: {
int count = 0;
SDL_GetAudioOutputDevices(&count);
for (int i = 0; i < count; ++i) {
if (const char* name = SDL_GetAudioDeviceName(i)) {
device_list.emplace_back(name);
}
}
break;
}
case true: {
int count = 0;
SDL_GetAudioCaptureDevices(&count);
for (int i = 0; i < count; ++i) {
if (const char* name = SDL_GetAudioDeviceName(i)) {
device_list.emplace_back(name);
}
}
break;
}
}
return device_list;
}
bool IsSDLSuitable() {
#if !defined(HAVE_SDL2)
#if !defined(HAVE_SDL3)
return false;
#else
// Check SDL can init
@ -246,6 +288,7 @@ bool IsSDLSuitable() {
// We can set any latency frequency we want with SDL, so no need to check that.
// Check we can open a device with standard parameters
/*
SDL_AudioSpec spec;
spec.freq = TargetSampleRate;
spec.channels = 2u;
@ -256,6 +299,10 @@ bool IsSDLSuitable() {
SDL_AudioSpec obtained;
auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
*/
SDL_AudioSpec spec{.format = SDL_AUDIO_S16, .channels = 2, .freq = TargetSampleRate};
const auto device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec);
if (device == 0) {
LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",

View File

@ -13,8 +13,8 @@
#ifdef HAVE_CUBEB
#include "audio_core/sink/cubeb_sink.h"
#endif
#ifdef HAVE_SDL2
#include "audio_core/sink/sdl2_sink.h"
#ifdef HAVE_SDL3
#include "audio_core/sink/sdl3_sink.h"
#endif
#include "audio_core/sink/null_sink.h"
#include "common/logging/log.h"
@ -59,9 +59,9 @@ constexpr SinkDetails sink_details[] = {
&IsCubebSuitable,
},
#endif
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
SinkDetails{
Settings::AudioEngine::Sdl2,
Settings::AudioEngine::Sdl3,
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<SDLSink>(device_id);
},

View File

@ -80,7 +80,7 @@ struct EnumMetadata {
enum class AudioEngine : u32 {
Auto,
Cubeb,
Sdl2,
Sdl3,
Null,
Oboe,
};
@ -89,7 +89,7 @@ template <>
inline std::vector<std::pair<std::string, AudioEngine>>
EnumMetadata<AudioEngine>::Canonicalizations() {
return {
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2},
{"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl3", AudioEngine::Sdl3},
{"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe},
};
}

View File

@ -51,7 +51,7 @@ void Config::Initialize(const std::string& config_name) {
void Config::Initialize(const std::optional<std::string> config_path) {
const std::filesystem::path default_sdl_config_path =
FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl2-config.ini";
FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl3-config.ini";
config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path));
void(FS::CreateParentDir(config_loc));
SetUpIni();

View File

@ -47,7 +47,7 @@ else()
)
endif()
if (ENABLE_SDL2)
if (ENABLE_SDL3)
target_sources(input_common PRIVATE
drivers/joycon.cpp
drivers/joycon.h
@ -73,8 +73,8 @@ if (ENABLE_SDL2)
helpers/joycon_protocol/rumble.cpp
helpers/joycon_protocol/rumble.h
)
target_link_libraries(input_common PRIVATE SDL2::SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
target_link_libraries(input_common PRIVATE SDL3::SDL3)
target_compile_definitions(input_common PRIVATE HAVE_SDL3)
endif()
if (ENABLE_LIBUSB)

File diff suppressed because it is too large Load Diff

View File

@ -15,18 +15,14 @@
#include "input_common/input_engine.h"
union SDL_Event;
using SDL_GameController = struct _SDL_GameController;
using SDL_Joystick = struct _SDL_Joystick;
using SDL_JoystickID = s32;
namespace InputCommon {
class SDLJoystick;
class SDLGamepad;
using ButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 20>;
using ZButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
using ButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadButton>, 20>;
using ZButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GamepadAxis>, 2>;
class SDLDriver : public InputEngine {
public:
@ -38,11 +34,13 @@ public:
void PumpEvents() const;
/// Handle SDL_Events for joysticks from SDL_PollEvent
/// Handle SDL_Events for gamepads from SDL_PollEvent
void HandleGameControllerEvent(const SDL_Event& event);
void HandleGamepadEvent(const SDL_Event& event);
/// Get the nth joystick with the corresponding GUID
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
std::shared_ptr<SDLGamepad> GetSDLGamepadBySDLID(SDL_JoystickID sdl_id);
/**
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
@ -50,6 +48,8 @@ public:
*/
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const Common::UUID& guid, int port);
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
std::shared_ptr<SDLGamepad> GetSDLGamepadByGUID(const Common::UUID& guid, int port);
std::shared_ptr<SDLGamepad> GetSDLGamepadByGUID(const std::string& guid, int port);
std::vector<Common::ParamPackage> GetInputDevices() const override;
@ -71,9 +71,12 @@ public:
private:
void InitJoystick(int joystick_index);
void CloseJoystick(SDL_Joystick* sdl_joystick);
void InitGamepad(int gamepad_index);
void CloseGamepad(SDL_Gamepad* sdl_gamepad);
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks();
void CloseGamepads();
/// Takes all vibrations from the queue and sends the command to the controller
void SendVibrations();
@ -88,8 +91,8 @@ private:
Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const;
Common::ParamPackage BuildParamPackageForBinding(
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const;
Common::ParamPackage BuildParamPackageForBinding(int port, const Common::UUID& guid,
const SDL_GamepadBinding& binding) const;
Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
int axis_y, float offset_x,
@ -97,17 +100,25 @@ private:
/// Returns the default button bindings list
ButtonBindings GetDefaultButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
ButtonBindings GetDefaultButtonBinding(const std::shared_ptr<SDLGamepad>& gamepad) const;
/// Returns the button mappings from a single controller
ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const;
ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLGamepad>& gamepad,
const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const;
/// Returns the button mappings from two different controllers
ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
const std::shared_ptr<SDLJoystick>& joystick2,
const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const;
ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLGamepad>& gamepad,
const std::shared_ptr<SDLGamepad>& gamepad2,
const ButtonBindings& switch_to_sdl_button,
const ZButtonBindings& switch_to_sdl_axis) const;
/// Returns true if the button is on the left joycon
bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
@ -118,6 +129,8 @@ private:
/// Map of GUID of a list of corresponding virtual Joysticks
std::unordered_map<Common::UUID, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
std::unordered_map<Common::UUID, std::vector<std::shared_ptr<SDLGamepad>>> gamepad_map;
std::mutex gamepad_map_mutex;
bool start_thread = false;
std::atomic<bool> initialized = false;

View File

@ -22,7 +22,7 @@
#ifdef HAVE_LIBUSB
#include "input_common/drivers/gc_adapter.h"
#endif
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
#include "input_common/drivers/joycon.h"
#include "input_common/drivers/sdl_driver.h"
#endif
@ -87,7 +87,7 @@ struct InputSubsystem::Impl {
#endif
RegisterEngine("virtual_amiibo", virtual_amiibo);
RegisterEngine("virtual_gamepad", virtual_gamepad);
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
RegisterEngine("sdl", sdl);
RegisterEngine("joycon", joycon);
#endif
@ -121,7 +121,7 @@ struct InputSubsystem::Impl {
#endif
UnregisterEngine(virtual_amiibo);
UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
UnregisterEngine(sdl);
UnregisterEngine(joycon);
#endif
@ -151,7 +151,7 @@ struct InputSubsystem::Impl {
#endif
auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
auto sdl_devices = sdl->GetInputDevices();
@ -186,7 +186,7 @@ struct InputSubsystem::Impl {
if (engine == udp_client->GetEngineName()) {
return udp_client;
}
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) {
return sdl;
}
@ -277,7 +277,7 @@ struct InputSubsystem::Impl {
if (engine == virtual_gamepad->GetEngineName()) {
return true;
}
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) {
return true;
}
@ -298,7 +298,7 @@ struct InputSubsystem::Impl {
gcadapter->BeginConfiguration();
#endif
udp_client->BeginConfiguration();
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
sdl->BeginConfiguration();
joycon->BeginConfiguration();
#endif
@ -314,7 +314,7 @@ struct InputSubsystem::Impl {
gcadapter->EndConfiguration();
#endif
udp_client->EndConfiguration();
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
sdl->EndConfiguration();
joycon->EndConfiguration();
#endif
@ -322,7 +322,7 @@ struct InputSubsystem::Impl {
void PumpEvents() const {
update_engine->PumpEvents();
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
sdl->PumpEvents();
#endif
}
@ -347,7 +347,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<GCAdapter> gcadapter;
#endif
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<Joycons> joycon;
#endif

View File

@ -453,9 +453,9 @@ if (SUDACHI_USE_BUNDLED_QT AND QT_VERSION VERSION_LESS 6)
copy_sudachi_Qt5_deps(sudachi)
endif()
if (ENABLE_SDL2)
target_link_libraries(sudachi PRIVATE SDL2::SDL2)
target_compile_definitions(sudachi PRIVATE HAVE_SDL2)
if (ENABLE_SDL3)
target_link_libraries(sudachi PRIVATE SDL3::SDL3)
target_compile_definitions(sudachi PRIVATE HAVE_SDL3)
endif()
if (MSVC)

View File

@ -87,7 +87,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QUrl>
#include <QtConcurrent/QtConcurrent>
#ifdef HAVE_SDL2
#ifdef HAVE_SDL3
#include <SDL.h> // For SDL ScreenSaver functions
#endif
@ -468,7 +468,7 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle());
}
#if defined(HAVE_SDL2) && !defined(_WIN32)
#if defined(HAVE_SDL3) && !defined(_WIN32)
SDL_InitSubSystem(SDL_INIT_VIDEO);
// Set a screensaver inhibition reason string. Currently passed to DBus by SDL and visible to
@ -1760,7 +1760,7 @@ void GMainWindow::OnSigInterruptNotifierActivated() {
void GMainWindow::PreventOSSleep() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
#elif defined(HAVE_SDL2)
#elif defined(HAVE_SDL3)
SDL_DisableScreenSaver();
#endif
}
@ -1768,7 +1768,7 @@ void GMainWindow::PreventOSSleep() {
void GMainWindow::AllowOSSleep() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS);
#elif defined(HAVE_SDL2)
#elif defined(HAVE_SDL3)
SDL_EnableScreenSaver();
#endif
}

View File

@ -13,14 +13,14 @@ function(create_resource file output filename)
endfunction()
add_executable(sudachi-cmd
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
emu_window/emu_window_sdl2_null.cpp
emu_window/emu_window_sdl2_null.h
emu_window/emu_window_sdl2_vk.cpp
emu_window/emu_window_sdl2_vk.h
emu_window/emu_window_sdl3.cpp
emu_window/emu_window_sdl3.h
emu_window/emu_window_sdl3_gl.cpp
emu_window/emu_window_sdl3_gl.h
emu_window/emu_window_sdl3_null.cpp
emu_window/emu_window_sdl3_null.h
emu_window/emu_window_sdl3_vk.cpp
emu_window/emu_window_sdl3_vk.h
precompiled_headers.h
sdl_config.cpp
sdl_config.h
@ -38,7 +38,7 @@ target_link_libraries(sudachi-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads
create_resource("../../dist/sudachi.bmp" "sudachi_cmd/sudachi_icon.h" "sudachi_icon")
target_include_directories(sudachi-cmd PRIVATE ${RESOURCES_DIR})
target_link_libraries(sudachi-cmd PRIVATE SDL2::SDL2 Vulkan::Headers)
target_link_libraries(sudachi-cmd PRIVATE SDL3::SDL3 Vulkan::Headers)
if(UNIX AND NOT APPLE)
install(TARGETS sudachi-cmd)

View File

@ -19,27 +19,27 @@
namespace FS = Common::FS;
const std::filesystem::path default_config_path =
FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl2-config.ini";
FS::GetSudachiPath(FS::SudachiPath::ConfigDir) / "sdl3-config.ini";
Config::Config(std::optional<std::filesystem::path> config_path)
: sdl2_config_loc{config_path.value_or(default_config_path)},
sdl2_config{std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc))} {
: sdl3_config_loc{config_path.value_or(default_config_path)},
sdl3_config{std::make_unique<INIReader>(FS::PathToUTF8String(sdl3_config_loc))} {
Reload();
}
Config::~Config() = default;
bool Config::LoadINI(const std::string& default_contents, bool retry) {
const auto config_loc_str = FS::PathToUTF8String(sdl2_config_loc);
if (sdl2_config->ParseError() < 0) {
const auto config_loc_str = FS::PathToUTF8String(sdl3_config_loc);
if (sdl3_config->ParseError() < 0) {
if (retry) {
LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
config_loc_str);
void(FS::CreateParentDir(sdl2_config_loc));
void(FS::WriteStringToFile(sdl2_config_loc, FS::FileType::TextFile, default_contents));
void(FS::CreateParentDir(sdl3_config_loc));
void(FS::WriteStringToFile(sdl3_config_loc, FS::FileType::TextFile, default_contents));
sdl2_config = std::make_unique<INIReader>(config_loc_str);
sdl3_config = std::make_unique<INIReader>(config_loc_str);
return LoadINI(default_contents, false);
}
@ -80,7 +80,7 @@ static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs>
template <>
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
std::string setting_value = sdl3_config->Get(group, setting.GetLabel(), setting.GetDefault());
if (setting_value.empty()) {
setting_value = setting.GetDefault();
}
@ -89,12 +89,12 @@ void Config::ReadSetting(const std::string& group, Settings::Setting<std::string
template <>
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
setting = sdl3_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
}
template <typename Type, bool ranged>
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
setting = static_cast<Type>(sdl2_config->GetInteger(group, setting.GetLabel(),
setting = static_cast<Type>(sdl3_config->GetInteger(group, setting.GetLabel(),
static_cast<long>(setting.GetDefault())));
}
@ -109,7 +109,7 @@ void Config::ReadCategory(Settings::Category category) {
}
}();
std::string setting_value =
sdl2_config->Get(category_name, setting->GetLabel(), setting->DefaultToString());
sdl3_config->Get(category_name, setting->GetLabel(), setting->DefaultToString());
setting->LoadString(setting_value);
}
}
@ -125,7 +125,7 @@ void Config::ReadValues() {
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
player.buttons[i] =
sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
sdl3_config->Get(group, Settings::NativeButton::mapping[i], default_param);
if (player.buttons[i].empty()) {
player.buttons[i] = default_param;
}
@ -136,7 +136,7 @@ void Config::ReadValues() {
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
player.analogs[i] =
sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
sdl3_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
if (player.analogs[i].empty()) {
player.analogs[i] = default_param;
}
@ -148,18 +148,18 @@ void Config::ReadValues() {
auto& player_motions = player.motions[i];
player_motions =
sdl2_config->Get(group, Settings::NativeMotion::mapping[i], default_param);
sdl3_config->Get(group, Settings::NativeMotion::mapping[i], default_param);
if (player_motions.empty()) {
player_motions = default_param;
}
}
player.connected = sdl2_config->GetBoolean(group, "connected", false);
player.connected = sdl3_config->GetBoolean(group, "connected", false);
}
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.debug_pad_buttons[i] = sdl2_config->Get(
Settings::values.debug_pad_buttons[i] = sdl3_config->Get(
"ControlsGeneral", std::string("debug_pad_") + Settings::NativeButton::mapping[i],
default_param);
if (Settings::values.debug_pad_buttons[i].empty())
@ -170,7 +170,7 @@ void Config::ReadValues() {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.debug_pad_analogs[i] = sdl2_config->Get(
Settings::values.debug_pad_analogs[i] = sdl3_config->Get(
"ControlsGeneral", std::string("debug_pad_") + Settings::NativeAnalog::mapping[i],
default_param);
if (Settings::values.debug_pad_analogs[i].empty())
@ -178,24 +178,24 @@ void Config::ReadValues() {
}
Settings::values.touchscreen.enabled =
sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
sdl3_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
Settings::values.touchscreen.rotation_angle =
sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0);
sdl3_config->GetInteger("ControlsGeneral", "touch_angle", 0);
Settings::values.touchscreen.diameter_x =
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
sdl3_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
Settings::values.touchscreen.diameter_y =
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
sdl3_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
int num_touch_from_button_maps =
sdl2_config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
sdl3_config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
if (num_touch_from_button_maps > 0) {
for (int i = 0; i < num_touch_from_button_maps; ++i) {
Settings::TouchFromButtonMap map;
map.name = sdl2_config->Get("ControlsGeneral",
map.name = sdl3_config->Get("ControlsGeneral",
std::string("touch_from_button_maps_") + std::to_string(i) +
std::string("_name"),
"default");
const int num_touch_maps = sdl2_config->GetInteger(
const int num_touch_maps = sdl3_config->GetInteger(
"ControlsGeneral",
std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
0);
@ -203,7 +203,7 @@ void Config::ReadValues() {
for (int j = 0; j < num_touch_maps; ++j) {
std::string touch_mapping =
sdl2_config->Get("ControlsGeneral",
sdl3_config->Get("ControlsGeneral",
std::string("touch_from_button_maps_") + std::to_string(i) +
std::string("_bind_") + std::to_string(j),
"");
@ -239,28 +239,28 @@ void Config::ReadValues() {
// Data Storage
FS::SetSudachiPath(FS::SudachiPath::NANDDir,
sdl2_config->Get("Data Storage", "nand_directory",
sdl3_config->Get("Data Storage", "nand_directory",
FS::GetSudachiPathString(FS::SudachiPath::NANDDir)));
FS::SetSudachiPath(FS::SudachiPath::SDMCDir,
sdl2_config->Get("Data Storage", "sdmc_directory",
sdl3_config->Get("Data Storage", "sdmc_directory",
FS::GetSudachiPathString(FS::SudachiPath::SDMCDir)));
FS::SetSudachiPath(FS::SudachiPath::LoadDir,
sdl2_config->Get("Data Storage", "load_directory",
sdl3_config->Get("Data Storage", "load_directory",
FS::GetSudachiPathString(FS::SudachiPath::LoadDir)));
FS::SetSudachiPath(FS::SudachiPath::DumpDir,
sdl2_config->Get("Data Storage", "dump_directory",
sdl3_config->Get("Data Storage", "dump_directory",
FS::GetSudachiPathString(FS::SudachiPath::DumpDir)));
// Debugging
Settings::values.record_frame_times =
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
sdl3_config->GetBoolean("Debugging", "record_frame_times", false);
const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
const auto title_list = sdl3_config->Get("AddOns", "title_ids", "");
std::stringstream ss(title_list);
std::string line;
while (std::getline(ss, line, '|')) {
const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
const auto disabled_list = sdl3_config->Get("AddOns", "disabled_" + line, "");
std::stringstream inner_ss(disabled_list);
std::string inner_line;
@ -274,6 +274,6 @@ void Config::ReadValues() {
}
void Config::Reload() {
LoadINI(DefaultINI::sdl2_config_file);
LoadINI(DefaultINI::sdl3_config_file);
ReadValues();
}

View File

@ -13,8 +13,8 @@
class INIReader;
class Config {
std::filesystem::path sdl2_config_loc;
std::unique_ptr<INIReader> sdl2_config;
std::filesystem::path sdl3_config_loc;
std::unique_ptr<INIReader> sdl3_config;
bool LoadINI(const std::string& default_contents = "", bool retry = true);
void ReadValues();
@ -27,7 +27,7 @@ public:
private:
/**
* Applies a value read from the sdl2_config to a Setting.
* Applies a value read from the sdl3_config to a Setting.
*
* @param group The name of the INI group
* @param setting The sudachi setting to modify

View File

@ -5,7 +5,7 @@
namespace DefaultINI {
const char* sdl2_config_file =
const char* sdl3_config_file =
R"(
[ControlsP0]
# The input devices and parameters for each Switch native input
@ -424,7 +424,7 @@ bg_green =
# Which audio output engine to use.
# auto (default): Auto-select
# cubeb: Cubeb audio engine (if available)
# sdl2: SDL2 audio engine (if available)
# sdl3: SDL3 audio engine (if available)
# null: No audio output
output_engine =

View File

@ -1,93 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 sudachi Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <memory>
#include <string>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2_vk.h"
#include <SDL.h>
#include <SDL_syswm.h>
EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
: EmuWindow_SDL2{input_subsystem_, system_} {
const std::string window_title = fmt::format("sudachi {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_SysWMinfo wm;
SDL_VERSION(&wm.version);
if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) {
LOG_CRITICAL(Frontend, "Failed to get information from the window manager: {}",
SDL_GetError());
std::exit(EXIT_FAILURE);
}
SetWindowIcon();
if (fullscreen) {
Fullscreen();
ShowCursor(false);
}
switch (wm.subsystem) {
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS:
window_info.type = Core::Frontend::WindowSystemType::Windows;
window_info.render_surface = reinterpret_cast<void*>(wm.info.win.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_X11
case SDL_SYSWM_TYPE::SDL_SYSWM_X11:
window_info.type = Core::Frontend::WindowSystemType::X11;
window_info.display_connection = wm.info.x11.display;
window_info.render_surface = reinterpret_cast<void*>(wm.info.x11.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_WAYLAND
case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND:
window_info.type = Core::Frontend::WindowSystemType::Wayland;
window_info.display_connection = wm.info.wl.display;
window_info.render_surface = wm.info.wl.surface;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA:
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
window_info.render_surface = SDL_Metal_CreateView(render_window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_ANDROID
case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID:
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface = reinterpret_cast<void*>(wm.info.android.window);
break;
#endif
default:
LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem);
std::exit(EXIT_FAILURE);
break;
}
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
LOG_INFO(Frontend, "sudachi Version: {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
}
EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}

View File

@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL_main.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
@ -13,26 +15,27 @@
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/main.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3.h"
#include "sudachi_cmd/sudachi_icon.h"
EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
: input_subsystem{input_subsystem_}, system{system_} {
input_subsystem->Initialize();
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}, Exiting...", SDL_GetError());
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError());
exit(1);
}
SDL_SetMainReady();
}
EmuWindow_SDL2::~EmuWindow_SDL2() {
EmuWindow_SDL3::~EmuWindow_SDL3() {
system.HIDCore().UnloadInputDevices();
input_subsystem->Shutdown();
SDL_Quit();
}
InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const {
InputCommon::MouseButton EmuWindow_SDL3::SDLButtonToMouseButton(u32 button) const {
switch (button) {
case SDL_BUTTON_LEFT:
return InputCommon::MouseButton::Left;
@ -49,7 +52,7 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons
}
}
std::pair<float, float> EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y) const {
std::pair<float, float> EmuWindow_SDL3::MouseToTouchPos(s32 touch_x, s32 touch_y) const {
int w, h;
SDL_GetWindowSize(render_window, &w, &h);
const float fx = static_cast<float>(touch_x) / w;
@ -58,7 +61,7 @@ std::pair<float, float> EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y
return {std::clamp<float>(fx, 0.0f, 1.0f), std::clamp<float>(fy, 0.0f, 1.0f)};
}
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
void EmuWindow_SDL3::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
const auto mouse_button = SDLButtonToMouseButton(button);
if (state == SDL_PRESSED) {
const auto [touch_x, touch_y] = MouseToTouchPos(x, y);
@ -70,26 +73,26 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
}
}
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
void EmuWindow_SDL3::OnMouseMotion(s32 x, s32 y) {
const auto [touch_x, touch_y] = MouseToTouchPos(x, y);
input_subsystem->GetMouse()->Move(x, y, 0, 0);
input_subsystem->GetMouse()->MouseMove(touch_x, touch_y);
input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
}
void EmuWindow_SDL2::OnFingerDown(float x, float y, std::size_t id) {
void EmuWindow_SDL3::OnFingerDown(float x, float y, std::size_t id) {
input_subsystem->GetTouchScreen()->TouchPressed(x, y, id);
}
void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) {
void EmuWindow_SDL3::OnFingerMotion(float x, float y, std::size_t id) {
input_subsystem->GetTouchScreen()->TouchMoved(x, y, id);
}
void EmuWindow_SDL2::OnFingerUp() {
void EmuWindow_SDL3::OnFingerUp() {
input_subsystem->GetTouchScreen()->ReleaseAllTouch();
}
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
void EmuWindow_SDL3::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
input_subsystem->GetKeyboard()->PressKey(static_cast<std::size_t>(key));
} else if (state == SDL_RELEASED) {
@ -97,32 +100,33 @@ void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
}
}
bool EmuWindow_SDL2::IsOpen() const {
bool EmuWindow_SDL3::IsOpen() const {
return is_open;
}
bool EmuWindow_SDL2::IsShown() const {
bool EmuWindow_SDL3::IsShown() const {
return is_shown;
}
void EmuWindow_SDL2::OnResize() {
void EmuWindow_SDL3::OnResize() {
int width, height;
SDL_GL_GetDrawableSize(render_window, &width, &height);
SDL_GetWindowSizeInPixels(render_window, &width, &height);
UpdateCurrentFramebufferLayout(width, height);
}
void EmuWindow_SDL2::ShowCursor(bool show_cursor) {
SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE);
void EmuWindow_SDL3::ShowCursor(bool show_cursor) {
show_cursor ? SDL_ShowCursor() : SDL_HideCursor();
}
void EmuWindow_SDL2::Fullscreen() {
SDL_DisplayMode display_mode;
void EmuWindow_SDL3::Fullscreen() {
const SDL_DisplayMode* display_mode = nullptr;
switch (Settings::values.fullscreen_mode.GetValue()) {
case Settings::FullscreenMode::Exclusive:
// Set window size to render size before entering fullscreen -- SDL2 does not resize window
// Set window size to render size before entering fullscreen -- SDL3 does not resize window
// to display dimensions automatically in this mode.
if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) {
SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
display_mode = SDL_GetDesktopDisplayMode(0);
if (display_mode == NULL) {
SDL_SetWindowSize(render_window, display_mode->w, display_mode->h);
} else {
LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError());
}
@ -135,7 +139,7 @@ void EmuWindow_SDL2::Fullscreen() {
LOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
[[fallthrough]];
case Settings::FullscreenMode::Borderless:
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) {
if (SDL_SetWindowFullscreen(render_window, SDL_TRUE) == 0) {
return;
}
@ -150,7 +154,7 @@ void EmuWindow_SDL2::Fullscreen() {
}
}
void EmuWindow_SDL2::WaitEvent() {
void EmuWindow_SDL3::WaitEvent() {
// Called on main thread
SDL_Event event;
@ -168,59 +172,55 @@ void EmuWindow_SDL2::WaitEvent() {
}
switch (event.type) {
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
OnResize();
break;
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_EXPOSED:
is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED;
OnResize();
break;
case SDL_WINDOWEVENT_CLOSE:
is_open = false;
break;
}
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
OnResize();
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_EXPOSED:
is_shown = event.window.type == SDL_EVENT_WINDOW_EXPOSED;
OnResize();
break;
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
is_open = false;
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
break;
case SDL_MOUSEMOTION:
case SDL_EVENT_MOUSE_MOTION:
// ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID)
OnMouseMotion(event.motion.x, event.motion.y);
OnMouseMotion(static_cast<s32>(event.motion.x), static_cast<s32>(event.motion.y));
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
// ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID) {
OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y);
OnMouseButton(event.button.button, event.button.state, static_cast<s32>(event.button.x),
static_cast<s32>(event.button.y));
}
break;
case SDL_FINGERDOWN:
case SDL_EVENT_FINGER_DOWN:
OnFingerDown(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchId));
static_cast<std::size_t>(event.tfinger.touchID));
break;
case SDL_FINGERMOTION:
case SDL_EVENT_FINGER_MOTION:
OnFingerMotion(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchId));
static_cast<std::size_t>(event.tfinger.touchID));
break;
case SDL_FINGERUP:
case SDL_EVENT_FINGER_UP:
OnFingerUp();
break;
case SDL_QUIT:
case SDL_EVENT_QUIT:
is_open = false;
break;
default:
break;
}
const u32 current_time = SDL_GetTicks();
const u64 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) {
const auto results = system.GetAndResetPerfStats();
const auto title =
@ -233,22 +233,23 @@ void EmuWindow_SDL2::WaitEvent() {
}
// Credits to Samantas5855 and others for this function.
void EmuWindow_SDL2::SetWindowIcon() {
SDL_RWops* const sudachi_icon_stream = SDL_RWFromConstMem((void*)sudachi_icon, sudachi_icon_size);
void EmuWindow_SDL3::SetWindowIcon() {
SDL_IOStream* const sudachi_icon_stream =
SDL_IOFromConstMem((void*)sudachi_icon, sudachi_icon_size);
if (sudachi_icon_stream == nullptr) {
LOG_WARNING(Frontend, "Failed to create sudachi icon stream.");
return;
}
SDL_Surface* const window_icon = SDL_LoadBMP_RW(sudachi_icon_stream, 1);
SDL_Surface* const window_icon = SDL_LoadBMP_IO(sudachi_icon_stream, 1);
if (window_icon == nullptr) {
LOG_WARNING(Frontend, "Failed to read BMP from stream.");
return;
}
// The icon is attached to the window pointer
SDL_SetWindowIcon(render_window, window_icon);
SDL_FreeSurface(window_icon);
SDL_DestroySurface(window_icon);
}
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
}

View File

@ -19,10 +19,10 @@ class InputSubsystem;
enum class MouseButton;
} // namespace InputCommon
class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
class EmuWindow_SDL3 : public Core::Frontend::EmuWindow {
public:
explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_);
~EmuWindow_SDL2();
explicit EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_);
~EmuWindow_SDL3();
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
@ -79,11 +79,11 @@ protected:
/// Is the window being shown?
bool is_shown = true;
/// Internal SDL2 render window
/// Internal SDL3 render window
SDL_Window* render_window{};
/// Keeps track of how often to update the title bar during gameplay
u32 last_time = 0;
u64 last_time = 0;
/// Input subsystem to use with this window.
InputCommon::InputSubsystem* input_subsystem;

View File

@ -16,8 +16,8 @@
#include "common/string_util.h"
#include "core/core.h"
#include "input_common/main.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3_gl.h"
#include "video_core/renderer_base.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2_gl.h"
class SDLGLContext : public Core::Frontend::GraphicsContext {
public:
@ -55,7 +55,7 @@ private:
bool is_current = false;
};
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() {
std::vector<std::string_view> unsupported_ext;
// Extensions required to support some texture formats.
@ -73,9 +73,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
return unsupported_ext.empty();
}
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_,
EmuWindow_SDL3_GL::EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
: EmuWindow_SDL2{input_subsystem_, system_} {
: EmuWindow_SDL3{input_subsystem_, system_} {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
@ -93,14 +93,11 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
std::string window_title = fmt::format("sudachi {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
Layout::ScreenUndocked::Height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
LOG_CRITICAL(Frontend, "Failed to create SDL3 window! {}", SDL_GetError());
exit(1);
}
@ -117,15 +114,15 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
core_context = CreateSharedContext();
if (window_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
LOG_CRITICAL(Frontend, "Failed to create SDL3 GL context: {}", SDL_GetError());
exit(1);
}
if (core_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
LOG_CRITICAL(Frontend, "Failed to create shared SDL3 GL context: {}", SDL_GetError());
exit(1);
}
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
exit(1);
}
@ -138,16 +135,16 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
LOG_INFO(Frontend, "sudachi Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_desc);
LOG_INFO(Frontend, "sudachi Version: {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
Settings::LogSettings();
}
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
EmuWindow_SDL3_GL::~EmuWindow_SDL3_GL() {
core_context.reset();
SDL_GL_DeleteContext(window_context);
}
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL3_GL::CreateSharedContext() const {
return std::make_unique<SDLGLContext>(render_window);
}

View File

@ -5,7 +5,7 @@
#include <memory>
#include "core/frontend/emu_window.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3.h"
namespace Core {
class System;
@ -15,11 +15,11 @@ namespace InputCommon {
class InputSubsystem;
}
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
class EmuWindow_SDL3_GL final : public EmuWindow_SDL3 {
public:
explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_,
explicit EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_,
bool fullscreen);
~EmuWindow_SDL2_GL();
~EmuWindow_SDL3_GL();
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;

View File

@ -9,10 +9,10 @@
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2_null.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3_null.h"
#include "video_core/renderer_null/renderer_null.h"
#ifdef SUDACHI_USE_EXTERNAL_SDL2
#ifdef SUDACHI_USE_EXTERNAL_SDL3
// Include this before SDL.h to prevent the external from including a dummy
#define USING_GENERATED_CONFIG_H
#include <SDL_config.h>
@ -20,16 +20,14 @@
#include <SDL.h>
EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_,
EmuWindow_SDL3_Null::EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
: EmuWindow_SDL2{input_subsystem_, system_} {
: EmuWindow_SDL3{input_subsystem_, system_} {
const std::string window_title =
fmt::format("sudachi {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch,
Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
render_window = SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
Layout::ScreenUndocked::Height, SDL_WINDOW_RESIZABLE);
SetWindowIcon();
@ -45,8 +43,8 @@ EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subs
Common::g_scm_branch, Common::g_scm_desc);
}
EmuWindow_SDL2_Null::~EmuWindow_SDL2_Null() = default;
EmuWindow_SDL3_Null::~EmuWindow_SDL3_Null() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_Null::CreateSharedContext() const {
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL3_Null::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}

View File

@ -6,7 +6,7 @@
#include <memory>
#include "core/frontend/emu_window.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3.h"
namespace Core {
class System;
@ -16,11 +16,11 @@ namespace InputCommon {
class InputSubsystem;
}
class EmuWindow_SDL2_Null final : public EmuWindow_SDL2 {
class EmuWindow_SDL3_Null final : public EmuWindow_SDL3 {
public:
explicit EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_,
explicit EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system, bool fullscreen);
~EmuWindow_SDL2_Null() override;
~EmuWindow_SDL3_Null() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
};

View File

@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2018 sudachi Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <memory>
#include <string>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3_vk.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include <SDL.h>
EmuWindow_SDL3_VK::EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_,
Core::System& system_, bool fullscreen)
: EmuWindow_SDL3{input_subsystem_, system_} {
const std::string window_title =
fmt::format("sudachi {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch,
Common::g_scm_desc);
render_window = SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
Layout::ScreenUndocked::Height, SDL_WINDOW_RESIZABLE);
// SDL_SysWMinfo wm;
// SDL_VERSION(&wm.version);
// if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) {
// LOG_CRITICAL(Frontend, "Failed to get information from the window manager: {}",
// SDL_GetError());
// std::exit(EXIT_FAILURE);
// }
SetWindowIcon();
if (fullscreen) {
Fullscreen();
ShowCursor(false);
}
#if defined(SDL_PLATFORM_WIN32)
HWND hwnd = (HWND)SDL_GetProperty(SDL_GetWindowProperties(render_window),
SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
window_info.type = Core::Frontend::WindowSystemType::Windows;
window_info.render_surface = reinterpret_cast<void*>(hwnd);
#endif
/*
switch (wm.subsystem) {
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS:
window_info.type = Core::Frontend::WindowSystemType::Windows;
window_info.render_surface = reinterpret_cast<void*>(wm.info.win.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_X11
case SDL_SYSWM_TYPE::SDL_SYSWM_X11:
window_info.type = Core::Frontend::WindowSystemType::X11;
window_info.display_connection = wm.info.x11.display;
window_info.render_surface = reinterpret_cast<void*>(wm.info.x11.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_WAYLAND
case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND:
window_info.type = Core::Frontend::WindowSystemType::Wayland;
window_info.display_connection = wm.info.wl.display;
window_info.render_surface = wm.info.wl.surface;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA:
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
window_info.render_surface = SDL_Metal_CreateView(render_window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_ANDROID
case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID:
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface = reinterpret_cast<void*>(wm.info.android.window);
break;
#endif
default:
LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem);
std::exit(EXIT_FAILURE);
break;
}
*/
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
LOG_INFO(Frontend, "sudachi Version: {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
}
EmuWindow_SDL3_VK::~EmuWindow_SDL3_VK() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL3_VK::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}

View File

@ -6,7 +6,7 @@
#include <memory>
#include "core/frontend/emu_window.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3.h"
namespace Core {
class System;
@ -16,11 +16,11 @@ namespace InputCommon {
class InputSubsystem;
}
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
class EmuWindow_SDL3_VK final : public EmuWindow_SDL3 {
public:
explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system,
explicit EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system,
bool fullscreen);
~EmuWindow_SDL2_VK() override;
~EmuWindow_SDL3_VK() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
};

View File

@ -34,11 +34,11 @@
#include "input_common/main.h"
#include "network/network.h"
#include "sdl_config.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3_gl.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3_null.h"
#include "sudachi_cmd/emu_window/emu_window_sdl3_vk.h"
#include "video_core/renderer_base.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2_gl.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2_null.h"
#include "sudachi_cmd/emu_window/emu_window_sdl2_vk.h"
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
@ -346,16 +346,16 @@ int main(int argc, char** argv) {
// Apply the command line arguments
system.ApplySettings();
std::unique_ptr<EmuWindow_SDL2> emu_window;
std::unique_ptr<EmuWindow_SDL3> emu_window;
switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, system, fullscreen);
emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, system, fullscreen);
break;
case Settings::RendererBackend::Vulkan:
emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem, system, fullscreen);
emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, system, fullscreen);
break;
case Settings::RendererBackend::Null:
emu_window = std::make_unique<EmuWindow_SDL2_Null>(&input_subsystem, system, fullscreen);
emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, system, fullscreen);
break;
}
@ -394,11 +394,12 @@ int main(int argc, char** argv) {
static_cast<u32>(Core::SystemResultStatus::ErrorLoader)) {
const u16 loader_id = static_cast<u16>(Core::SystemResultStatus::ErrorLoader);
const u16 error_id = static_cast<u16>(load_result) - loader_id;
LOG_CRITICAL(Frontend,
"While attempting to load the ROM requested, an error occurred. Please "
"refer to the sudachi wiki for more information or the sudachi discord for "
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
LOG_CRITICAL(
Frontend,
"While attempting to load the ROM requested, an error occurred. Please "
"refer to the sudachi wiki for more information or the sudachi discord for "
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
}
break;
}