Duckstation/src/pse-sdl/sdl_interface.cpp

630 lines
17 KiB
C++
Raw Normal View History

2019-09-09 07:01:26 +00:00
#include "sdl_interface.h"
#include "YBaseLib/ByteStream.h"
#include "YBaseLib/Error.h"
2019-09-12 14:18:13 +00:00
#include "YBaseLib/Log.h"
2019-09-09 07:01:26 +00:00
#include "imgui.h"
#include "imgui_impl_opengl3.h"
#include "imgui_impl_sdl.h"
#include "pse/digital_controller.h"
2019-09-29 15:07:38 +00:00
#include "pse/memory_card.h"
2019-09-09 07:01:26 +00:00
#include "pse/system.h"
#include <cinttypes>
#include <glad.h>
2019-09-12 14:18:13 +00:00
Log_SetChannel(SDLInterface);
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
SDLInterface::SDLInterface() = default;
2019-09-09 07:01:26 +00:00
SDLInterface::~SDLInterface()
{
2019-09-12 14:18:13 +00:00
if (m_gl_context)
2019-09-09 07:01:26 +00:00
{
2019-09-12 14:18:13 +00:00
if (m_display_vao != 0)
glDeleteVertexArrays(1, &m_display_vao);
m_display_program.Destroy();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_GL_MakeCurrent(nullptr, nullptr);
SDL_GL_DeleteContext(m_gl_context);
2019-09-09 07:01:26 +00:00
}
2019-09-12 14:18:13 +00:00
if (m_window)
SDL_DestroyWindow(m_window);
2019-09-09 07:01:26 +00:00
}
2019-09-12 14:18:13 +00:00
bool SDLInterface::CreateSDLWindow()
2019-09-09 07:01:26 +00:00
{
constexpr u32 DEFAULT_WINDOW_WIDTH = 900;
constexpr u32 DEFAULT_WINDOW_HEIGHT = 700;
// Create window.
2019-09-12 14:18:13 +00:00
constexpr u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL;
m_window = SDL_CreateWindow("Some idea to emulate a system starting with a P", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT, window_flags);
if (!m_window)
2019-09-09 07:01:26 +00:00
{
Panic("Failed to create window");
2019-09-12 14:18:13 +00:00
return false;
2019-09-09 07:01:26 +00:00
}
2019-09-12 14:18:13 +00:00
SDL_GetWindowSize(m_window, &m_window_width, &m_window_height);
return true;
}
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar* message, const void* userParam)
{
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH_KHR:
Log_InfoPrint(message);
break;
case GL_DEBUG_SEVERITY_MEDIUM_KHR:
Log_WarningPrint(message);
break;
case GL_DEBUG_SEVERITY_LOW_KHR:
Log_InfoPrintf(message);
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
2019-09-20 10:14:00 +00:00
// Log_DebugPrint(message);
break;
}
2019-09-12 14:18:13 +00:00
}
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
bool SDLInterface::CreateGLContext()
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
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_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
m_gl_context = SDL_GL_CreateContext(m_window);
if (!m_gl_context || SDL_GL_MakeCurrent(m_window, m_gl_context) != 0 || !gladLoadGL())
2019-09-09 07:01:26 +00:00
{
2019-09-12 14:18:13 +00:00
Panic("Failed to create GL context");
return false;
2019-09-09 07:01:26 +00:00
}
2019-09-12 14:18:13 +00:00
if (GLAD_GL_KHR_debug)
2019-09-09 07:01:26 +00:00
{
2019-09-12 14:18:13 +00:00
glad_glDebugMessageCallbackKHR(GLDebugCallback, nullptr);
2019-09-27 15:31:08 +00:00
// glEnable(GL_DEBUG_OUTPUT);
// glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
2019-09-09 07:01:26 +00:00
}
SDL_GL_SetSwapInterval(0);
2019-09-12 14:18:13 +00:00
return true;
}
bool SDLInterface::CreateImGuiContext()
{
2019-09-09 07:01:26 +00:00
ImGui::CreateContext();
ImGui::GetIO().IniFilename = nullptr;
2019-09-12 14:18:13 +00:00
if (!ImGui_ImplSDL2_InitForOpenGL(m_window, m_gl_context) || !ImGui_ImplOpenGL3_Init())
return false;
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(m_window);
ImGui::NewFrame();
return true;
}
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
bool SDLInterface::CreateGLResources()
{
static constexpr char fullscreen_quad_vertex_shader[] = R"(
#version 330 core
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
out vec2 v_tex0;
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
void main()
{
v_tex0 = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
gl_Position = vec4(v_tex0 * vec2(2.0f, -2.0f) + vec2(-1.0f, 1.0f), 0.0f, 1.0f);
gl_Position.y = -gl_Position.y;
}
)";
static constexpr char display_fragment_shader[] = R"(
#version 330 core
uniform sampler2D samp0;
uniform vec4 u_src_rect;
2019-09-12 14:18:13 +00:00
in vec2 v_tex0;
2019-09-14 06:43:39 +00:00
out vec4 o_col0;
2019-09-12 14:18:13 +00:00
void main()
{
vec2 coords = u_src_rect.xy + v_tex0 * u_src_rect.zw;
o_col0 = texture(samp0, coords);
2019-09-12 14:18:13 +00:00
}
)";
if (!m_display_program.Compile(fullscreen_quad_vertex_shader, display_fragment_shader))
return false;
2019-09-14 06:43:39 +00:00
m_display_program.BindFragData(0, "o_col0");
2019-09-12 14:18:13 +00:00
if (!m_display_program.Link())
return false;
m_display_program.Bind();
m_display_program.RegisterUniform("u_src_rect");
m_display_program.RegisterUniform("samp0");
m_display_program.Uniform1i(1, 0);
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
glGenVertexArrays(1, &m_display_vao);
return true;
2019-09-09 07:01:26 +00:00
}
2019-09-12 14:18:13 +00:00
std::unique_ptr<SDLInterface> SDLInterface::Create()
2019-09-09 07:01:26 +00:00
{
2019-09-12 14:18:13 +00:00
std::unique_ptr<SDLInterface> intf = std::make_unique<SDLInterface>();
if (!intf->CreateSDLWindow() || !intf->CreateGLContext() || !intf->CreateImGuiContext() || !intf->CreateGLResources())
return nullptr;
intf->m_controller = DigitalController::Create();
2019-09-29 15:07:38 +00:00
intf->m_memory_card = MemoryCard::Create();
2019-09-12 14:18:13 +00:00
return intf;
2019-09-09 07:01:26 +00:00
}
2019-09-14 10:28:47 +00:00
TinyString SDLInterface::GetSaveStateFilename(u32 index)
{
return TinyString::FromFormat("savestate_%u.bin", index);
}
2019-09-12 14:18:13 +00:00
2019-09-09 07:01:26 +00:00
void SDLInterface::ReportMessage(const char* message)
{
AddOSDMessage(message, 3.0f);
}
bool SDLInterface::IsWindowFullscreen() const
{
return ((SDL_GetWindowFlags(m_window) & SDL_WINDOW_FULLSCREEN) != 0);
}
static inline u32 SDLButtonToHostButton(u32 button)
{
// SDL left = 1, middle = 2, right = 3 :/
switch (button)
{
case 1:
return 0;
case 2:
return 2;
case 3:
return 1;
default:
return 0xFFFFFFFF;
}
}
bool SDLInterface::HandleSDLEvent(const SDL_Event* event)
{
if (PassEventToImGui(event))
return true;
switch (event->type)
{
case SDL_WINDOWEVENT:
{
if (event->window.event == SDL_WINDOWEVENT_RESIZED)
2019-09-12 14:18:13 +00:00
{
m_window_width = event->window.data1;
m_window_height = event->window.data2;
}
2019-09-09 07:01:26 +00:00
}
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
{
const bool pressed = (event->type == SDL_KEYDOWN);
switch (event->key.keysym.scancode)
{
case SDL_SCANCODE_KP_8:
case SDL_SCANCODE_I:
m_controller->SetButtonState(DigitalController::Button::Triangle, pressed);
return true;
case SDL_SCANCODE_KP_2:
case SDL_SCANCODE_K:
m_controller->SetButtonState(DigitalController::Button::Cross, pressed);
return true;
case SDL_SCANCODE_KP_4:
case SDL_SCANCODE_J:
m_controller->SetButtonState(DigitalController::Button::Square, pressed);
return true;
case SDL_SCANCODE_KP_6:
case SDL_SCANCODE_L:
m_controller->SetButtonState(DigitalController::Button::Circle, pressed);
return true;
case SDL_SCANCODE_W:
case SDL_SCANCODE_UP:
m_controller->SetButtonState(DigitalController::Button::Up, pressed);
return true;
case SDL_SCANCODE_S:
case SDL_SCANCODE_DOWN:
m_controller->SetButtonState(DigitalController::Button::Down, pressed);
return true;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
m_controller->SetButtonState(DigitalController::Button::Left, pressed);
return true;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
m_controller->SetButtonState(DigitalController::Button::Right, pressed);
return true;
case SDL_SCANCODE_Q:
m_controller->SetButtonState(DigitalController::Button::L1, pressed);
return true;
case SDL_SCANCODE_E:
m_controller->SetButtonState(DigitalController::Button::R1, pressed);
return true;
case SDL_SCANCODE_1:
m_controller->SetButtonState(DigitalController::Button::L2, pressed);
return true;
case SDL_SCANCODE_3:
m_controller->SetButtonState(DigitalController::Button::R2, pressed);
return true;
case SDL_SCANCODE_RETURN:
m_controller->SetButtonState(DigitalController::Button::Start, pressed);
return true;
case SDL_SCANCODE_BACKSPACE:
m_controller->SetButtonState(DigitalController::Button::Select, pressed);
return true;
case SDL_SCANCODE_F1:
case SDL_SCANCODE_F2:
case SDL_SCANCODE_F3:
case SDL_SCANCODE_F4:
case SDL_SCANCODE_F5:
case SDL_SCANCODE_F6:
case SDL_SCANCODE_F7:
case SDL_SCANCODE_F8:
2019-09-29 15:07:38 +00:00
{
if (!pressed)
{
2019-09-29 15:07:38 +00:00
auto filename = GetSaveStateFilename(event->key.keysym.scancode - SDL_SCANCODE_F1 + 1);
if (event->key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
SaveState(filename);
else
LoadState(filename);
}
2019-09-29 15:07:38 +00:00
}
break;
2019-09-27 15:31:08 +00:00
case SDL_SCANCODE_TAB:
SDL_GL_SetSwapInterval(pressed ? 0 : 1);
break;
default:
break;
}
}
break;
2019-09-09 07:01:26 +00:00
case SDL_QUIT:
m_running = false;
break;
}
return false;
}
bool SDLInterface::PassEventToImGui(const SDL_Event* event)
{
ImGuiIO& io = ImGui::GetIO();
switch (event->type)
{
case SDL_MOUSEWHEEL:
{
if (event->wheel.x > 0)
io.MouseWheelH += 1;
if (event->wheel.x < 0)
io.MouseWheelH -= 1;
if (event->wheel.y > 0)
io.MouseWheel += 1;
if (event->wheel.y < 0)
io.MouseWheel -= 1;
return io.WantCaptureMouse;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (event->button.button == SDL_BUTTON_LEFT)
io.MouseDown[0] = down;
if (event->button.button == SDL_BUTTON_RIGHT)
io.MouseDown[1] = down;
if (event->button.button == SDL_BUTTON_MIDDLE)
io.MouseDown[2] = down;
return io.WantCaptureMouse;
}
case SDL_MOUSEMOTION:
{
io.MousePos.x = float(event->motion.x);
io.MousePos.y = float(event->motion.y);
return io.WantCaptureMouse;
}
case SDL_TEXTINPUT:
{
io.AddInputCharactersUTF8(event->text.text);
return io.WantCaptureKeyboard;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
int key = event->key.keysym.scancode;
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
io.KeysDown[key] = (event->type == SDL_KEYDOWN);
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
return io.WantCaptureKeyboard;
}
}
return false;
}
void SDLInterface::Render()
{
2019-09-12 14:18:13 +00:00
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_SCISSOR_TEST);
2019-09-12 14:18:13 +00:00
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
RenderDisplay();
2019-09-09 07:01:26 +00:00
RenderImGui();
2019-09-12 14:18:13 +00:00
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
SDL_GL_SwapWindow(m_window);
2019-09-09 07:01:26 +00:00
2019-09-12 14:18:13 +00:00
ImGui_ImplSDL2_NewFrame(m_window);
ImGui_ImplOpenGL3_NewFrame();
2019-09-09 07:01:26 +00:00
ImGui::NewFrame();
}
static std::tuple<int, int, int, int> CalculateDrawRect(int window_width, int window_height, float display_ratio)
{
const float window_ratio = float(window_width) / float(window_height);
int left, top, width, height;
if (window_ratio >= display_ratio)
{
width = static_cast<int>(float(window_height) * display_ratio);
height = static_cast<int>(window_height);
left = (window_width - width) / 2;
top = 0;
}
else
{
width = static_cast<int>(window_width);
height = static_cast<int>(float(window_width) / display_ratio);
left = 0;
top = (window_height - height) / 2;
}
return std::tie(left, top, width, height);
}
2019-09-12 14:18:13 +00:00
void SDLInterface::RenderDisplay()
{
if (!m_display_texture)
return;
// - 20 for main menu padding
const auto [vp_left, vp_top, vp_width, vp_height] =
CalculateDrawRect(m_window_width, std::max(m_window_height - 20, 1), m_display_aspect_ratio);
glViewport(vp_left, m_window_height - (20 + vp_top) - vp_height, vp_width, vp_height);
2019-09-18 14:55:06 +00:00
glDisable(GL_BLEND);
2019-09-12 14:18:13 +00:00
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_SCISSOR_TEST);
2019-09-12 14:18:13 +00:00
glDepthMask(GL_FALSE);
m_display_program.Bind();
m_display_program.Uniform4f(
0, static_cast<float>(m_display_texture_offset_x) / static_cast<float>(m_display_texture->GetWidth()),
static_cast<float>(m_display_texture_offset_y) / static_cast<float>(m_display_texture->GetHeight()),
static_cast<float>(m_display_texture_width) / static_cast<float>(m_display_texture->GetWidth()),
static_cast<float>(m_display_texture_height) / static_cast<float>(m_display_texture->GetHeight()));
2019-09-12 14:18:13 +00:00
m_display_texture->Bind();
glBindVertexArray(m_display_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
2019-09-09 07:01:26 +00:00
void SDLInterface::RenderImGui()
{
RenderMainMenuBar();
2019-09-26 04:03:32 +00:00
m_system->RenderUI();
2019-09-26 11:44:02 +00:00
RenderOSDMessages();
2019-09-09 07:01:26 +00:00
ImGui::Render();
}
void SDLInterface::RenderMainMenuBar()
{
if (!ImGui::BeginMainMenuBar())
return;
if (ImGui::BeginMenu("System"))
{
if (ImGui::MenuItem("Reset"))
m_system->Reset();
ImGui::Separator();
#if 0
if (ImGui::MenuItem("Enable Speed Limiter", nullptr, IsSpeedLimiterEnabled()))
SetSpeedLimiterEnabled(!IsSpeedLimiterEnabled());
#endif
ImGui::Separator();
if (ImGui::BeginMenu("Load State"))
{
for (u32 i = 1; i <= 8; i++)
{
if (ImGui::MenuItem(TinyString::FromFormat("State %u", i).GetCharArray()))
DoLoadState(i);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Save State"))
{
for (u32 i = 1; i <= 8; i++)
{
if (ImGui::MenuItem(TinyString::FromFormat("State %u", i).GetCharArray()))
DoSaveState(i);
}
ImGui::EndMenu();
}
if (ImGui::MenuItem("Exit"))
m_running = false;
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View"))
{
if (ImGui::MenuItem("Fullscreen", nullptr, IsWindowFullscreen()))
SDL_SetWindowFullscreen(m_window, IsWindowFullscreen() ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
ImGui::EndMenu();
}
ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 170.0f);
ImGui::Text("FPS: %.2f", m_fps);
ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 80.0f);
ImGui::Text("VPS: %.2f", m_vps);
2019-09-09 07:01:26 +00:00
ImGui::EndMainMenuBar();
}
void SDLInterface::AddOSDMessage(const char* message, float duration /*= 2.0f*/)
{
OSDMessage msg;
msg.text = message;
msg.duration = duration;
std::unique_lock<std::mutex> lock(m_osd_messages_lock);
m_osd_messages.push_back(std::move(msg));
}
void SDLInterface::SetDisplayTexture(GL::Texture* texture, u32 offset_x, u32 offset_y, u32 width, u32 height,
float aspect_ratio)
2019-09-12 14:18:13 +00:00
{
m_display_texture = texture;
m_display_texture_offset_x = offset_x;
m_display_texture_offset_y = offset_y;
2019-09-12 14:18:13 +00:00
m_display_texture_width = width;
m_display_texture_height = height;
m_display_aspect_ratio = aspect_ratio;
2019-09-12 14:18:13 +00:00
m_display_texture_changed = true;
}
2019-09-09 07:01:26 +00:00
void SDLInterface::RenderOSDMessages()
{
constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing;
std::unique_lock<std::mutex> lock(m_osd_messages_lock);
auto iter = m_osd_messages.begin();
float position_x = 10.0f;
float position_y = 10.0f + 20.0f;
u32 index = 0;
while (iter != m_osd_messages.end())
{
const OSDMessage& msg = *iter;
const double time = msg.time.GetTimeSeconds();
const float time_remaining = static_cast<float>(msg.duration - time);
if (time_remaining <= 0.0f)
{
iter = m_osd_messages.erase(iter);
continue;
}
const float opacity = std::min(time_remaining, 1.0f);
ImGui::SetNextWindowPos(ImVec2(position_x, position_y));
ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, opacity);
if (ImGui::Begin(SmallString::FromFormat("osd_%u", index++), nullptr, window_flags))
{
ImGui::TextUnformatted(msg.text);
position_y += ImGui::GetWindowSize().y + (4.0f * ImGui::GetIO().DisplayFramebufferScale.x);
}
ImGui::End();
ImGui::PopStyleVar();
++iter;
}
}
void SDLInterface::DoLoadState(u32 index)
{
2019-09-14 10:28:47 +00:00
LoadState(GetSaveStateFilename(index));
2019-09-09 07:01:26 +00:00
}
void SDLInterface::DoSaveState(u32 index)
{
2019-09-14 10:28:47 +00:00
SaveState(GetSaveStateFilename(index));
2019-09-09 07:01:26 +00:00
}
void SDLInterface::Run()
{
2019-09-29 15:07:38 +00:00
m_system->SetController(0, m_controller);
m_system->SetMemoryCard(0, m_memory_card);
2019-09-09 07:01:26 +00:00
while (m_running)
{
for (;;)
{
SDL_Event ev;
if (SDL_PollEvent(&ev))
HandleSDLEvent(&ev);
else
break;
}
m_system->RunFrame();
2019-09-26 11:44:02 +00:00
2019-09-27 15:31:08 +00:00
Render();
2019-09-26 11:44:02 +00:00
// update fps counter
{
const double time = m_fps_timer.GetTimeSeconds();
if (time >= 0.25f)
2019-09-26 11:44:02 +00:00
{
m_vps = static_cast<float>(static_cast<double>(m_system->GetFrameNumber() - m_last_frame_number) / time);
m_last_frame_number = m_system->GetFrameNumber();
m_fps = static_cast<float>(
static_cast<double>(m_system->GetInternalFrameNumber() - m_last_internal_frame_number) / time);
m_last_internal_frame_number = m_system->GetInternalFrameNumber();
2019-09-26 11:44:02 +00:00
m_fps_timer.Reset();
}
}
2019-09-09 07:01:26 +00:00
}
}