[externals] Update to SDL3 (#3952)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run

Since the launch of the steam controller I think it's only best to push towards updating to SDL3 allowing for a wider range of controller support

I went ahead and started on getting it working. Everything here should be functional, I've personally tested it all on Arch Linux. Still untested on windows, so looking for feedback on that

Any feedback and help would be appreciated!

Main changes:
- Bump everything to SDL3
- Handle SDL3 audio and input
- Add steam controller support, including HD Rumble
- Improved battery reporting via the status icon by using real % rather than state alone

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3952
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
This commit is contained in:
Kaydax 2026-05-18 19:07:41 +02:00 committed by crueter
parent 02521882e7
commit ad2e1cc554
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
34 changed files with 768 additions and 537 deletions

View file

@ -17,20 +17,20 @@ endfunction()
if (ENABLE_OPENGL)
list(APPEND OPENGL_SOURCES
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
emu_window/emu_window_sdl3_gl.cpp
emu_window/emu_window_sdl3_gl.h
)
else()
set(OPENGL_SOURCES "")
endif()
add_executable(yuzu-cmd
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.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_null.cpp
emu_window/emu_window_sdl3_null.h
emu_window/emu_window_sdl3_vk.cpp
emu_window/emu_window_sdl3_vk.h
sdl_config.cpp
sdl_config.h
yuzu.cpp
@ -50,7 +50,7 @@ target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR})
target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2)
target_link_libraries(yuzu-cmd PRIVATE SDL3::SDL3)
if(UNIX AND NOT APPLE)
install(TARGETS yuzu-cmd)

View file

@ -1,98 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <memory>
#include <string>
#include <fmt/ranges.h>
#include "common/logging.h"
#include "common/scm_rev.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "yuzu_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("Eden {} | {}-{} (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, "Eden 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,9 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL.h>
#include <SDL3/SDL.h>
#include "common/logging.h"
#include "common/scm_rev.h"
@ -15,26 +16,25 @@
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/main.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#include "yuzu_cmd/yuzu_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)) {
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;
@ -52,7 +52,7 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons
}
/// @brief Translates pixel position to float position
EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y) const {
EmuWindow_SDL3::FloatPairNonHFA EmuWindow_SDL3::MouseToTouchPos(s32 touch_x, s32 touch_y) const {
int w = 0, h = 0;
SDL_GetWindowSize(render_window, &w, &h);
const float fx = float(touch_x) / w;
@ -64,9 +64,9 @@ EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32
};
}
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) {
if (state != 0) {
auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y);
input_subsystem->GetMouse()->PressButton(x, y, mouse_button);
input_subsystem->GetMouse()->PressMouseButton(mouse_button);
@ -76,64 +76,70 @@ 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) {
auto const [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) {
if (state == SDL_PRESSED) {
void EmuWindow_SDL3::OnKeyEvent(int key, u8 state) {
if (state != 0) {
input_subsystem->GetKeyboard()->PressKey(static_cast<std::size_t>(key));
} else if (state == SDL_RELEASED) {
} else {
input_subsystem->GetKeyboard()->ReleaseKey(static_cast<std::size_t>(key));
}
}
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) {
if (show_cursor) {
SDL_ShowCursor();
} else {
SDL_HideCursor();
}
}
void EmuWindow_SDL2::Fullscreen() {
void EmuWindow_SDL3::Fullscreen() {
SDL_DisplayMode display_mode;
switch (Settings::values.fullscreen_mode.GetValue()) {
case Settings::FullscreenMode::Exclusive:
// Set window size to render size before entering fullscreen -- SDL2 does not resize window
// to display dimensions automatically in this mode.
if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) {
// Set window size to render size before entering fullscreen in exclusive mode.
if (const SDL_DisplayMode* display_mode_ptr =
SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) {
display_mode = *display_mode_ptr;
SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
SDL_SetWindowFullscreenMode(render_window, &display_mode);
} else {
LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError());
}
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) {
if (SDL_SetWindowFullscreen(render_window, true)) {
return;
}
@ -141,7 +147,8 @@ 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) {
SDL_SetWindowFullscreenMode(render_window, nullptr);
if (SDL_SetWindowFullscreen(render_window, true)) {
return;
}
@ -156,7 +163,7 @@ void EmuWindow_SDL2::Fullscreen() {
}
}
void EmuWindow_SDL2::WaitEvent() {
void EmuWindow_SDL3::WaitEvent() {
// Called on main thread
SDL_Event event;
@ -174,52 +181,52 @@ 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_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
OnResize();
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
case SDL_EVENT_WINDOW_MINIMIZED:
is_shown = false;
OnResize();
break;
case SDL_MOUSEMOTION:
case SDL_EVENT_WINDOW_EXPOSED:
is_shown = true;
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.scancode), event.key.down ? 1 : 0);
break;
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);
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.down ? 1 : 0,
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:
@ -241,22 +248,22 @@ void EmuWindow_SDL2::WaitEvent() {
}
// Credits to Samantas5855 and others for this function.
void EmuWindow_SDL2::SetWindowIcon() {
SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size);
void EmuWindow_SDL3::SetWindowIcon() {
SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
if (yuzu_icon_stream == nullptr) {
LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
return;
}
SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1);
SDL_Surface* const window_icon = SDL_LoadBMP_IO(yuzu_icon_stream, true);
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

@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -22,10 +23,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;
@ -85,7 +86,7 @@ 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

View file

@ -9,7 +9,7 @@
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL3/SDL.h>
#include <fmt/ranges.h>
#include <glad/glad.h>
@ -20,7 +20,13 @@
#include "core/core.h"
#include "input_common/main.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h"
namespace {
void* SDLGLGetProcAddress(const char* proc_name) {
return reinterpret_cast<void*>(SDL_GL_GetProcAddress(proc_name));
}
} // Anonymous namespace
class SDLGLContext : public Core::Frontend::GraphicsContext {
public:
@ -30,7 +36,7 @@ public:
~SDLGLContext() {
DoneCurrent();
SDL_GL_DeleteContext(context);
SDL_GL_DestroyContext(context);
}
void SwapBuffers() override {
@ -58,7 +64,7 @@ private:
bool is_current = false;
};
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() {
std::vector<std::string_view> unsupported_ext{};
#ifdef HAS_OPENGL
// Extensions required to support some texture formats.
@ -72,9 +78,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);
@ -92,14 +98,13 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
std::string window_title = fmt::format("{} | {}-{}", 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 |
SDL_WINDOW_HIGH_PIXEL_DENSITY);
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);
}
@ -116,15 +121,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(static_cast<GLADloadproc>(SDLGLGetProcAddress))) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
exit(1);
}
@ -142,11 +147,11 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
Settings::LogSettings();
}
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
EmuWindow_SDL3_GL::~EmuWindow_SDL3_GL() {
core_context.reset();
SDL_GL_DeleteContext(window_context);
SDL_GL_DestroyContext(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

@ -1,11 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <SDL3/SDL.h>
#include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
namespace Core {
class System;
@ -15,11 +19,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;
@ -27,8 +31,6 @@ private:
/// Whether the GPU and driver supports the OpenGL extension required
bool SupportsRequiredGLExtensions();
using SDL_GLContext = void*;
/// The OpenGL context associated with the window
SDL_GLContext window_context;

View file

@ -13,25 +13,25 @@
#include "common/logging.h"
#include "common/scm_rev.h"
#include "video_core/renderer_null/renderer_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
#ifdef YUZU_USE_EXTERNAL_SDL2
#ifdef YUZU_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>
#include <SDL3/SDL_config.h>
#endif
#include <SDL.h>
#include <SDL3/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("Eden {} | {}-{} (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_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width,
Layout::ScreenUndocked::Height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
SetWindowIcon();
@ -47,8 +47,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

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -6,7 +9,7 @@
#include <memory>
#include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
namespace Core {
class System;
@ -16,11 +19,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,99 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include <cstdint>
#include <memory>
#include <string>
#include <fmt/ranges.h>
#include "common/logging.h"
#include "common/scm_rev.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_properties.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("Eden {} | {}-{} (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_WINDOW_HIGH_PIXEL_DENSITY);
const SDL_PropertiesID window_props = SDL_GetWindowProperties(render_window);
SetWindowIcon();
if (fullscreen) {
Fullscreen();
ShowCursor(false);
}
if (void* hwnd =
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr)) {
window_info.type = Core::Frontend::WindowSystemType::Windows;
window_info.render_surface = hwnd;
} else if (void* wl_display =
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER,
nullptr);
wl_display != nullptr) {
void* wl_surface =
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr);
if (wl_surface == nullptr) {
LOG_CRITICAL(Frontend, "Wayland surface is unavailable");
std::exit(EXIT_FAILURE);
}
window_info.type = Core::Frontend::WindowSystemType::Wayland;
window_info.display_connection = wl_display;
window_info.render_surface = wl_surface;
} else if (void* x11_display =
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER,
nullptr);
x11_display != nullptr) {
const auto x11_window =
SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
if (x11_window == 0) {
LOG_CRITICAL(Frontend, "X11 window handle is unavailable");
std::exit(EXIT_FAILURE);
}
window_info.type = Core::Frontend::WindowSystemType::X11;
window_info.display_connection = x11_display;
window_info.render_surface = reinterpret_cast<void*>(static_cast<uintptr_t>(x11_window));
} else if (SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER,
nullptr) != nullptr) {
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
window_info.render_surface = SDL_Metal_CreateView(render_window);
} else if (void* android_window =
SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER,
nullptr);
android_window != nullptr) {
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface = android_window;
} else {
LOG_CRITICAL(Frontend, "Unable to determine native window backend from SDL properties");
std::exit(EXIT_FAILURE);
}
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
LOG_INFO(Frontend, "Eden 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

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -6,7 +9,7 @@
#include <memory>
#include "core/frontend/emu_window.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
namespace Core {
class System;
@ -16,11 +19,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

@ -6,7 +6,7 @@
// SDL will break our main function in yuzu-cmd if we don't define this before adding SDL.h
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL3/SDL.h>
#include "common/logging.h"
#include "input_common/main.h"

View file

@ -30,12 +30,12 @@
#include "network/network.h"
#include "sdl_config.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#ifdef HAS_OPENGL
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h"
#endif
#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
@ -349,20 +349,20 @@ 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()) {
#ifdef HAS_OPENGL
case Settings::RendererBackend::OpenGL_GLSL:
case Settings::RendererBackend::OpenGL_GLASM:
case Settings::RendererBackend::OpenGL_SPIRV:
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;
#endif
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;
default:
LOG_CRITICAL(Frontend, "Invalid renderer backend");