Add go2 frontend
This commit is contained in:
parent
14fbdd3657
commit
34107dd573
|
@ -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()
|
||||
|
|
|
@ -35,3 +35,6 @@ if(${CPU_ARCH} STREQUAL "aarch64")
|
|||
add_subdirectory(vixl)
|
||||
endif()
|
||||
|
||||
if(BUILD_OGA_FRONTEND)
|
||||
add_subdirectory(libgo2)
|
||||
endif()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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, ¤t_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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue