[APU] Upgrade to SDL3

This commit is contained in:
Herman S.
2026-05-26 21:13:06 +09:00
parent d2b5419d79
commit a34897f0a3
19 changed files with 297 additions and 448 deletions
+3 -1
View File
@@ -45,7 +45,9 @@ jobs:
sudo apt-add-repository "deb http://apt.llvm.org/${UBUNTU_BASE}/ llvm-toolchain-${UBUNTU_BASE}-21 main"
sudo apt-get -y update
sudo apt-get -y install libc++-dev libc++abi-dev libgtk-3-dev libsdl2-dev libx11-xcb-dev clang-21 lld-21 ninja-build cmake libwxgtk3.2-dev libfontconfig1-dev libxtst-dev
sudo apt-get -y install libc++-dev libc++abi-dev libgtk-3-dev libx11-xcb-dev clang-21 lld-21 ninja-build cmake libfontconfig1-dev libxtst-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev \
libx11-dev libxext-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxss-dev libxkbcommon-dev libxfixes-dev
# Download linuxdeploy if not cached.
if [ '${{ steps.cache-linuxdeploy.outputs.cache-hit }}' != 'true' ]; then
+4 -3
View File
@@ -29,9 +29,6 @@
[submodule "third_party/cxxopts"]
path = third_party/cxxopts
url = https://github.com/jarro2783/cxxopts.git
[submodule "third_party/SDL2"]
path = third_party/SDL2
url = https://github.com/libsdl-org/SDL.git
[submodule "third_party/utfcpp"]
path = third_party/utfcpp
url = https://github.com/nemtrif/utfcpp.git
@@ -129,3 +126,7 @@
[submodule "third_party/cereal"]
path = third_party/cereal
url = https://github.com/USCiLab/cereal.git
[submodule "third_party/SDL3"]
path = third_party/SDL3
url = https://github.com/libsdl-org/SDL.git
ignore = dirty
+1 -1
View File
@@ -17,7 +17,7 @@ if(APPLE)
endif()
if(APPLE)
# OBJC needed for SDL2's .m sources (Cocoa video, CoreAudio, MFi joystick).
# OBJC needed for SDL3's .m sources (Cocoa, CoreAudio, MFi joystick).
project(xenia LANGUAGES C CXX OBJC OBJCXX)
set(CMAKE_OBJCXX_STANDARD 20)
set(CMAKE_OBJCXX_STANDARD_REQUIRED ON)
+3 -1
View File
@@ -94,7 +94,9 @@ The build script uses Clang 21.
You will also need some development libraries. To get them on an Ubuntu system:
```sh
sudo apt-get install build-essential mesa-vulkan-drivers libc++-dev libc++abi-dev liblz4-dev libsdl2-dev libvulkan-dev libx11-xcb-dev clang-21 llvm-21 ninja-build libwxgtk3.2-dev libfontconfig1-dev
sudo apt-get install build-essential mesa-vulkan-drivers libc++-dev libc++abi-dev liblz4-dev libvulkan-dev libx11-xcb-dev clang-21 llvm-21 ninja-build libfontconfig1-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev \
libx11-dev libxext-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxss-dev libxkbcommon-dev libxfixes-dev
```
In addition, you will need up to date Vulkan libraries and drivers for your hardware, which most distributions have in their standard repositories nowadays.
+1 -1
View File
@@ -118,7 +118,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
X11
xcb
X11-xcb
SDL2
SDL3
)
elseif(APPLE)
target_link_libraries(xenia-app PRIVATE
+1 -1
View File
@@ -8,6 +8,6 @@ target_link_libraries(xenia-apu PUBLIC libavcodec libavutil libavformat xenia-ba
# AudioMediaPlayer instantiates SDLAudioDriver directly on Linux, so the
# SDL chain is a transitive dependency of xenia-apu there.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(xenia-apu PUBLIC xenia-helper-sdl SDL2)
target_link_libraries(xenia-apu PUBLIC xenia-helper-sdl SDL3)
endif()
xe_target_defaults(xenia-apu)
+1 -1
View File
@@ -1,4 +1,4 @@
add_library(xenia-apu-sdl STATIC)
xe_platform_sources(xenia-apu-sdl ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(xenia-apu-sdl PUBLIC xenia-apu xenia-base xenia-helper-sdl SDL2)
target_link_libraries(xenia-apu-sdl PUBLIC xenia-apu xenia-base xenia-helper-sdl SDL3)
xe_target_defaults(xenia-apu-sdl)
+68 -80
View File
@@ -50,57 +50,30 @@ SDLAudioDriver::~SDLAudioDriver() {
};
bool SDLAudioDriver::Initialize() {
SDL_version ver = {};
SDL_GetVersion(&ver);
if ((ver.major < 2) || (ver.major == 2 && ver.minor == 0 && ver.patch < 8)) {
XELOGW(
"SDL library version {}.{}.{} is outdated. "
"You may experience choppy audio.",
ver.major, ver.minor, ver.patch);
}
if (!xe::helper::sdl::SDLHelper::Prepare()) {
return false;
}
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
return false;
}
sdl_initialized_ = true;
SDL_AudioSpec desired_spec = {};
SDL_AudioSpec obtained_spec;
desired_spec.freq = frame_frequency_;
desired_spec.format = AUDIO_F32;
desired_spec.channels = frame_channels_;
desired_spec.samples = channel_samples_;
desired_spec.callback = SDLCallback;
desired_spec.userdata = this;
// Allow the hardware to decide between 5.1 and stereo,
// unless the input is stereo
int allowed_change =
frame_channels_ != 2 ? SDL_AUDIO_ALLOW_CHANNELS_CHANGE : 0;
for (int i = 0; i < 2; i++) {
sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &desired_spec,
&obtained_spec, allowed_change);
if (sdl_device_id_ <= 0) {
XELOGE("SDL_OpenAudioDevice() failed.");
return false;
}
if (obtained_spec.channels == 2 || obtained_spec.channels == 6) {
break;
}
// If the system is 4 or 7.1, let SDL convert
allowed_change = 0;
SDL_CloseAudioDevice(sdl_device_id_);
sdl_device_id_ = -1;
}
if (sdl_device_id_ <= 0) {
XELOGE("Failed to get a compatible SDL Audio Device.");
// SDL3 always converts between the stream spec and the device spec, so we
// just request our native channel count and let SDL3 downmix/upmix to the
// hardware's actual configuration.
SDL_AudioSpec spec = {};
spec.format = SDL_AUDIO_F32;
spec.channels = static_cast<int>(frame_channels_);
spec.freq = static_cast<int>(frame_frequency_);
sdl_stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
&spec, SDLCallback, this);
if (!sdl_stream_) {
XELOGE("SDL_OpenAudioDeviceStream() failed: {}", SDL_GetError());
return false;
}
sdl_device_channels_ = obtained_spec.channels;
SDL_PauseAudioDevice(sdl_device_id_, 0);
SDL_ResumeAudioStreamDevice(sdl_stream_);
return true;
}
@@ -125,14 +98,24 @@ void SDLAudioDriver::SubmitFrame(float* frame) {
}
}
void SDLAudioDriver::Pause() { SDL_PauseAudioDevice(sdl_device_id_, 1); }
void SDLAudioDriver::Pause() {
if (sdl_stream_) {
SDL_PauseAudioStreamDevice(sdl_stream_);
}
}
void SDLAudioDriver::Resume() { SDL_PauseAudioDevice(sdl_device_id_, 0); }
void SDLAudioDriver::Resume() {
if (sdl_stream_) {
SDL_ResumeAudioStreamDevice(sdl_stream_);
}
}
void SDLAudioDriver::Shutdown() {
if (sdl_device_id_ > 0) {
SDL_CloseAudioDevice(sdl_device_id_);
sdl_device_id_ = -1;
if (sdl_stream_) {
// SDL_OpenAudioDeviceStream-created streams own the underlying logical
// device and close it on destroy.
SDL_DestroyAudioStream(sdl_stream_);
sdl_stream_ = nullptr;
}
if (sdl_initialized_) {
SDL_QuitSubSystem(SDL_INIT_AUDIO);
@@ -149,58 +132,63 @@ void SDLAudioDriver::Shutdown() {
};
}
void SDLAudioDriver::SDLCallback(void* userdata, Uint8* stream, int len) {
void SDLAudioDriver::SDLCallback(void* userdata, SDL_AudioStream* stream,
int additional_amount, int total_amount) {
SCOPE_profile_cpu_f("apu");
if (!userdata || !stream) {
XELOGE("SDLAudioDriver::sdl_callback called with nullptr.");
if (!userdata || !stream || additional_amount <= 0) {
return;
}
const auto driver = static_cast<SDLAudioDriver*>(userdata);
assert_true(len == sizeof(float) * driver->channel_samples_ *
driver->sdl_device_channels_);
const int frame_bytes = static_cast<int>(driver->frame_size_);
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
if (driver->frames_queued_.empty()) {
std::memset(stream, 0, len);
} else {
auto buffer = driver->frames_queued_.front();
driver->frames_queued_.pop();
if (driver->need_format_conversion_) {
switch (driver->sdl_device_channels_) {
case 2:
conversion::sequential_6_BE_to_interleaved_2_LE(
reinterpret_cast<float*>(stream), buffer,
driver->channel_samples_);
break;
case 6:
conversion::sequential_6_BE_to_interleaved_6_LE(
reinterpret_cast<float*>(stream), buffer,
driver->channel_samples_);
break;
default:
assert_unhandled_case(driver->sdl_device_channels_);
break;
// SDL3 pulls until additional_amount is satisfied or we run out of frames.
// If the queue empties, returning early lets SDL3 fill the remainder with
// silence — the producer will catch up on the next callback.
while (additional_amount > 0) {
float* buffer;
{
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
if (driver->frames_queued_.empty()) {
return;
}
} else {
assert_true(driver->sdl_device_channels_ == driver->frame_channels_);
std::memcpy(stream, buffer, len);
buffer = driver->frames_queued_.front();
driver->frames_queued_.pop();
}
// Convert into a scratch buffer in the format SDL3 expects (interleaved
// little-endian floats at our requested channel count). SDL3 will then
// matrix-mix this to whatever the hardware actually wants.
float scratch[kFrameSizeMax / sizeof(float)];
if (driver->need_format_conversion_) {
// need_format_conversion implies 6-channel BE-sequential input.
conversion::sequential_6_BE_to_interleaved_6_LE(scratch, buffer,
driver->channel_samples_);
} else {
std::memcpy(scratch, buffer, frame_bytes);
}
// Scale by master (cvar) and per-driver volume.
const uint32_t mv = cvars::volume > 100 ? 100 : cvars::volume;
const float volume = driver->volume_ * (mv / 100.0f);
if (volume != 1.0f) {
float* samples = reinterpret_cast<float*>(stream);
const size_t count = len / sizeof(float);
const size_t count = frame_bytes / sizeof(float);
for (size_t i = 0; i < count; ++i) {
samples[i] *= volume;
scratch[i] *= volume;
}
}
driver->frames_unused_.push(buffer);
SDL_PutAudioStreamData(stream, scratch, frame_bytes);
additional_amount -= frame_bytes;
{
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
driver->frames_unused_.push(buffer);
}
auto ret = driver->semaphore_->Release(1, nullptr);
assert_true(ret);
}
};
}
} // namespace sdl
} // namespace apu
} // namespace xe
+4 -4
View File
@@ -14,7 +14,7 @@
#include <queue>
#include <stack>
#include "SDL.h"
#include <SDL3/SDL.h>
#include "xenia/apu/audio_driver.h"
#include "xenia/base/threading.h"
@@ -38,13 +38,13 @@ class SDLAudioDriver : public AudioDriver {
void Shutdown() override;
protected:
static void SDLCallback(void* userdata, Uint8* stream, int len);
static void SDLCallback(void* userdata, SDL_AudioStream* stream,
int additional_amount, int total_amount);
xe::threading::Semaphore* semaphore_ = nullptr;
SDL_AudioDeviceID sdl_device_id_ = -1;
SDL_AudioStream* sdl_stream_ = nullptr;
bool sdl_initialized_ = false;
uint8_t sdl_device_channels_ = 0;
float volume_ = 1.0f;
+25 -13
View File
@@ -11,6 +11,7 @@
#include <dlfcn.h>
#include <stdlib.h>
#include <cstdint>
#include <cstring>
#include "xenia/base/assert.h"
@@ -18,8 +19,16 @@
#include "xenia/base/string.h"
#include "xenia/base/system.h"
// Use headers in third party to not depend on system sdl headers for building
#include "third_party/SDL2/include/SDL.h"
// Forward-declared instead of pulling in <SDL3/SDL.h> — xenia-base must not
// drag SDL onto its include path, and only the dlsym() cast needs the type.
// Flags and signature are stable across SDL2/3.
namespace {
using SDL_ShowSimpleMessageBox_fn = int (*)(uint32_t flags, const char* title,
const char* message, void* window);
constexpr uint32_t kSDL_MessageBoxError = 0x00000010;
constexpr uint32_t kSDL_MessageBoxWarning = 0x00000020;
constexpr uint32_t kSDL_MessageBoxInformation = 0x00000040;
} // namespace
namespace xe {
@@ -37,15 +46,18 @@ void LaunchFileExplorer(const std::filesystem::path& path) {
}
void ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message) {
void* libsdl2 = dlopen("libSDL2.so", RTLD_LAZY | RTLD_LOCAL);
assert_not_null(libsdl2);
if (libsdl2) {
auto* pSDL_ShowSimpleMessageBox =
reinterpret_cast<decltype(SDL_ShowSimpleMessageBox)*>(
dlsym(libsdl2, "SDL_ShowSimpleMessageBox"));
void* libsdl = dlopen("libSDL3.so.0", RTLD_LAZY | RTLD_LOCAL);
if (!libsdl) {
libsdl = dlopen("libSDL3.so", RTLD_LAZY | RTLD_LOCAL);
}
assert_not_null(libsdl);
if (libsdl) {
auto pSDL_ShowSimpleMessageBox =
reinterpret_cast<SDL_ShowSimpleMessageBox_fn>(
dlsym(libsdl, "SDL_ShowSimpleMessageBox"));
assert_not_null(pSDL_ShowSimpleMessageBox);
if (pSDL_ShowSimpleMessageBox) {
Uint32 flags;
uint32_t flags;
const char* title;
char* message_copy = reinterpret_cast<char*>(alloca(message.size() + 1));
std::memcpy(message_copy, message.data(), message.size());
@@ -55,20 +67,20 @@ void ShowSimpleMessageBox(SimpleMessageBoxType type, std::string_view message) {
default:
case SimpleMessageBoxType::Help:
title = "Xenia Help";
flags = SDL_MESSAGEBOX_INFORMATION;
flags = kSDL_MessageBoxInformation;
break;
case SimpleMessageBoxType::Warning:
title = "Xenia Warning";
flags = SDL_MESSAGEBOX_WARNING;
flags = kSDL_MessageBoxWarning;
break;
case SimpleMessageBoxType::Error:
title = "Xenia Error";
flags = SDL_MESSAGEBOX_ERROR;
flags = kSDL_MessageBoxError;
break;
}
pSDL_ShowSimpleMessageBox(flags, title, message_copy, NULL);
}
dlclose(libsdl2);
dlclose(libsdl);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
add_library(xenia-helper-sdl STATIC)
xe_platform_sources(xenia-helper-sdl ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(xenia-helper-sdl PUBLIC SDL2)
target_link_libraries(xenia-helper-sdl PUBLIC SDL3)
xe_target_defaults(xenia-helper-sdl)
+10 -21
View File
@@ -9,13 +9,6 @@
#include "xenia/helper/sdl/sdl_helper.h"
// On linux we likely build on an "outdated" system but still want to control
// these features when available on a newer system.
#if !SDL_VERSION_ATLEAST(2, 0, 14)
#define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME"
#define SDL_HINT_JOYSTICK_RAWINPUT "SDL_JOYSTICK_RAWINPUT"
#endif
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
@@ -33,9 +26,9 @@ bool SDLHelper::Prepare() {
is_prepared_ &= SetHints();
is_prepared_ &= RedirectLog();
SDL_version ver = {};
SDL_GetVersion(&ver);
XELOGI("SDL Version {}.{}.{} initialized.", ver.major, ver.minor, ver.patch);
const int ver = SDL_GetVersion();
XELOGI("SDL Version {}.{}.{} initialized.", SDL_VERSIONNUM_MAJOR(ver),
SDL_VERSIONNUM_MINOR(ver), SDL_VERSIONNUM_MICRO(ver));
return is_prepared_;
}
@@ -48,8 +41,7 @@ bool SDLHelper::SetHints() {
// Setting hints with normal priority fails when the hint is set via env
// vars or a hint with override priority is set, which does not conclude a
// failure.
if (SDL_FALSE ==
SDL_SetHintWithPriority(
if (!SDL_SetHintWithPriority(
name, value, override_ ? SDL_HINT_OVERRIDE : SDL_HINT_NORMAL)) {
const char* msg_fmt =
"SDLHelper: Unable to set hint \"{}\" to value \"{}\".";
@@ -63,15 +55,12 @@ bool SDLHelper::SetHints() {
return true;
};
// SDL calls timeBeginPeriod(1) but xenia sets this to a lower value before
// using NtSetTimerResolution(). Having that value overwritten causes overall
// fps drops. Use override priority as timer resolution should always be
// managed by xenia. https://bugzilla.libsdl.org/show_bug.cgi?id=5104
suc &= setHint(SDL_HINT_TIMER_RESOLUTION, "0", true);
suc &= setHint(SDL_HINT_AUDIO_CATEGORY, "playback");
suc &= setHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "xenia emulator");
// SDL3 replaced SDL_HINT_AUDIO_DEVICE_APP_NAME with app metadata. Backends
// (PulseAudio, PipeWire, WASAPI session names, etc.) read this for the
// user-facing device label.
SDL_SetAppMetadata("xenia emulator", nullptr, nullptr);
// On Windows, SDL's joystick subsystem creates a hidden device-notification
// window (WM_DEVICECHANGE + raw-input dispatch) on whichever thread inits
@@ -92,7 +81,7 @@ bool SDLHelper::SetHints() {
bool SDLHelper::RedirectLog() {
// Redirect SDL_Log* output (internal library stuff) to our log system.
SDL_LogSetOutputFunction(
SDL_SetLogOutputFunction(
[](void* userdata, int category, SDL_LogPriority priority,
const char* message) {
const char* msg_fmt = "SDL: {}";
@@ -121,7 +110,7 @@ bool SDLHelper::RedirectLog() {
// SDL itself isn't that talkative. Additionally to this settings there are
// hints that can be switched on to output additional internal logging
// information.
SDL_LogSetAllPriority(SDL_LogPriority::SDL_LOG_PRIORITY_VERBOSE);
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
return true;
}
} // namespace sdl
+1 -1
View File
@@ -10,7 +10,7 @@
#ifndef XENIA_HELPER_SDL_SDL_HELPER_H_
#define XENIA_HELPER_SDL_SDL_HELPER_H_
#include "SDL.h"
#include <SDL3/SDL.h>
namespace xe {
namespace helper {
+1 -1
View File
@@ -1,6 +1,6 @@
add_library(xenia-hid-sdl STATIC)
xe_platform_sources(xenia-hid-sdl ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(xenia-hid-sdl PUBLIC xenia-base xenia-hid xenia-ui SDL2)
target_link_libraries(xenia-hid-sdl PUBLIC xenia-base xenia-hid xenia-ui SDL3)
xe_target_defaults(xenia-hid-sdl)
# Bake the bundled controller mappings.
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -18,7 +18,7 @@
#include <optional>
#include <thread>
#include "SDL.h"
#include <SDL3/SDL.h>
#include "third_party/rapidcsv/src/rapidcsv.h"
#include "xenia/hid/input_driver.h"
#include "xenia/xbox.h"
@@ -54,7 +54,7 @@ class SDLInputDriver final : public InputDriver {
private:
struct ControllerState {
SDL_GameController* sdl;
SDL_Gamepad* sdl;
X_INPUT_CAPABILITIES caps;
X_INPUT_STATE state;
bool state_changed;
@@ -84,7 +84,6 @@ class SDLInputDriver final : public InputDriver {
std::optional<size_t> GetControllerIndexFromInstanceID(
SDL_JoystickID instance_id);
ControllerState* GetControllerState(uint32_t user_index);
bool TestSDLVersion() const;
void UpdateXCapabilities(ControllerState& state);
// Owns SDL init, the event pump, and teardown so the UI thread's modal
@@ -92,7 +91,7 @@ class SDLInputDriver final : public InputDriver {
void SDLEventThread(std::promise<X_STATUS> init_result);
bool sdl_events_initialized_;
bool sdl_gamecontroller_initialized_;
bool sdl_gamepad_initialized_;
int sdl_events_unflushed_;
std::thread sdl_thread_;
std::atomic<bool> sdl_thread_should_exit_;
+61 -158
View File
@@ -677,165 +677,68 @@ if(WIN32)
endif()
# ==============================================================================
# SDL2
# SDL3
# ==============================================================================
if(WIN32)
# Build SDL2 from source on Windows
file(GLOB _sdl2_sources
"SDL2/src/*.c"
"SDL2/src/atomic/*.c"
"SDL2/src/audio/*.c"
"SDL2/src/audio/directsound/*.c"
"SDL2/src/audio/disk/*.c"
"SDL2/src/audio/dummy/*.c"
"SDL2/src/audio/wasapi/*.c"
"SDL2/src/audio/winmm/*.c"
"SDL2/src/core/windows/*.c"
"SDL2/src/cpuinfo/*.c"
"SDL2/src/dynapi/*.c"
"SDL2/src/events/*.c"
"SDL2/src/file/*.c"
"SDL2/src/filesystem/windows/*.c"
"SDL2/src/haptic/*.c"
"SDL2/src/haptic/dummy/*.c"
"SDL2/src/haptic/windows/*.c"
"SDL2/src/hidapi/*.c"
"SDL2/src/joystick/*.c"
"SDL2/src/joystick/dummy/*.c"
"SDL2/src/joystick/hidapi/*.c"
"SDL2/src/joystick/virtual/*.c"
"SDL2/src/joystick/windows/*.c"
"SDL2/src/libm/*.c"
"SDL2/src/loadso/windows/*.c"
"SDL2/src/locale/*.c"
"SDL2/src/locale/windows/*.c"
"SDL2/src/misc/*.c"
"SDL2/src/misc/windows/*.c"
"SDL2/src/power/*.c"
"SDL2/src/power/windows/*.c"
"SDL2/src/render/*.c"
"SDL2/src/render/direct3d/*.c"
"SDL2/src/render/direct3d11/*.c"
"SDL2/src/render/direct3d12/*.c"
"SDL2/src/render/opengl/*.c"
"SDL2/src/render/opengles2/*.c"
"SDL2/src/render/software/*.c"
"SDL2/src/sensor/*.c"
"SDL2/src/sensor/dummy/*.c"
"SDL2/src/sensor/windows/*.c"
"SDL2/src/stdlib/*.c"
"SDL2/src/thread/*.c"
"SDL2/src/thread/generic/SDL_syscond.c"
"SDL2/src/thread/windows/*.c"
"SDL2/src/timer/*.c"
"SDL2/src/timer/windows/*.c"
"SDL2/src/video/*.c"
"SDL2/src/video/dummy/*.c"
"SDL2/src/video/windows/*.c"
"SDL2/src/video/yuv2rgb/*.c"
)
add_library(SDL2 STATIC ${_sdl2_sources})
target_compile_definitions(SDL2 PRIVATE
HAVE_LIBC
SDL_LEAN_AND_MEAN
SDL_RENDER_DISABLED
)
target_include_directories(SDL2 PUBLIC SDL2/include)
target_link_libraries(SDL2 PRIVATE setupapi winmm imm32 version)
elseif(APPLE)
# Build SDL2 from source on macOS. SDL2's auto-detected SDL_config_macosx.h
# selects the CoreAudio audio, Cocoa video, IOKit joystick/haptic, MFi
# controller, pthread, and dlopen subsystems wired up below; SDL_RENDER_DISABLED
# keeps the renderer subsystem out (xenia drives Metal directly).
file(GLOB _sdl2_sources
"SDL2/src/*.c"
"SDL2/src/atomic/*.c"
"SDL2/src/audio/*.c"
"SDL2/src/audio/coreaudio/*.m"
"SDL2/src/audio/disk/*.c"
"SDL2/src/audio/dummy/*.c"
"SDL2/src/cpuinfo/*.c"
"SDL2/src/dynapi/*.c"
"SDL2/src/events/*.c"
"SDL2/src/file/*.c"
"SDL2/src/file/cocoa/*.m"
"SDL2/src/filesystem/cocoa/*.m"
"SDL2/src/haptic/*.c"
"SDL2/src/haptic/darwin/*.c"
"SDL2/src/hidapi/*.c"
"SDL2/src/joystick/*.c"
"SDL2/src/joystick/darwin/*.c"
"SDL2/src/joystick/dummy/*.c"
"SDL2/src/joystick/hidapi/*.c"
"SDL2/src/joystick/iphoneos/*.m"
"SDL2/src/joystick/virtual/*.c"
"SDL2/src/libm/*.c"
"SDL2/src/loadso/dlopen/*.c"
"SDL2/src/locale/*.c"
"SDL2/src/locale/macosx/*.m"
"SDL2/src/misc/*.c"
"SDL2/src/misc/macosx/*.m"
"SDL2/src/power/*.c"
"SDL2/src/power/macosx/*.c"
"SDL2/src/render/*.c"
"SDL2/src/sensor/*.c"
"SDL2/src/sensor/dummy/*.c"
"SDL2/src/stdlib/*.c"
"SDL2/src/thread/*.c"
"SDL2/src/thread/pthread/*.c"
"SDL2/src/timer/*.c"
"SDL2/src/timer/unix/*.c"
"SDL2/src/video/*.c"
"SDL2/src/video/cocoa/*.m"
"SDL2/src/video/dummy/*.c"
"SDL2/src/video/yuv2rgb/*.c"
)
add_library(SDL2 STATIC ${_sdl2_sources})
target_compile_definitions(SDL2 PRIVATE
HAVE_LIBC
SDL_LEAN_AND_MEAN
SDL_RENDER_DISABLED
# Apple ships CGL but not <EGL/egl.h>. SDL_USE_BUILTIN_OPENGL_DEFINITIONS
# makes SDL_egl.h provide stub EGL typedefs, which is enough for the
# whole EGL family (SDL_egl.c, SDL_cocoaopengles.m) to compile — the
# actual EGL entry points are runtime-loaded via dlopen("libEGL.so") and
# only fail if an app explicitly asks for SDL_GL_CONTEXT_PROFILE_ES.
SDL_USE_BUILTIN_OPENGL_DEFINITIONS
)
# SDL_mfijoystick.m uses __weak — requires ARC. Other .m files don't rely
# on explicit retain/release, so ARC across the whole OBJC set is safe.
target_compile_options(SDL2 PRIVATE
$<$<COMPILE_LANGUAGE:OBJC>:-fobjc-arc>
)
target_include_directories(SDL2 PUBLIC SDL2/include)
target_link_libraries(SDL2 PRIVATE
"-framework AVFoundation"
"-framework AudioToolbox"
"-framework Carbon"
"-framework Cocoa"
"-framework CoreAudio"
"-framework CoreFoundation"
"-framework CoreGraphics"
"-framework CoreHaptics"
"-framework CoreServices"
"-framework CoreVideo"
"-framework ForceFeedback"
"-framework Foundation"
"-framework GameController"
"-framework IOKit"
"-framework Metal"
"-framework OpenGL"
"-framework QuartzCore"
iconv
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Use system SDL2 on Linux
add_library(SDL2 INTERFACE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2_PKG REQUIRED sdl2)
target_include_directories(SDL2 INTERFACE ${SDL2_PKG_INCLUDE_DIRS})
target_link_libraries(SDL2 INTERFACE ${SDL2_PKG_LDFLAGS})
target_compile_options(SDL2 INTERFACE ${SDL2_PKG_CFLAGS_OTHER})
# Build SDL3 from source on every platform using upstream's own CMake. Xenia
# drives windowing/rendering/GPU directly, so the SDL subsystems we don't
# consume are switched off. Only audio + joystick/gamepad/haptic + events are
# exercised by xenia-apu-sdl / xenia-hid-sdl. Linux builds shared so the
# runtime dlopen("libSDL3.so.0") path in system_gnulinux.cc can resolve and so
# linuxdeploy can bundle the .so into the AppImage; Windows/macOS build static.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(SDL_SHARED ON CACHE INTERNAL "")
set(SDL_STATIC OFF CACHE INTERNAL "")
else()
set(SDL_SHARED OFF CACHE INTERNAL "")
set(SDL_STATIC ON CACHE INTERNAL "")
endif()
set(SDL_TEST_LIBRARY OFF CACHE INTERNAL "")
set(SDL_INSTALL OFF CACHE INTERNAL "")
set(SDL_DISABLE_INSTALL ON CACHE INTERNAL "")
set(SDL_CAMERA OFF CACHE INTERNAL "")
set(SDL_DIALOG OFF CACHE INTERNAL "")
set(SDL_GPU OFF CACHE INTERNAL "")
set(SDL_RENDER OFF CACHE INTERNAL "")
set(SDL_SENSOR OFF CACHE INTERNAL "")
set(SDL_HIDAPI_LIBUSB OFF CACHE INTERNAL "")
# SDL3's sdlcommands.cmake uses file(GLOB CONFIGURE_DEPENDS ...) for its
# source lists. CMake honours that by emitting a "Re-checking globbed
# directories" probe on every build — slow and pointless for a submodule
# pinned to a release tag. Strip the flag in place at configure time;
# .gitmodules marks SDL3 as ignore=dirty so the in-tree edit doesn't pollute
# git status. Same pattern as snappy and zlib-ng.
set(_sdl3_cmd "${CMAKE_CURRENT_SOURCE_DIR}/SDL3/cmake/sdlcommands.cmake")
if(EXISTS "${_sdl3_cmd}")
file(READ "${_sdl3_cmd}" _sdl3_cmd_content)
string(REPLACE " CONFIGURE_DEPENDS " " " _sdl3_cmd_patched "${_sdl3_cmd_content}")
if(NOT "${_sdl3_cmd_patched}" STREQUAL "${_sdl3_cmd_content}")
file(WRITE "${_sdl3_cmd}" "${_sdl3_cmd_patched}")
endif()
endif()
add_subdirectory(SDL3 EXCLUDE_FROM_ALL)
# Consistent target name for downstream link calls.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_library(SDL3 ALIAS SDL3-shared)
else()
add_library(SDL3 ALIAS SDL3-static)
endif()
# SDL3 explicitly bakes /W3 into its target COMPILE_OPTIONS, which clashes
# with the directory-level /W0 above and produces D9025 noise on every TU.
# Strip the /W* and /WX flags it added so only the directory-level /W0
# survives. Mirrors the same workaround we apply to SPIRV-Tools.
if(MSVC)
foreach(_sdl3_target SDL3-static SDL_uclibc)
if(TARGET ${_sdl3_target})
get_target_property(_opts ${_sdl3_target} COMPILE_OPTIONS)
if(_opts)
list(FILTER _opts EXCLUDE REGEX "^/[Ww]([0-9]|all|X)$")
set_target_properties(${_sdl3_target} PROPERTIES COMPILE_OPTIONS "${_opts}")
endif()
endif()
endforeach()
endif()
# ==============================================================================
-1
Submodule third_party/SDL2 deleted from e11183ea6c
Vendored Submodule
+1
Submodule third_party/SDL3 added at d9d5536704