Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 70 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(MegaManXSNESRecomp C)
project(MegaManXSNESRecomp C CXX)

# Cross-platform (macOS/Linux) build for MegaManXSNESRecomp.
#
Expand All @@ -22,6 +22,8 @@ project(MegaManXSNESRecomp C)
# ROM staged); SDL2 (e.g. `brew install sdl2`).

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(SNESRECOMP_ROOT ${CMAKE_SOURCE_DIR}/snesrecomp)

Expand All @@ -33,7 +35,56 @@ if(BUILD_TESTING)
endif()

include(${SNESRECOMP_ROOT}/runner/runner.cmake)
include(${SNESRECOMP_ROOT}/runner/src/launcher_ng/launcher_ng.cmake)

# The runner launcher supplies the game-agnostic C pieces. This repo owns the
# ImGui copy used by both the launcher and native platform UI; using the
# launcher's older bundled ImGui would create duplicate symbols and an API
# mismatch with platform backends vendored here.
function(mmx_target_launcher_ng TGT)
cmake_parse_arguments(LNG "" "BOXART" "" ${ARGN})
set(LNG_ROOT ${SNESRECOMP_ROOT}/runner/src/launcher_ng)
set(LNG_IMGUI ${CMAKE_SOURCE_DIR}/third_party/imgui)
target_sources(${TGT} PRIVATE
${LNG_ROOT}/launcher_model.c
${LNG_ROOT}/launcher_platform_sdl2.c
${LNG_ROOT}/launcher_gl.c
${LNG_ROOT}/launcher_input.c
${LNG_ROOT}/launcher_files.c
${LNG_ROOT}/launcher_debug.c
${LNG_ROOT}/launcher_binds.c
${LNG_ROOT}/launcher_ng_capi.c
${LNG_ROOT}/third_party/tinyfiledialogs.c
${LNG_ROOT}/backends/imgui/launcher_imgui.cpp
${LNG_IMGUI}/imgui.cpp
${LNG_IMGUI}/imgui_draw.cpp
${LNG_IMGUI}/imgui_tables.cpp
${LNG_IMGUI}/imgui_widgets.cpp
${LNG_IMGUI}/backends/imgui_impl_sdl2.cpp
${LNG_IMGUI}/backends/imgui_impl_opengl3.cpp)
target_include_directories(${TGT} PRIVATE
${LNG_ROOT}/../launcher ${LNG_ROOT}
${LNG_ROOT}/third_party ${LNG_IMGUI}
${LNG_IMGUI}/backends)
target_compile_definitions(${TGT} PRIVATE SNES_LAUNCHER SDL_MAIN_HANDLED)
set_target_properties(${TGT} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON)
add_custom_command(TARGET ${TGT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:${TGT}>/assets/fonts
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:${TGT}>/assets/img
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${LNG_ROOT}/../launcher/assets/fonts/LatoLatin-Regular.ttf
${LNG_ROOT}/../launcher/assets/fonts/LatoLatin-Bold.ttf
$<TARGET_FILE_DIR:${TGT}>/assets/fonts/
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${LNG_ROOT}/../launcher/assets/img/brand_mark.tga
${LNG_ROOT}/../launcher/assets/img/snes_pad.tga
$<TARGET_FILE_DIR:${TGT}>/assets/img/
VERBATIM)
if(LNG_BOXART AND EXISTS ${LNG_BOXART})
add_custom_command(TARGET ${TGT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${LNG_BOXART}
$<TARGET_FILE_DIR:${TGT}>/assets/img/boxart.tga VERBATIM)
endif()
endfunction()

find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
Expand Down Expand Up @@ -69,6 +120,10 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)
src/glsl_shader.c
src/main.c
src/mmx_wide_preview.c
$<$<PLATFORM_ID:Linux>:src/linux_ui.cpp>
# launcher_ng supplies the single shared ImGui core, SDL2 backend, and
# OpenGL3 backend. Keep only the game UI's platform renderer here.
$<$<PLATFORM_ID:Linux>:third_party/imgui/backends/imgui_impl_sdlrenderer2.cpp>
src/post_mortem.c
src/opengl.c
src/mmx_00.c
Expand All @@ -94,6 +149,18 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)
${SDL2_INCLUDE_DIRS}
)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_include_directories(${TARGET} PRIVATE
${CMAKE_SOURCE_DIR}/third_party/imgui
${CMAKE_SOURCE_DIR}/third_party/imgui/backends)
endif()

# host_report.c uses dl_phdr_info for Linux module diagnostics; glibc hides
# that declaration unless this feature-test macro is set before headers.
if(UNIX AND NOT APPLE)
target_compile_definitions(${TARGET} PRIVATE _GNU_SOURCE=1)
endif()

target_compile_definitions(${TARGET} PRIVATE
SMW_DISABLE_LM=1
SYSTEM_VOLUME_MIXER_AVAILABLE=0 # Win32 WASAPI volume control excluded
Expand Down Expand Up @@ -128,7 +195,7 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)

# Dear ImGui pre-boot launcher (GUI). One call wires sources/includes/
# SNES_LAUNCHER/CXX/asset-staging; see launcher_ng/launcher_ng.cmake.
snesrecomp_target_launcher_ng(${TARGET}
mmx_target_launcher_ng(${TARGET}
BOXART ${CMAKE_SOURCE_DIR}/recomp/launcher/boxart.tga)
endfunction()

Expand Down
139 changes: 139 additions & 0 deletions src/linux_ui.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "linux_ui.h"

#include "mmx_display.h"
extern "C" {
#include "mmx_rtl.h"
}

#include "imgui.h"
#include "backends/imgui_impl_opengl3.h"
#include "backends/imgui_impl_sdl2.h"
#include "backends/imgui_impl_sdlrenderer2.h"

#include <SDL.h>
#include <stdio.h>

static bool g_ui_open;
static bool g_use_opengl;
static char g_state_message[64];

static void RenderMenu(void) {
ImGui::SetNextWindowSize(ImVec2(430.0f, 600.0f), ImGuiCond_FirstUseEver);
ImGui::Begin("Mega Man X", &g_ui_open);
ImGui::TextUnformatted("Display settings");
ImGui::Separator();

bool widescreen = MmxDisplay_IsWidescreenEnabled();
if (ImGui::Checkbox("True widescreen renderer", &widescreen))
MmxDisplay_SetWidescreenEnabled(widescreen);
ImGui::Text("Status: %s, %s (%dx224)", widescreen ? "enabled" : "disabled",
MmxDisplay_IsWidescreenActive() ? "active" : "inactive",
MmxDisplay_GetCurrentFrameWidth());
ImGui::TextWrapped("Adds host-only PPU scene columns; gameplay stays unchanged.");

ImGui::Separator();
ImGui::TextUnformatted("Save states");
for (int slot = 0; slot < 10; ++slot) {
ImGui::PushID(slot);
char save_label[16];
char load_label[16];
snprintf(save_label, sizeof(save_label), "Save %d", slot + 1);
snprintf(load_label, sizeof(load_label), "Load %d", slot + 1);
if (ImGui::Button(save_label)) {
RtlSaveLoad(kSaveLoad_Save, slot);
snprintf(g_state_message, sizeof(g_state_message), "Saved state %d", slot + 1);
}
ImGui::SameLine();
if (ImGui::Button(load_label)) {
RtlSaveLoad(kSaveLoad_Load, slot);
snprintf(g_state_message, sizeof(g_state_message), "Loaded state %d", slot + 1);
}
ImGui::PopID();
}
if (g_state_message[0]) ImGui::TextUnformatted(g_state_message);

ImGui::Separator();
ImGui::TextUnformatted("F1 toggles this menu");
ImGui::Text("Renderer: %s", g_use_opengl ? "OpenGL" : "SDL");
ImGui::TextUnformatted("Controller: SDL GameController");
ImGui::TextUnformatted("Audio: SDL audio");
ImGui::End();
}

static void BeginFrame(void) {
if (g_use_opengl)
ImGui_ImplOpenGL3_NewFrame();
else
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
RenderMenu();
ImGui::Render();
}

extern "C" bool LinuxUi_Init(SDL_Window *window, SDL_Renderer *renderer,
bool use_opengl) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
ImGui::StyleColorsDark();

g_use_opengl = use_opengl;
bool platform_ok;
bool renderer_ok;
if (use_opengl) {
platform_ok = ImGui_ImplSDL2_InitForOpenGL(window, SDL_GL_GetCurrentContext());
renderer_ok = ImGui_ImplOpenGL3_Init("#version 330 core");
} else {
platform_ok = ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);
renderer_ok = ImGui_ImplSDLRenderer2_Init(renderer);
}
if (!platform_ok || !renderer_ok) {
LinuxUi_Shutdown();
return false;
}
g_ui_open = false;
g_state_message[0] = '\0';
return true;
}

extern "C" void LinuxUi_Shutdown(void) {
if (!ImGui::GetCurrentContext()) return;
if (g_use_opengl)
ImGui_ImplOpenGL3_Shutdown();
else
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
g_ui_open = false;
}

extern "C" void LinuxUi_ProcessEvent(const void *event) {
if (ImGui::GetCurrentContext())
ImGui_ImplSDL2_ProcessEvent((const SDL_Event *)event);
}

extern "C" void LinuxUi_Toggle(void) { g_ui_open = !g_ui_open; }
extern "C" bool LinuxUi_IsOpen(void) { return g_ui_open; }
extern "C" bool LinuxUi_CaptureInput(void) { return g_ui_open; }

extern "C" void LinuxUi_RenderSdl(SDL_Renderer *renderer) {
if (!g_ui_open || !ImGui::GetCurrentContext() || g_use_opengl) return;

/* The game uses an emulated-pixel logical size. ImGui uses window pixels,
* so render it with logical scaling disabled and restore the game mapping. */
int logical_width, logical_height;
SDL_RenderGetLogicalSize(renderer, &logical_width, &logical_height);
SDL_RenderSetLogicalSize(renderer, 0, 0);
BeginFrame();
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer);
SDL_RenderSetLogicalSize(renderer, logical_width, logical_height);
}

extern "C" void LinuxUi_RenderOpenGL(void) {
if (!g_ui_open || !ImGui::GetCurrentContext() || !g_use_opengl) return;
BeginFrame();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
26 changes: 26 additions & 0 deletions src/linux_ui.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <stdbool.h>

struct SDL_Renderer;
struct SDL_Window;

#ifdef __cplusplus
extern "C" {
#endif

/* Runtime settings UI for Linux. The renderer is NULL for OpenGL and the
* active SDL_Renderer for accelerated/software SDL output. */
bool LinuxUi_Init(struct SDL_Window *window, struct SDL_Renderer *renderer,
bool use_opengl);
void LinuxUi_Shutdown(void);
void LinuxUi_ProcessEvent(const void *event);
void LinuxUi_Toggle(void);
bool LinuxUi_IsOpen(void);
bool LinuxUi_CaptureInput(void);
void LinuxUi_RenderSdl(struct SDL_Renderer *renderer);
void LinuxUi_RenderOpenGL(void);

#ifdef __cplusplus
}
#endif
34 changes: 34 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#include "launcher.h"
#include "keybinds.h"
#include "host_report.h"
#ifdef __linux__
#include "linux_ui.h"
#endif

typedef struct GamepadInfo {
uint32 modifiers;
Expand Down Expand Up @@ -572,6 +575,9 @@ static void SdlRenderer_EndDraw(void) {
// printf("%f ms\n", v * 1000);
SDL_RenderClear(g_renderer);
SDL_RenderCopy(g_renderer, g_texture, &g_sdl_renderer_rect, NULL);
#ifdef __linux__
LinuxUi_RenderSdl(g_renderer);
#endif
SDL_RenderPresent(g_renderer); // vsyncs to 60 FPS?
}

Expand Down Expand Up @@ -1170,6 +1176,14 @@ error_reading:;
g_config.output_method);
return 1;
}
#ifdef __linux__
if (!LinuxUi_Init(window,
g_config.output_method == kOutputMethod_OpenGL ? NULL : g_renderer,
g_config.output_method == kOutputMethod_OpenGL)) {
host_report_breadcrumb("Linux ImGui initialization failed");
return 1;
}
#endif
host_report_breadcrumb("renderer initialized: %s",
g_config.output_method == kOutputMethod_OpenGL ? "opengl" :
g_config.output_method == kOutputMethod_SDLSoftware ? "sdl-software" : "sdl");
Expand Down Expand Up @@ -1272,6 +1286,15 @@ error_reading:;
host_report_crash_test_tick();

while (SDL_PollEvent(&event)) {
#ifdef __linux__
LinuxUi_ProcessEvent(&event);
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F1 && !event.key.repeat) {
LinuxUi_Toggle();
continue;
}
if (LinuxUi_CaptureInput() && event.type != SDL_QUIT)
continue;
#endif
switch (event.type) {
case SDL_CONTROLLERDEVICEADDED:
OpenOneGamepad(event.cdevice.which);
Expand Down Expand Up @@ -1358,6 +1381,11 @@ error_reading:;
* A B X Y L R). HandleCommand is idempotent for set/clear, so calling
* it every frame is safe. */
{
#ifdef __linux__
if (LinuxUi_IsOpen()) {
/* Dear ImGui owns keyboard focus while the settings menu is open. */
} else {
#endif
const uint8_t *keys = SDL_GetKeyboardState(NULL);
uint16_t kb_p1 = keybinds_read_player(keys, 1);
uint16_t kb_p2 = keybinds_read_player(keys, 2);
Expand All @@ -1366,6 +1394,9 @@ error_reading:;
HandleCommand(kKeys_Controls + i, (kb_p1 >> kKb2CtrlsIdx[i]) & 1);
HandleCommand(kKeys_ControlsP2 + i, (kb_p2 >> kKb2CtrlsIdx[i]) & 1);
}
#ifdef __linux__
}
#endif
}

uint32 inputs = g_input_state | g_pad_buttons | g_gamepad[0].axis_buttons | g_gamepad[1].axis_buttons << 12;
Expand Down Expand Up @@ -1466,6 +1497,9 @@ error_reading:;
SDL_DestroyMutex(g_audio_mutex);
free(g_audiobuffer);

#ifdef __linux__
LinuxUi_Shutdown();
#endif
g_renderer_funcs.Destroy();

#ifdef __SWITCH__
Expand Down
7 changes: 7 additions & 0 deletions src/opengl.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include "glsl_shader.h"
#include "config.h"
#include "mmx_display.h"
#ifdef __linux__
#include "linux_ui.h"
#endif

#define CODE(...) #__VA_ARGS__

Expand Down Expand Up @@ -202,6 +205,10 @@ static void OpenGLRenderer_EndDraw(void) {
GlslShader_Render(g_glsl_shader, &g_texture, viewport_x, viewport_y, viewport_width, viewport_height);
}

#ifdef __linux__
LinuxUi_RenderOpenGL();
#endif

SDL_GL_SwapWindow(g_window);
}

Expand Down
Loading