09ac66388c
Changes the interface as well to remove any unique methods that frontends needed to call such as StartJoystickEventHandler by conditionally starting the polling thread only if the frontend hasn't started it already. Additionally, moves all global state into a single SDLState class in order to guarantee that the destructors are called in the proper order
281 lines
9 KiB
C++
281 lines
9 KiB
C++
// Copyright 2016 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#define SDL_MAIN_HANDLED
|
|
#include <SDL.h>
|
|
#include <fmt/format.h>
|
|
#include <glad/glad.h>
|
|
#include "common/logging/log.h"
|
|
#include "common/scm_rev.h"
|
|
#include "common/string_util.h"
|
|
#include "core/settings.h"
|
|
#include "input_common/keyboard.h"
|
|
#include "input_common/main.h"
|
|
#include "input_common/motion_emu.h"
|
|
#include "input_common/sdl/sdl.h"
|
|
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
|
|
|
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
|
|
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
|
InputCommon::GetMotionEmu()->Tilt(x, y);
|
|
}
|
|
|
|
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
|
if (button == SDL_BUTTON_LEFT) {
|
|
if (state == SDL_PRESSED) {
|
|
TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
|
} else {
|
|
TouchReleased();
|
|
}
|
|
} else if (button == SDL_BUTTON_RIGHT) {
|
|
if (state == SDL_PRESSED) {
|
|
InputCommon::GetMotionEmu()->BeginTilt(x, y);
|
|
} else {
|
|
InputCommon::GetMotionEmu()->EndTilt();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::pair<unsigned, unsigned> EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const {
|
|
int w, h;
|
|
SDL_GetWindowSize(render_window, &w, &h);
|
|
|
|
touch_x *= w;
|
|
touch_y *= h;
|
|
|
|
return {static_cast<unsigned>(std::max(std::round(touch_x), 0.0f)),
|
|
static_cast<unsigned>(std::max(std::round(touch_y), 0.0f))};
|
|
}
|
|
|
|
void EmuWindow_SDL2::OnFingerDown(float x, float y) {
|
|
// TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind
|
|
// This isn't critical because the best we can do when we have that is to average them, like the
|
|
// 3DS does
|
|
|
|
const auto [px, py] = TouchToPixelPos(x, y);
|
|
TouchPressed(px, py);
|
|
}
|
|
|
|
void EmuWindow_SDL2::OnFingerMotion(float x, float y) {
|
|
const auto [px, py] = TouchToPixelPos(x, y);
|
|
TouchMoved(px, py);
|
|
}
|
|
|
|
void EmuWindow_SDL2::OnFingerUp() {
|
|
TouchReleased();
|
|
}
|
|
|
|
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
|
|
if (state == SDL_PRESSED) {
|
|
InputCommon::GetKeyboard()->PressKey(key);
|
|
} else if (state == SDL_RELEASED) {
|
|
InputCommon::GetKeyboard()->ReleaseKey(key);
|
|
}
|
|
}
|
|
|
|
bool EmuWindow_SDL2::IsOpen() const {
|
|
return is_open;
|
|
}
|
|
|
|
void EmuWindow_SDL2::OnResize() {
|
|
int width, height;
|
|
SDL_GetWindowSize(render_window, &width, &height);
|
|
UpdateCurrentFramebufferLayout(width, height);
|
|
}
|
|
|
|
void EmuWindow_SDL2::Fullscreen() {
|
|
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) {
|
|
return;
|
|
}
|
|
|
|
LOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError());
|
|
|
|
// Try a different fullscreening method
|
|
LOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
|
|
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) {
|
|
return;
|
|
}
|
|
|
|
LOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
|
|
|
|
// Fallback algorithm: Maximise window.
|
|
// Works on all systems (unless something is seriously wrong), so no fallback for this one.
|
|
LOG_INFO(Frontend, "Falling back on a maximised window...");
|
|
SDL_MaximizeWindow(render_window);
|
|
}
|
|
|
|
bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
|
|
std::vector<std::string> unsupported_ext;
|
|
|
|
if (!GLAD_GL_ARB_direct_state_access)
|
|
unsupported_ext.push_back("ARB_direct_state_access");
|
|
if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
|
|
unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
|
|
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
|
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
|
if (!GLAD_GL_ARB_multi_bind)
|
|
unsupported_ext.push_back("ARB_multi_bind");
|
|
|
|
// Extensions required to support some texture formats.
|
|
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
|
unsupported_ext.push_back("EXT_texture_compression_s3tc");
|
|
if (!GLAD_GL_ARB_texture_compression_rgtc)
|
|
unsupported_ext.push_back("ARB_texture_compression_rgtc");
|
|
if (!GLAD_GL_ARB_depth_buffer_float)
|
|
unsupported_ext.push_back("ARB_depth_buffer_float");
|
|
|
|
for (const std::string& ext : unsupported_ext)
|
|
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext);
|
|
|
|
return unsupported_ext.empty();
|
|
}
|
|
|
|
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
|
// Initialize the window
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
|
|
LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
|
|
exit(1);
|
|
}
|
|
|
|
InputCommon::Init();
|
|
|
|
SDL_SetMainReady();
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
|
|
|
std::string window_title = fmt::format("yuzu {} | {}-{}", 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);
|
|
|
|
if (render_window == nullptr) {
|
|
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
|
|
if (fullscreen) {
|
|
Fullscreen();
|
|
}
|
|
|
|
gl_context = SDL_GL_CreateContext(render_window);
|
|
|
|
if (gl_context == nullptr) {
|
|
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
|
|
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
|
LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
|
|
if (!SupportsRequiredGLExtensions()) {
|
|
LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
|
|
exit(1);
|
|
}
|
|
|
|
OnResize();
|
|
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
|
SDL_PumpEvents();
|
|
SDL_GL_SetSwapInterval(false);
|
|
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
|
Common::g_scm_desc);
|
|
Settings::LogSettings();
|
|
|
|
DoneCurrent();
|
|
}
|
|
|
|
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
|
InputCommon::Shutdown();
|
|
SDL_GL_DeleteContext(gl_context);
|
|
SDL_Quit();
|
|
}
|
|
|
|
void EmuWindow_SDL2::SwapBuffers() {
|
|
SDL_GL_SwapWindow(render_window);
|
|
}
|
|
|
|
void EmuWindow_SDL2::PollEvents() {
|
|
SDL_Event event;
|
|
|
|
// SDL_PollEvent returns 0 when there are no more events in the event queue
|
|
while (SDL_PollEvent(&event)) {
|
|
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:
|
|
case SDL_WINDOWEVENT_MINIMIZED:
|
|
OnResize();
|
|
break;
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
is_open = false;
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP:
|
|
OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
|
|
break;
|
|
case SDL_MOUSEMOTION:
|
|
// 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:
|
|
// 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);
|
|
}
|
|
break;
|
|
case SDL_FINGERDOWN:
|
|
OnFingerDown(event.tfinger.x, event.tfinger.y);
|
|
break;
|
|
case SDL_FINGERMOTION:
|
|
OnFingerMotion(event.tfinger.x, event.tfinger.y);
|
|
break;
|
|
case SDL_FINGERUP:
|
|
OnFingerUp();
|
|
break;
|
|
case SDL_QUIT:
|
|
is_open = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EmuWindow_SDL2::MakeCurrent() {
|
|
SDL_GL_MakeCurrent(render_window, gl_context);
|
|
}
|
|
|
|
void EmuWindow_SDL2::DoneCurrent() {
|
|
SDL_GL_MakeCurrent(render_window, nullptr);
|
|
}
|
|
|
|
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
|
|
const std::pair<unsigned, unsigned>& minimal_size) {
|
|
|
|
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
|
}
|