Add go2 frontend

This commit is contained in:
Connor McLaughlin 2020-10-22 22:43:57 +10:00
parent 14fbdd3657
commit 34107dd573
13 changed files with 1165 additions and 1 deletions

View File

@ -21,6 +21,7 @@ if(NOT ANDROID)
option(BUILD_SDL_FRONTEND "Build the SDL frontend" ON)
option(BUILD_QT_FRONTEND "Build the Qt frontend" ON)
option(BUILD_LIBRETRO_CORE "Build a libretro core" OFF)
option(BUILD_GO2_FRONTEND "Build the ODROID-GO Advance frontend" OFF)
option(ENABLE_DISCORD_PRESENCE "Build with Discord Rich Presence support" ON)
option(USE_SDL2 "Link with SDL2 for controller support" ON)
endif()

View File

@ -35,3 +35,6 @@ if(${CPU_ARCH} STREQUAL "aarch64")
add_subdirectory(vixl)
endif()
if(BUILD_OGA_FRONTEND)
add_subdirectory(libgo2)
endif()

View File

@ -13,7 +13,7 @@ if(NOT BUILD_LIBRETRO_CORE)
endif()
endif()
if(ANDROID OR BUILD_SDL_FRONTEND OR BUILD_QT_FRONTEND OR BUILD_LIBRETRO_CORE)
if(ANDROID OR BUILD_SDL_FRONTEND OR BUILD_QT_FRONTEND OR BUILD_LIBRETRO_CORE OR BUILD_GO2_FRONTEND)
add_subdirectory(frontend-common)
endif()
@ -29,3 +29,6 @@ if(BUILD_LIBRETRO_CORE)
add_subdirectory(duckstation-libretro)
endif()
if(BUILD_GO2_FRONTEND)
add_subdirectory(duckstation-go2)
endif()

View File

@ -0,0 +1,16 @@
find_package(EGL REQUIRED)
add_executable(duckstation-go2
go2_controller_interface.cpp
go2_controller_interface.h
go2_host_display.cpp
go2_host_display.h
go2_host_interface.cpp
go2_host_interface.h
go2_opengl_host_display.cpp
go2_opengl_host_display.h
main.cpp
)
target_link_libraries(duckstation-go2 PRIVATE core common imgui glad frontend-common scmversion vulkan-loader go2 EGL::EGL)

View File

@ -0,0 +1,222 @@
#include "go2_controller_interface.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "core/controller.h"
#include "core/host_interface.h"
#include "core/system.h"
#include <cmath>
Log_SetChannel(Go2ControllerInterface);
Go2ControllerInterface::Go2ControllerInterface() = default;
Go2ControllerInterface::~Go2ControllerInterface()
{
if (m_input_state)
go2_input_state_destroy(m_input_state);
if (m_input)
go2_input_destroy(m_input);
}
ControllerInterface::Backend Go2ControllerInterface::GetBackend() const
{
return ControllerInterface::Backend::None;
}
bool Go2ControllerInterface::Initialize(CommonHostInterface* host_interface)
{
m_input = go2_input_create();
m_input_state = go2_input_state_create();
if (!m_input || !m_input_state)
return false;
if (!ControllerInterface::Initialize(host_interface))
return false;
return true;
}
void Go2ControllerInterface::Shutdown()
{
ControllerInterface::Shutdown();
}
void Go2ControllerInterface::PollEvents()
{
go2_input_state_read(m_input, m_input_state);
CheckForStateChanges();
}
void Go2ControllerInterface::CheckForStateChanges()
{
for (u32 i = 0; i < NUM_BUTTONS; i++)
{
const bool new_state = go2_input_state_button_get(m_input_state, static_cast<go2_input_button_t>(i)) == ButtonState_Pressed;
if (m_last_button_state[i] == new_state)
continue;
HandleButtonEvent(i, new_state);
m_last_button_state[i] = new_state;
}
go2_thumb_t thumb = go2_input_state_thumbstick_get(m_input_state, Go2InputThumbstick_Left);
if (thumb.x != m_last_axis_state[0])
{
HandleAxisEvent(Axis::X, thumb.x);
m_last_axis_state[0] = thumb.x;
}
if (thumb.y != m_last_axis_state[1])
{
HandleAxisEvent(Axis::Y, thumb.y);
m_last_axis_state[1] = thumb.y;
}
}
void Go2ControllerInterface::ClearBindings()
{
for (AxisCallback& ac : m_axis_mapping)
ac = {};
for (ButtonCallback& bc : m_button_mapping)
bc = {};
}
bool Go2ControllerInterface::BindControllerAxis(int controller_index, int axis_number, AxisCallback callback)
{
if (controller_index != 0)
return false;
if (axis_number < 0 || axis_number >= NUM_AXISES)
return false;
m_axis_mapping[axis_number] = std::move(callback);
return true;
}
bool Go2ControllerInterface::BindControllerButton(int controller_index, int button_number, ButtonCallback callback)
{
if (controller_index != 0)
return false;
if (button_number < 0 || button_number >= NUM_BUTTONS)
return false;
m_button_mapping[button_number] = std::move(callback);
return true;
}
bool Go2ControllerInterface::BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
ButtonCallback callback)
{
if (controller_index != 0)
return false;
if (axis_number < 0 || axis_number >= NUM_AXISES)
return false;
m_axis_button_mapping[axis_number][BoolToUInt8(direction)] = std::move(callback);
return true;
}
bool Go2ControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number,
AxisCallback callback)
{
if (controller_index != 0)
return false;
if (button_number < 0 || button_number >= NUM_BUTTONS)
return false;
m_button_axis_mapping[button_number] = std::move(callback);
return true;
}
bool Go2ControllerInterface::HandleAxisEvent(Axis axis, float value)
{
Log_DevPrintf("axis %u %f", static_cast<u32>(axis), value);
if (DoEventHook(Hook::Type::Axis, 0, static_cast<u32>(axis), value))
return true;
const AxisCallback& cb = m_axis_mapping[static_cast<u32>(axis)];
if (cb)
{
// Apply axis scaling only when controller axis is mapped to an axis
cb(std::clamp(m_axis_scale * value, -1.0f, 1.0f));
return true;
}
// set the other direction to false so large movements don't leave the opposite on
const bool outside_deadzone = (std::abs(value) >= m_deadzone);
const bool positive = (value >= 0.0f);
const ButtonCallback& other_button_cb =
m_axis_button_mapping[static_cast<u32>(axis)][BoolToUInt8(!positive)];
const ButtonCallback& button_cb =
m_axis_button_mapping[static_cast<u32>(axis)][BoolToUInt8(positive)];
if (button_cb)
{
button_cb(outside_deadzone);
if (other_button_cb)
other_button_cb(false);
return true;
}
else if (other_button_cb)
{
other_button_cb(false);
return true;
}
else
{
return false;
}
}
bool Go2ControllerInterface::HandleButtonEvent(u32 button, bool pressed)
{
Log_DevPrintf("button %u %s", button, pressed ? "pressed" : "released");
if (DoEventHook(Hook::Type::Button, 0, button, pressed ? 1.0f : 0.0f))
return true;
const ButtonCallback& cb = m_button_mapping[button];
if (cb)
{
cb(pressed);
return true;
}
// Assume a half-axis, i.e. in 0..1 range
const AxisCallback& axis_cb = m_button_axis_mapping[button];
if (axis_cb)
{
axis_cb(pressed ? 1.0f : 0.0f);
}
return true;
}
u32 Go2ControllerInterface::GetControllerRumbleMotorCount(int controller_index)
{
return 0;
}
void Go2ControllerInterface::SetControllerRumbleStrength(int controller_index, const float* strengths,
u32 num_motors)
{
}
bool Go2ControllerInterface::SetControllerAxisScale(int controller_index, float scale /* = 1.00f */)
{
if (controller_index != 0)
return false;
m_axis_scale = std::clamp(std::abs(scale), 0.01f, 1.50f);
Log_InfoPrintf("Controller %d axis scale set to %f", controller_index, m_axis_scale);
return true;
}
bool Go2ControllerInterface::SetControllerDeadzone(int controller_index, float size /* = 0.25f */)
{
if (controller_index != 0)
return false;
m_deadzone = std::clamp(std::abs(size), 0.01f, 0.99f);
Log_InfoPrintf("Controller %d deadzone size set to %f", controller_index, m_deadzone);
return true;
}

View File

@ -0,0 +1,72 @@
#pragma once
#include "frontend-common/controller_interface.h"
#include "core/types.h"
#include <go2/input.h>
#include <array>
#include <functional>
#include <mutex>
#include <vector>
class Go2ControllerInterface final : public ControllerInterface
{
public:
Go2ControllerInterface();
~Go2ControllerInterface() override;
Backend GetBackend() const override;
bool Initialize(CommonHostInterface* host_interface) override;
void Shutdown() override;
// Removes all bindings. Call before setting new bindings.
void ClearBindings() override;
// Binding to events. If a binding for this axis/button already exists, returns false.
bool BindControllerAxis(int controller_index, int axis_number, AxisCallback callback) override;
bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override;
bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
ButtonCallback callback) override;
bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override;
// Changing rumble strength.
u32 GetControllerRumbleMotorCount(int controller_index) override;
void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) override;
// Set scaling that will be applied on axis-to-axis mappings
bool SetControllerAxisScale(int controller_index, float scale = 1.00f) override;
// Set deadzone that will be applied on axis-to-button mappings
bool SetControllerDeadzone(int controller_index, float size = 0.25f) override;
void PollEvents() override;
private:
enum : u32
{
NUM_BUTTONS = Go2InputButton_TriggerRight + 1,
NUM_AXISES = 2
};
enum class Axis : u32
{
X,
Y
};
void CheckForStateChanges();
bool HandleAxisEvent(Axis axis, float value);
bool HandleButtonEvent(u32 button, bool pressed);
go2_input_t* m_input = nullptr;
go2_input_state_t* m_input_state = nullptr;
std::array<bool, NUM_BUTTONS> m_last_button_state = {};
std::array<float, NUM_AXISES> m_last_axis_state = {};
// Scaling value of 1.30f to 1.40f recommended when using recent controllers
float m_axis_scale = 1.00f;
float m_deadzone = 0.25f;
std::array<AxisCallback, NUM_AXISES> m_axis_mapping;
std::array<ButtonCallback, NUM_BUTTONS> m_button_mapping;
std::array<std::array<ButtonCallback, 2>, NUM_AXISES> m_axis_button_mapping;
std::array<AxisCallback, NUM_BUTTONS> m_button_axis_mapping;
};

View File

@ -0,0 +1,221 @@
#include "go2_host_display.h"
#include "common/assert.h"
#include "common/log.h"
#include "imgui.h"
#include <array>
#include <drm/drm_fourcc.h>
#include <tuple>
Log_SetChannel(Go2HostDisplay);
Go2HostDisplay::Go2HostDisplay() = default;
Go2HostDisplay::~Go2HostDisplay()
{
Assert(!m_display && !m_surface && !m_presenter);
}
HostDisplay::RenderAPI Go2HostDisplay::GetRenderAPI() const
{
return RenderAPI::None;
}
void* Go2HostDisplay::GetRenderDevice() const
{
return nullptr;
}
void* Go2HostDisplay::GetRenderContext() const
{
return nullptr;
}
bool Go2HostDisplay::HasRenderDevice() const
{
return true;
}
bool Go2HostDisplay::HasRenderSurface() const
{
return true;
}
bool Go2HostDisplay::MakeRenderContextCurrent()
{
return true;
}
bool Go2HostDisplay::DoneRenderContextCurrent()
{
return true;
}
void Go2HostDisplay::DestroyRenderSurface()
{
// noop
}
bool Go2HostDisplay::ChangeRenderWindow(const WindowInfo& wi)
{
m_window_info = wi;
return true;
}
bool Go2HostDisplay::CreateResources()
{
return true;
}
void Go2HostDisplay::DestroyResources()
{
// noop
}
bool Go2HostDisplay::SetPostProcessingChain(const std::string_view& config)
{
return false;
}
void Go2HostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height)
{
m_window_info.surface_width = static_cast<u32>(new_window_width);
m_window_info.surface_height = static_cast<u32>(new_window_height);
}
std::unique_ptr<HostDisplayTexture> Go2HostDisplay::CreateTexture(u32 width, u32 height, const void* data,
u32 data_stride, bool dynamic /*= false*/)
{
return nullptr;
}
void Go2HostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data,
u32 data_stride)
{
// noop
}
bool Go2HostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data,
u32 out_data_stride)
{
return false;
}
void Go2HostDisplay::SetVSync(bool enabled)
{
// noop
}
bool Go2HostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device)
{
m_display = go2_display_create();
if (!m_display)
return false;
m_presenter = go2_presenter_create(m_display, DRM_FORMAT_RGB565, 0xff000000);
if (!m_presenter)
return false;
m_window_info = wi;
m_window_info.surface_width = go2_display_width_get(m_display);
m_window_info.surface_height = go2_display_height_get(m_display);
ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_info.surface_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_info.surface_height);
unsigned char* pixels;
int width, height;
ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
return true;
}
bool Go2HostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device)
{
return true;
}
void Go2HostDisplay::DestroyRenderDevice()
{
if (m_surface)
{
go2_surface_destroy(m_surface);
m_surface = nullptr;
}
if (m_presenter)
{
go2_presenter_destroy(m_presenter);
m_presenter = nullptr;
}
if (m_display)
{
go2_display_destroy(m_display);
m_display = nullptr;
}
}
static constexpr std::array<int, static_cast<u32>(HostDisplayPixelFormat::Count)> s_display_pixel_format_mapping = {
{DRM_FORMAT_INVALID, DRM_FORMAT_RGBA8888, DRM_FORMAT_INVALID, DRM_FORMAT_RGB565, DRM_FORMAT_RGBA5551}};
bool Go2HostDisplay::CheckSurface(u32 width, u32 height, HostDisplayPixelFormat format)
{
if (width <= m_surface_width && height <= m_surface_height && format == m_surface_format)
return true;
if (m_surface)
go2_surface_destroy(m_surface);
m_surface = go2_surface_create(m_display, width, height, s_display_pixel_format_mapping[static_cast<u32>(format)]);
if (!m_surface)
Panic("Failed to create surface");
m_surface_width = width;
m_surface_height = height;
m_surface_format = format;
return false;
}
bool Go2HostDisplay::SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const
{
return (s_display_pixel_format_mapping[static_cast<u32>(format)] != DRM_FORMAT_INVALID);
}
bool Go2HostDisplay::BeginSetDisplayPixels(HostDisplayPixelFormat format, u32 width, u32 height, void** out_buffer,
u32* out_pitch)
{
if (!CheckSurface(width, height, format))
return false;
void* map = go2_surface_map(m_surface);
if (!map)
return false;
*out_buffer = map;
*out_pitch = static_cast<u32>(go2_surface_stride_get(m_surface));
SetDisplayTexture(m_surface, format, m_surface_width, m_surface_height, 0, 0, width, height);
return true;
}
void Go2HostDisplay::EndSetDisplayPixels()
{
go2_surface_unmap(m_surface);
}
bool Go2HostDisplay::Render()
{
ImGui::Render();
if (HasDisplayTexture())
{
s32 left, top, width, height, left_padding, top_padding;
CalculateDrawRect(m_window_info.surface_height, m_window_info.surface_width,
static_cast<float>(m_window_info.surface_height) /
static_cast<float>(m_window_info.surface_width),
&left, &top, &width, &height, &left_padding, &top_padding, nullptr, nullptr, true);
go2_presenter_post(m_presenter, static_cast<go2_surface*>(m_display_texture_handle), m_display_texture_view_x,
m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height,
top + top_padding, left + left_padding, height, width, GO2_ROTATION_DEGREES_270);
}
return true;
}

View File

@ -0,0 +1,60 @@
#pragma once
#include "core/host_display.h"
#include <go2/display.h>
#include <memory>
#include <string>
class Go2HostDisplay final : public HostDisplay
{
public:
Go2HostDisplay();
~Go2HostDisplay();
RenderAPI GetRenderAPI() const override;
void* GetRenderDevice() const override;
void* GetRenderContext() const override;
bool HasRenderDevice() const override;
bool HasRenderSurface() const override;
bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override;
bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) override;
void DestroyRenderDevice() override;
bool MakeRenderContextCurrent() override;
bool DoneRenderContextCurrent() override;
void DestroyRenderSurface() override;
bool ChangeRenderWindow(const WindowInfo& wi) override;
void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override;
bool CreateResources() override;
void DestroyResources() override;
bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* data, u32 data_stride,
bool dynamic = false) override;
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data,
u32 data_stride) override;
bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data,
u32 out_data_stride) override;
bool SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const override;
bool BeginSetDisplayPixels(HostDisplayPixelFormat format, u32 width, u32 height, void** out_buffer,
u32* out_pitch) override;
void EndSetDisplayPixels() override;
void SetVSync(bool enabled) override;
bool Render() override;
private:
bool CheckSurface(u32 width, u32 height, HostDisplayPixelFormat format);
go2_display_t* m_display = nullptr;
go2_surface_t* m_surface = nullptr;
go2_presenter_t* m_presenter = nullptr;
u32 m_surface_width = 0;
u32 m_surface_height = 0;
HostDisplayPixelFormat m_surface_format = HostDisplayPixelFormat::Unknown;
};

View File

@ -0,0 +1,289 @@
#include "go2_host_interface.h"
#include "common/assert.h"
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/controller.h"
#include "core/gpu.h"
#include "core/host_display.h"
#include "core/system.h"
#include "frontend-common/icon.h"
#include "frontend-common/imgui_styles.h"
#include "frontend-common/ini_settings_interface.h"
#include "go2_controller_interface.h"
#include "go2_host_display.h"
#include "go2_opengl_host_display.h"
#include "scmversion/scmversion.h"
#include <cinttypes>
#include <cmath>
#include <imgui.h>
#include <imgui_stdlib.h>
Log_SetChannel(Go2HostInterface);
Go2HostInterface::Go2HostInterface() = default;
Go2HostInterface::~Go2HostInterface() = default;
const char* Go2HostInterface::GetFrontendName() const
{
return "DuckStation ODROID-Go Advance Frontend";
}
ALWAYS_INLINE static TinyString GetWindowTitle()
{
return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str);
}
void Go2HostInterface::CreateImGuiContext()
{
const float framebuffer_scale = 1.0f;
ImGui::CreateContext();
ImGui::GetIO().IniFilename = nullptr;
// ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
ImGui::GetIO().DisplayFramebufferScale.x = framebuffer_scale;
ImGui::GetIO().DisplayFramebufferScale.y = framebuffer_scale;
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
ImGui::StyleColorsDarker();
ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale);
}
bool Go2HostInterface::AcquireHostDisplay()
{
WindowInfo wi;
CreateImGuiContext();
if (g_settings.gpu_renderer == GPURenderer::HardwareOpenGL)
m_display = std::make_unique<Go2OpenGLHostDisplay>();
else
m_display = std::make_unique<Go2HostDisplay>();
if (!m_display->CreateRenderDevice(wi, g_settings.gpu_adapter, g_settings.gpu_use_debug_device) ||
!m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device))
{
ReportError("Failed to create/initialize display render device");
m_display.reset();
ImGui::DestroyContext();
return false;
}
ImGui::NewFrame();
return true;
}
void Go2HostInterface::ReleaseHostDisplay()
{
m_display->DestroyRenderDevice();
m_display.reset();
ImGui::DestroyContext();
}
std::optional<CommonHostInterface::HostKeyCode> Go2HostInterface::GetHostKeyCode(const std::string_view key_code) const
{
return std::nullopt;
}
void Go2HostInterface::UpdateInputMap()
{
CommonHostInterface::UpdateInputMap(*m_settings_interface.get());
}
void Go2HostInterface::OnRunningGameChanged()
{
CommonHostInterface::OnRunningGameChanged();
Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(true);
CommonHostInterface::FixIncompatibleSettings(true);
CheckForSettingsChanges(old_settings);
}
void Go2HostInterface::OnSystemPerformanceCountersUpdated()
{
HostInterface::OnSystemPerformanceCountersUpdated();
Log_InfoPrintf("FPS: %.2f VPS: %.2f Average: %.2fms Worst: %.2fms", System::GetFPS(), System::GetVPS(),
System::GetAverageFrameTime(), System::GetWorstFrameTime());
}
void Go2HostInterface::RequestExit()
{
Log_ErrorPrintf("TODO");
}
void Go2HostInterface::PollAndUpdate()
{
CommonHostInterface::PollAndUpdate();
if (m_controller_interface)
m_controller_interface->PollEvents();
}
bool Go2HostInterface::IsFullscreen() const
{
return true;
}
bool Go2HostInterface::SetFullscreen(bool enabled)
{
return false;
}
std::unique_ptr<Go2HostInterface> Go2HostInterface::Create()
{
return std::make_unique<Go2HostInterface>();
}
bool Go2HostInterface::Initialize()
{
if (!CommonHostInterface::Initialize())
return false;
// Change to the user directory so that all default/relative paths in the config are after this.
if (!FileSystem::SetWorkingDirectory(m_user_directory.c_str()))
Log_ErrorPrintf("Failed to set working directory to '%s'", m_user_directory.c_str());
// process events to pick up controllers before updating input map
UpdateInputMap();
return true;
}
void Go2HostInterface::Shutdown()
{
CommonHostInterface::Shutdown();
}
std::string Go2HostInterface::GetStringSettingValue(const char* section, const char* key,
const char* default_value /*= ""*/)
{
return m_settings_interface->GetStringValue(section, key, default_value);
}
bool Go2HostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /* = false */)
{
return m_settings_interface->GetBoolValue(section, key, default_value);
}
int Go2HostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /* = 0 */)
{
return m_settings_interface->GetIntValue(section, key, default_value);
}
float Go2HostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /* = 0.0f */)
{
return m_settings_interface->GetFloatValue(section, key, default_value);
}
void Go2HostInterface::LoadSettings()
{
// Settings need to be loaded prior to creating the window for OpenGL bits.
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
if (!CommonHostInterface::CheckSettings(*m_settings_interface.get()))
m_settings_interface->Save();
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::FixIncompatibleSettings(false);
}
void Go2HostInterface::SetDefaultSettings(SettingsInterface& si)
{
CommonHostInterface::SetDefaultSettings(si);
si.SetBoolValue("Main", "ConfirmPowerOff", false);
si.SetStringValue("GPU", "Renderer", "Software");
si.SetStringValue("Controller1", "Type", "AnalogController");
si.SetBoolValue("Controller1", "AutoEnableAnalog", true);
si.SetStringValue("Controller1", "ButtonUp", "Controller0/Button0");
si.SetStringValue("Controller1", "ButtonDown", "Controller0/Button1");
si.SetStringValue("Controller1", "ButtonLeft", "Controller0/Button2");
si.SetStringValue("Controller1", "ButtonRight", "Controller0/Button3");
si.SetStringValue("Controller1", "ButtonSelect", "Controller0/Button10");
si.SetStringValue("Controller1", "ButtonStart", "Controller0/Button11");
si.SetStringValue("Controller1", "ButtonTriangle", "Controller0/Button6");
si.SetStringValue("Controller1", "ButtonCross", "Controller0/Button5");
si.SetStringValue("Controller1", "ButtonSquare", "Controller0/Button7");
si.SetStringValue("Controller1", "ButtonCircle", "Controller0/Button4");
si.SetStringValue("Controller1", "ButtonL1", "Controller0/Button16");
si.SetStringValue("Controller1", "ButtonL2", "Controller0/Button14");
si.SetStringValue("Controller1", "ButtonR1", "Controller0/Button17");
si.SetStringValue("Controller1", "ButtonR2", "Controller0/Button15");
si.SetStringValue("Controller1", "AxisLeftX", "Controller0/Axis0");
si.SetStringValue("Controller1", "AxisLeftY", "Controller0/Axis1");
si.SetStringValue("Hotkeys", "PowerOff", "Controller0/Button8");
si.SetStringValue("Logging", "LogLevel", "Info");
si.SetBoolValue("Logging", "LogToConsole", true);
si.SetBoolValue("Display", "ShowOSDMessages", true);
si.SetBoolValue("Display", "ShowFPS", false);
si.SetBoolValue("Display", "ShowVPS", false);
si.SetBoolValue("Display", "ShowSpeed", false);
si.SetBoolValue("Display", "ShowResolution", false);
si.SetBoolValue("BIOS", "PatchFastBoot", true);
si.SetStringValue("BIOS", "SearchDirectory", "/roms/bios");
}
void Go2HostInterface::UpdateControllerInterface()
{
if (m_controller_interface)
return;
m_controller_interface = std::make_unique<Go2ControllerInterface>();
if (!m_controller_interface->Initialize(this))
{
Log_WarningPrintf("Failed to initialize go2 controller interface");
m_controller_interface.reset();
}
}
void Go2HostInterface::Run()
{
while (!m_quit_request)
{
PollAndUpdate();
if (System::IsRunning())
{
System::RunFrame();
UpdateControllerRumble();
if (m_frame_step_request)
{
m_frame_step_request = false;
PauseSystem(true);
}
}
// rendering
{
DrawImGuiWindows();
m_display->Render();
ImGui::NewFrame();
if (System::IsRunning())
{
System::UpdatePerformanceCounters();
if (m_speed_limiter_enabled)
System::Throttle();
}
}
}
// Save state on exit so it can be resumed
if (!System::IsShutdown())
{
if (g_settings.save_state_on_exit)
SaveResumeSaveState();
DestroySystem();
}
}

View File

@ -0,0 +1,64 @@
#pragma once
#include "common/gl/program.h"
#include "common/gl/texture.h"
#include "core/host_display.h"
#include "core/host_interface.h"
#include "frontend-common/common_host_interface.h"
#include <array>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <string>
class AudioStream;
class INISettingsInterface;
class Go2HostInterface final : public CommonHostInterface
{
public:
Go2HostInterface();
~Go2HostInterface();
static std::unique_ptr<Go2HostInterface> Create();
const char* GetFrontendName() const override;
bool Initialize() override;
void Shutdown() override;
std::string GetStringSettingValue(const char* section, const char* key, const char* default_value = "") override;
bool GetBoolSettingValue(const char* section, const char* key, bool default_value = false) override;
int GetIntSettingValue(const char* section, const char* key, int default_value = 0) override;
float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override;
void Run();
protected:
void LoadSettings() override;
void SetDefaultSettings(SettingsInterface &si) override;
void UpdateControllerInterface() override;
bool AcquireHostDisplay() override;
void ReleaseHostDisplay() override;
void OnRunningGameChanged() override;
void OnSystemPerformanceCountersUpdated() override;
void RequestExit() override;
void PollAndUpdate() override;
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
void UpdateInputMap() override;
private:
void CreateImGuiContext();
bool IsFullscreen() const override;
bool SetFullscreen(bool enabled) override;
std::unique_ptr<INISettingsInterface> m_settings_interface;
bool m_quit_request = false;
};

View File

@ -0,0 +1,142 @@
#include "common/assert.h"
#include "common/log.h"
#include "go2_opengl_host_display.h"
#include "imgui.h"
#include <EGL/egl.h>
#include <array>
#include <drm/drm_fourcc.h>
#include <tuple>
// Must come after imgui.h
#include "frontend-common/imgui_impl_opengl3.h"
Log_SetChannel(Go2OpenGLHostDisplay);
Go2OpenGLHostDisplay::Go2OpenGLHostDisplay() = default;
Go2OpenGLHostDisplay::~Go2OpenGLHostDisplay()
{
Assert(!m_display && !m_context && !m_presenter);
}
HostDisplay::RenderAPI Go2OpenGLHostDisplay::GetRenderAPI() const
{
return HostDisplay::RenderAPI::OpenGLES;
}
void Go2OpenGLHostDisplay::SetVSync(bool enabled)
{
// Window framebuffer has to be bound to call SetSwapInterval.
GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
eglSwapInterval(go2_context_egldisplay_get(m_context), enabled ? 1 : 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
}
bool Go2OpenGLHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device)
{
m_display = go2_display_create();
if (!m_display)
return false;
m_presenter = go2_presenter_create(m_display, DRM_FORMAT_RGB565, 0xff080808);
if (!m_presenter)
return false;
m_window_info = wi;
m_window_info.surface_width = go2_display_height_get(m_display);
m_window_info.surface_height = go2_display_width_get(m_display);
static constexpr std::array<std::tuple<int, int>, 4> versions_to_try = {{{3, 2}, {3, 1}, {3, 0}, {2, 0}}};
go2_context_attributes_t attributes = {};
attributes.red_bits = 5;
attributes.green_bits = 6;
attributes.blue_bits = 5;
attributes.alpha_bits = 0;
attributes.depth_bits = 0;
attributes.stencil_bits = 0;
for (const auto [major, minor] : versions_to_try)
{
attributes.major = major;
attributes.minor = minor;
Log_InfoPrintf("Trying a OpenGL ES %d.%d context", major, minor);
m_context = go2_context_create(m_display, m_window_info.surface_width, m_window_info.surface_height, &attributes);
if (m_context)
{
Log_InfoPrintf("Got a OpenGL ES %d.%d context", major, minor);
break;
}
}
if (!m_context)
{
Log_ErrorPrintf("Failed to create any GL context");
return false;
}
// Load GLAD.
go2_context_make_current(m_context);
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(eglGetProcAddress)))
{
Log_ErrorPrintf("Failed to load GL functions");
return false;
}
// start with vsync on
eglSwapInterval(go2_context_egldisplay_get(m_context), 1);
return true;
}
void Go2OpenGLHostDisplay::DestroyRenderDevice()
{
if (ImGui::GetCurrentContext())
DestroyImGuiContext();
DestroyResources();
if (m_context)
{
go2_context_make_current(nullptr);
go2_context_destroy(m_context);
}
if (m_presenter)
go2_presenter_destroy(m_presenter);
if (m_display)
go2_display_destroy(m_display);
}
bool Go2OpenGLHostDisplay::Render()
{
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (HasDisplayTexture())
{
const auto [left, top, width, height] =
CalculateDrawRect(m_window_info.surface_width, m_window_info.surface_height, 0, false);
RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height,
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
m_display_texture_view_height, m_display_linear_filtering);
}
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
GL::Program::ResetLastProgram();
go2_context_swap_buffers(m_context);
go2_surface_t* gles_surface = go2_context_surface_lock(m_context);
go2_presenter_post(m_presenter, gles_surface, 0, 0, m_window_info.surface_width, m_window_info.surface_height, 0, 0,
m_window_info.surface_height, m_window_info.surface_width, GO2_ROTATION_DEGREES_270);
go2_context_surface_unlock(m_context, gles_surface);
ImGui_ImplOpenGL3_NewFrame();
return true;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "common/gl/program.h"
#include "common/gl/texture.h"
#include "frontend-common/opengl_host_display.h"
#include <go2/display.h>
#include <memory>
#include <string>
class Go2OpenGLHostDisplay final : public FrontendCommon::OpenGLHostDisplay
{
public:
Go2OpenGLHostDisplay();
~Go2OpenGLHostDisplay();
RenderAPI GetRenderAPI() const override;
bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override;
void DestroyRenderDevice() override;
void SetVSync(bool enabled) override;
bool Render() override;
private:
go2_display_t* m_display = nullptr;
go2_context_t* m_context = nullptr;
go2_presenter_t* m_presenter = nullptr;
};

View File

@ -0,0 +1,43 @@
#include "common/assert.h"
#include "common/log.h"
#include "core/system.h"
#include "frontend-common/sdl_initializer.h"
#include "go2_host_interface.h"
#include <cstdio>
#include <cstdlib>
int main(int argc, char* argv[])
{
std::unique_ptr<Go2HostInterface> host_interface = Go2HostInterface::Create();
std::unique_ptr<SystemBootParameters> boot_params;
if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params))
return EXIT_FAILURE;
if (!host_interface->Initialize())
{
host_interface->Shutdown();
return EXIT_FAILURE;
}
if (boot_params)
{
if (!host_interface->BootSystem(*boot_params) && host_interface->InBatchMode())
{
host_interface->Shutdown();
host_interface.reset();
return EXIT_FAILURE;
}
boot_params.reset();
host_interface->Run();
}
else
{
std::fprintf(stderr, "No file specified.\n");
}
host_interface->Shutdown();
host_interface.reset();
return EXIT_SUCCESS;
}