From 23431c9705e8977363e847eaf4770f5451558689 Mon Sep 17 00:00:00 2001 From: DraVee Date: Sun, 4 Jan 2026 09:39:24 -0300 Subject: [PATCH] initial wip --- CMakeLists.txt | 3 + src/CMakeLists.txt | 4 + src/core/frontend/emu_window.h | 3 + src/eden_libretro/CMakeLists.txt | 130 ++ src/eden_libretro/eden_libretro.info.in | 35 + src/eden_libretro/emu_window_libretro.cpp | 178 +++ src/eden_libretro/emu_window_libretro.h | 77 ++ src/eden_libretro/libretro.h | 1129 +++++++++++++++++ src/eden_libretro/libretro_core.cpp | 730 +++++++++++ src/eden_libretro/libretro_vfs.h | 306 +++++ src/video_core/gpu.cpp | 47 + src/video_core/gpu.h | 9 + src/video_core/gpu_thread.cpp | 53 + src/video_core/gpu_thread.h | 13 + .../renderer_opengl/gl_blit_screen.cpp | 3 +- .../renderer_opengl/gl_buffer_cache.cpp | 4 +- .../renderer_opengl/gl_rasterizer.cpp | 12 +- .../renderer_opengl/gl_rasterizer.h | 3 + .../renderer_opengl/gl_shader_manager.cpp | 8 + .../present/window_adapt_pass.cpp | 25 +- .../present/window_adapt_pass.h | 1 + .../renderer_opengl/renderer_opengl.cpp | 18 +- 22 files changed, 2776 insertions(+), 15 deletions(-) create mode 100644 src/eden_libretro/CMakeLists.txt create mode 100644 src/eden_libretro/eden_libretro.info.in create mode 100644 src/eden_libretro/emu_window_libretro.cpp create mode 100644 src/eden_libretro/emu_window_libretro.h create mode 100644 src/eden_libretro/libretro.h create mode 100644 src/eden_libretro/libretro_core.cpp create mode 100644 src/eden_libretro/libretro_vfs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0630c85d..ba5e206356 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,9 @@ cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}") +# Libretro core +option(ENABLE_LIBRETRO "Build the libretro core" OFF) + option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) if(YUZU_ENABLE_LTO) include(UseLTO) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2510458812..2284704047 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -247,4 +247,8 @@ if (ANDROID) target_include_directories(yuzu-android PRIVATE android/app/src/main) endif() +if (ENABLE_LIBRETRO) + add_subdirectory(eden_libretro) +endif() + include(GenerateDepHashes) diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index ee7a7693b2..84de21f4dc 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -75,6 +75,9 @@ public: /// Called from GPU thread when a frame is displayed. virtual void OnFrameDisplayed() {} + + /// Returns the framebuffer ID to use for final presentation (0 for normal, custom for libretro) + virtual u32 GetPresentationFramebuffer() const { return 0; } /** * Returns a GraphicsContext that the frontend provides to be used for rendering. diff --git a/src/eden_libretro/CMakeLists.txt b/src/eden_libretro/CMakeLists.txt new file mode 100644 index 0000000000..2d4c7270c2 --- /dev/null +++ b/src/eden_libretro/CMakeLists.txt @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +# Eden Libretro Core + +add_library(eden_libretro SHARED + libretro_core.cpp + emu_window_libretro.cpp + emu_window_libretro.h + libretro.h + libretro_vfs.h +) + +# Create eden_libretro alias target +add_library(Eden::LibretroCore ALIAS eden_libretro) + +# Set output name based on platform +if(WIN32) + set_target_properties(eden_libretro PROPERTIES + OUTPUT_NAME "eden_libretro" + PREFIX "" + SUFFIX ".dll" + ) +elseif(APPLE) + set_target_properties(eden_libretro PROPERTIES + OUTPUT_NAME "eden_libretro" + PREFIX "" + SUFFIX ".dylib" + ) +else() + set_target_properties(eden_libretro PROPERTIES + OUTPUT_NAME "eden_libretro" + PREFIX "" + SUFFIX ".so" + ) +endif() + +# Include directories +target_include_directories(eden_libretro PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} +) + +# Link against core libraries +target_link_libraries(eden_libretro PRIVATE + common + core + video_core + audio_core + hid_core + input_common + frontend_common + network + Boost::headers +) + +# Platform-specific settings +if(WIN32) + target_link_libraries(eden_libretro PRIVATE + ws2_32 + winmm + iphlpapi + ) + + # Disable console window on Windows + if(MSVC) + set_target_properties(eden_libretro PROPERTIES + LINK_FLAGS "/SUBSYSTEM:WINDOWS" + ) + endif() +endif() + +if(UNIX AND NOT APPLE) + target_link_libraries(eden_libretro PRIVATE + pthread + dl + ) +endif() + +# OpenGL support +find_package(OpenGL REQUIRED) +target_link_libraries(eden_libretro PRIVATE OpenGL::GL) + +# GLAD for OpenGL loading +if(TARGET glad) + target_link_libraries(eden_libretro PRIVATE glad) +endif() + +# Enable position-independent code for shared library +set_target_properties(eden_libretro PROPERTIES + POSITION_INDEPENDENT_CODE ON + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON +) + +# Export symbols for libretro API +# Note: RETRO_CALLCONV and RETRO_API are defined in libretro.h based on platform +# We don't need to override them here as the header handles it correctly + +# Compiler warnings +if(MSVC) + target_compile_options(eden_libretro PRIVATE + /W4 + /wd4100 # unreferenced formal parameter + /wd4244 # conversion warnings + ) +else() + target_compile_options(eden_libretro PRIVATE + -Wall + -Wextra + -Wno-unused-parameter + ) +endif() + +# Install rules +install(TARGETS eden_libretro + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +# Copy info file for RetroArch +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/eden_libretro.info.in + ${CMAKE_BINARY_DIR}/eden_libretro.info + @ONLY +) + +install(FILES ${CMAKE_BINARY_DIR}/eden_libretro.info + DESTINATION share/libretro/info +) diff --git a/src/eden_libretro/eden_libretro.info.in b/src/eden_libretro/eden_libretro.info.in new file mode 100644 index 0000000000..20fbe680c8 --- /dev/null +++ b/src/eden_libretro/eden_libretro.info.in @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +# Software Information +display_name = "Nintendo - Switch (Eden)" +authors = "Eden Emulator Project" +supported_extensions = "nsp|xci|nca|nso|nro" +corename = "Eden" +manufacturer = "Nintendo" +categories = "Emulator" +systemname = "Nintendo Switch" +systemid = "nintendo_switch" +database = "Nintendo - Switch" +license = "GPLv3" +permissions = "" +display_version = "@CMAKE_PROJECT_VERSION@" + +# Hardware Information +needs_fullpath = "true" +supports_no_game = "false" +single_purpose = "true" + +# Libretro Features +hw_render = "true" +requires_content = "true" +is_experimental = "true" + +# Firmware / BIOS +firmware_count = 0 + +# Notes +notes = "(!) Requires Nintendo Switch firmware and keys to be placed in the system directory.|Place prod.keys and title.keys in the system directory.|Some games may require additional system files." + +# Description +description = "Eden is an experimental Nintendo Switch emulator for the libretro API. It requires original Nintendo Switch firmware and decryption keys to function. This core uses OpenGL hardware rendering for optimal performance." diff --git a/src/eden_libretro/emu_window_libretro.cpp b/src/eden_libretro/emu_window_libretro.cpp new file mode 100644 index 0000000000..fa6a2bea9f --- /dev/null +++ b/src/eden_libretro/emu_window_libretro.cpp @@ -0,0 +1,178 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "eden_libretro/emu_window_libretro.h" +#include "common/logging/log.h" +#include + +namespace Libretro { + +// Static member initialization +std::thread::id LibretroGraphicsContext::main_thread_id{}; + +void LibretroGraphicsContext::SetMainThreadId() { + main_thread_id = std::this_thread::get_id(); + LOG_INFO(Frontend, "LibretroGraphicsContext: Main thread ID set"); +} + +bool LibretroGraphicsContext::IsMainThread() { + return std::this_thread::get_id() == main_thread_id; +} + +// LibretroGraphicsContext implementation +LibretroGraphicsContext::LibretroGraphicsContext(retro_hw_get_proc_address_t get_proc_address) + : hw_get_proc_address(get_proc_address) { + LOG_DEBUG(Frontend, "LibretroGraphicsContext created with get_proc_address: {}", + reinterpret_cast(get_proc_address)); +} + +LibretroGraphicsContext::~LibretroGraphicsContext() { + LOG_DEBUG(Frontend, "LibretroGraphicsContext destroyed"); +} + +void LibretroGraphicsContext::SwapBuffers() { + // In libretro, RetroArch handles buffer swapping via video_cb + // We don't need to do anything here +} + +void LibretroGraphicsContext::MakeCurrent() { + // In libretro, the context is always current on the main thread + // If we're not on the main thread, we can't make the context current + if (!IsMainThread()) { + LOG_WARNING(Frontend, "LibretroGraphicsContext: MakeCurrent called from non-main thread - OpenGL calls may fail"); + } +} + +void LibretroGraphicsContext::DoneCurrent() { + // Nothing to do - context stays current on main thread +} + +// EmuWindowLibretro implementation +EmuWindowLibretro::EmuWindowLibretro() { + LOG_INFO(Frontend, "EmuWindowLibretro: Initializing libretro emulator window"); + + // Set up window system info for headless/libretro mode + window_info.type = Core::Frontend::WindowSystemType::Headless; + window_info.display_connection = nullptr; + window_info.render_surface = nullptr; + window_info.render_surface_scale = 1.0f; + + // Initialize default framebuffer layout + Layout::FramebufferLayout layout; + layout.width = fb_width; + layout.height = fb_height; + layout.screen.left = 0; + layout.screen.top = 0; + layout.screen.right = fb_width; + layout.screen.bottom = fb_height; + layout.is_srgb = false; + + NotifyFramebufferLayoutChanged(layout); + NotifyClientAreaSizeChanged({fb_width, fb_height}); + + LOG_INFO(Frontend, "EmuWindowLibretro: Initialized with {}x{} framebuffer", fb_width, fb_height); +} + +EmuWindowLibretro::~EmuWindowLibretro() { + LOG_INFO(Frontend, "EmuWindowLibretro: Shutting down"); +} + +std::unique_ptr EmuWindowLibretro::CreateSharedContext() const { + LOG_DEBUG(Frontend, "EmuWindowLibretro: CreateSharedContext called"); + + if (hw_render_callback && hw_render_callback->get_proc_address) { + LOG_DEBUG(Frontend, "EmuWindowLibretro: Creating LibretroGraphicsContext with HW proc address"); + return std::make_unique(hw_render_callback->get_proc_address); + } + + LOG_WARNING(Frontend, "EmuWindowLibretro: No HW render callback, returning null context"); + return std::make_unique(nullptr); +} + +void EmuWindowLibretro::OnFrameDisplayed() { + LOG_TRACE(Frontend, "EmuWindowLibretro: OnFrameDisplayed"); + + if (frame_callback) { + frame_callback(); + } +} + +void EmuWindowLibretro::SetHWRenderCallback(retro_hw_render_callback* hw_render) { + hw_render_callback = hw_render; + + if (hw_render) { + LOG_INFO(Frontend, "EmuWindowLibretro: HW render callback set - context_type: {}, " + "version: {}.{}, depth: {}, stencil: {}, bottom_left_origin: {}", + static_cast(hw_render->context_type), + hw_render->version_major, hw_render->version_minor, + hw_render->depth, hw_render->stencil, hw_render->bottom_left_origin); + } else { + LOG_WARNING(Frontend, "EmuWindowLibretro: HW render callback cleared"); + } +} + +void EmuWindowLibretro::SetContextReady(bool ready) { + bool old_ready = context_ready.exchange(ready); + + if (old_ready != ready) { + LOG_INFO(Frontend, "EmuWindowLibretro: Context ready state changed: {} -> {}", + old_ready, ready); + } +} + +uintptr_t EmuWindowLibretro::GetCurrentFramebuffer() const { + if (!hw_render_callback || !hw_render_callback->get_current_framebuffer) { + LOG_TRACE(Frontend, "EmuWindowLibretro: GetCurrentFramebuffer - no callback, returning 0"); + return 0; + } + + uintptr_t fbo = hw_render_callback->get_current_framebuffer(); + LOG_TRACE(Frontend, "EmuWindowLibretro: GetCurrentFramebuffer returned FBO: {}", fbo); + return fbo; +} + +u32 EmuWindowLibretro::GetPresentationFramebuffer() const { + // Return RetroArch's FBO for final presentation + return static_cast(GetCurrentFramebuffer()); +} + +retro_hw_get_proc_address_t EmuWindowLibretro::GetProcAddress() const { + if (!hw_render_callback) { + LOG_TRACE(Frontend, "EmuWindowLibretro: GetProcAddress - no callback"); + return nullptr; + } + return hw_render_callback->get_proc_address; +} + +void EmuWindowLibretro::SetFramebufferSize(unsigned width, unsigned height) { + if (width == 0 || height == 0) { + LOG_WARNING(Frontend, "EmuWindowLibretro: Invalid framebuffer size {}x{}, ignoring", + width, height); + return; + } + + fb_width = width; + fb_height = height; + + // Update the framebuffer layout + Layout::FramebufferLayout layout; + layout.width = fb_width; + layout.height = fb_height; + layout.screen.left = 0; + layout.screen.top = 0; + layout.screen.right = fb_width; + layout.screen.bottom = fb_height; + layout.is_srgb = false; + + NotifyFramebufferLayoutChanged(layout); + NotifyClientAreaSizeChanged({fb_width, fb_height}); + + LOG_INFO(Frontend, "EmuWindowLibretro: Framebuffer size updated to {}x{}", fb_width, fb_height); +} + +void EmuWindowLibretro::SetFrameCallback(std::function callback) { + frame_callback = std::move(callback); + LOG_DEBUG(Frontend, "EmuWindowLibretro: Frame callback set"); +} + +} // namespace Libretro diff --git a/src/eden_libretro/emu_window_libretro.h b/src/eden_libretro/emu_window_libretro.h new file mode 100644 index 0000000000..11c95b91bb --- /dev/null +++ b/src/eden_libretro/emu_window_libretro.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "core/frontend/emu_window.h" +#include "core/frontend/graphics_context.h" + +#include "eden_libretro/libretro.h" + +namespace Core { +class System; +} + +namespace Libretro { + +// Graphics context for libretro hardware rendering +// In libretro, the OpenGL context is managed by RetroArch and is always current +// on the main thread during retro_run(). We can't make it current on other threads. +class LibretroGraphicsContext : public Core::Frontend::GraphicsContext { +public: + explicit LibretroGraphicsContext(retro_hw_get_proc_address_t get_proc_address); + ~LibretroGraphicsContext() override; + + void SwapBuffers() override; + void MakeCurrent() override; + void DoneCurrent() override; + + retro_hw_get_proc_address_t GetProcAddress() const { return hw_get_proc_address; } + + // Check if we're on the main thread where context is valid + static void SetMainThreadId(); + static bool IsMainThread(); + +private: + retro_hw_get_proc_address_t hw_get_proc_address = nullptr; + static std::thread::id main_thread_id; +}; + +// EmuWindow implementation for libretro +class EmuWindowLibretro : public Core::Frontend::EmuWindow { +public: + explicit EmuWindowLibretro(); + ~EmuWindowLibretro() override; + + // EmuWindow interface + std::unique_ptr CreateSharedContext() const override; + bool IsShown() const override { return true; } + void OnFrameDisplayed() override; + u32 GetPresentationFramebuffer() const override; + + // Libretro-specific methods + void SetHWRenderCallback(retro_hw_render_callback* hw_render); + void SetContextReady(bool ready); + bool IsContextReady() const { return context_ready.load(); } + + uintptr_t GetCurrentFramebuffer() const; + retro_hw_get_proc_address_t GetProcAddress() const; + + void SetFramebufferSize(unsigned width, unsigned height); + void SetFrameCallback(std::function callback); + +private: + retro_hw_render_callback* hw_render_callback = nullptr; + std::atomic context_ready{false}; + std::function frame_callback; + + unsigned fb_width = 1280; + unsigned fb_height = 720; +}; + +} // namespace Libretro diff --git a/src/eden_libretro/libretro.h b/src/eden_libretro/libretro.h new file mode 100644 index 0000000000..e6a2b19b88 --- /dev/null +++ b/src/eden_libretro/libretro.h @@ -0,0 +1,1129 @@ +/* Copyright (C) 2010-2024 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this libretro API header (libretro.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef LIBRETRO_H__ +#define LIBRETRO_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __cplusplus +#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(SN_TARGET_PS3) +typedef unsigned char bool; +#define true 1 +#define false 0 +#else +#include +#endif +#endif + +#ifndef RETRO_CALLCONV +# if defined(__GNUC__) && defined(__i386__) && !defined(__x86_64__) +# define RETRO_CALLCONV __attribute__((cdecl)) +# elif defined(_MSC_VER) && defined(_M_X86) && !defined(_M_X64) +# define RETRO_CALLCONV __cdecl +# else +# define RETRO_CALLCONV +# endif +#endif + +#ifndef RETRO_API +# if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +# ifdef RETRO_IMPORT_SYMBOLS +# ifdef __GNUC__ +# define RETRO_API RETRO_CALLCONV __attribute__((__dllimport__)) +# else +# define RETRO_API RETRO_CALLCONV __declspec(dllimport) +# endif +# else +# ifdef __GNUC__ +# define RETRO_API RETRO_CALLCONV __attribute__((__dllexport__)) +# else +# define RETRO_API RETRO_CALLCONV __declspec(dllexport) +# endif +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define RETRO_API RETRO_CALLCONV __attribute__((__visibility__("default"))) +# else +# define RETRO_API RETRO_CALLCONV +# endif +# endif +#endif + +#define RETRO_API_VERSION 1 + +#define RETRO_DEVICE_TYPE_SHIFT 8 +#define RETRO_DEVICE_MASK ((1 << RETRO_DEVICE_TYPE_SHIFT) - 1) +#define RETRO_DEVICE_SUBCLASS(base, id) (((id + 1) << RETRO_DEVICE_TYPE_SHIFT) | base) + +#define RETRO_DEVICE_NONE 0 +#define RETRO_DEVICE_JOYPAD 1 +#define RETRO_DEVICE_MOUSE 2 +#define RETRO_DEVICE_KEYBOARD 3 +#define RETRO_DEVICE_LIGHTGUN 4 +#define RETRO_DEVICE_ANALOG 5 +#define RETRO_DEVICE_POINTER 6 + +#define RETRO_DEVICE_ID_JOYPAD_B 0 +#define RETRO_DEVICE_ID_JOYPAD_Y 1 +#define RETRO_DEVICE_ID_JOYPAD_SELECT 2 +#define RETRO_DEVICE_ID_JOYPAD_START 3 +#define RETRO_DEVICE_ID_JOYPAD_UP 4 +#define RETRO_DEVICE_ID_JOYPAD_DOWN 5 +#define RETRO_DEVICE_ID_JOYPAD_LEFT 6 +#define RETRO_DEVICE_ID_JOYPAD_RIGHT 7 +#define RETRO_DEVICE_ID_JOYPAD_A 8 +#define RETRO_DEVICE_ID_JOYPAD_X 9 +#define RETRO_DEVICE_ID_JOYPAD_L 10 +#define RETRO_DEVICE_ID_JOYPAD_R 11 +#define RETRO_DEVICE_ID_JOYPAD_L2 12 +#define RETRO_DEVICE_ID_JOYPAD_R2 13 +#define RETRO_DEVICE_ID_JOYPAD_L3 14 +#define RETRO_DEVICE_ID_JOYPAD_R3 15 +#define RETRO_DEVICE_ID_JOYPAD_MASK 256 + +#define RETRO_DEVICE_ID_ANALOG_X 0 +#define RETRO_DEVICE_ID_ANALOG_Y 1 + +#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 +#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 +#define RETRO_DEVICE_INDEX_ANALOG_BUTTON 2 + +#define RETRO_DEVICE_ID_MOUSE_X 0 +#define RETRO_DEVICE_ID_MOUSE_Y 1 +#define RETRO_DEVICE_ID_MOUSE_LEFT 2 +#define RETRO_DEVICE_ID_MOUSE_RIGHT 3 +#define RETRO_DEVICE_ID_MOUSE_WHEELUP 4 +#define RETRO_DEVICE_ID_MOUSE_WHEELDOWN 5 +#define RETRO_DEVICE_ID_MOUSE_MIDDLE 6 +#define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP 7 +#define RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN 8 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_4 9 +#define RETRO_DEVICE_ID_MOUSE_BUTTON_5 10 + +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X 13 +#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y 14 +#define RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN 15 +#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 +#define RETRO_DEVICE_ID_LIGHTGUN_RELOAD 16 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_A 3 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_B 4 +#define RETRO_DEVICE_ID_LIGHTGUN_START 6 +#define RETRO_DEVICE_ID_LIGHTGUN_SELECT 7 +#define RETRO_DEVICE_ID_LIGHTGUN_AUX_C 8 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP 9 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN 10 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT 11 +#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT 12 + +#define RETRO_DEVICE_ID_POINTER_X 0 +#define RETRO_DEVICE_ID_POINTER_Y 1 +#define RETRO_DEVICE_ID_POINTER_PRESSED 2 +#define RETRO_DEVICE_ID_POINTER_COUNT 3 +#define RETRO_DEVICE_ID_POINTER_IS_OFFSCREEN 15 + +#define RETRO_REGION_NTSC 0 +#define RETRO_REGION_PAL 1 + +#define RETRO_MEMORY_MASK 0xff +#define RETRO_MEMORY_SAVE_RAM 0 +#define RETRO_MEMORY_RTC 1 +#define RETRO_MEMORY_SYSTEM_RAM 2 +#define RETRO_MEMORY_VIDEO_RAM 3 + +enum retro_key +{ + RETROK_UNKNOWN = 0, + RETROK_FIRST = 0, + RETROK_BACKSPACE = 8, + RETROK_TAB = 9, + RETROK_CLEAR = 12, + RETROK_RETURN = 13, + RETROK_PAUSE = 19, + RETROK_ESCAPE = 27, + RETROK_SPACE = 32, + RETROK_EXCLAIM = 33, + RETROK_QUOTEDBL = 34, + RETROK_HASH = 35, + RETROK_DOLLAR = 36, + RETROK_AMPERSAND = 38, + RETROK_QUOTE = 39, + RETROK_LEFTPAREN = 40, + RETROK_RIGHTPAREN = 41, + RETROK_ASTERISK = 42, + RETROK_PLUS = 43, + RETROK_COMMA = 44, + RETROK_MINUS = 45, + RETROK_PERIOD = 46, + RETROK_SLASH = 47, + RETROK_0 = 48, + RETROK_1 = 49, + RETROK_2 = 50, + RETROK_3 = 51, + RETROK_4 = 52, + RETROK_5 = 53, + RETROK_6 = 54, + RETROK_7 = 55, + RETROK_8 = 56, + RETROK_9 = 57, + RETROK_COLON = 58, + RETROK_SEMICOLON = 59, + RETROK_LESS = 60, + RETROK_EQUALS = 61, + RETROK_GREATER = 62, + RETROK_QUESTION = 63, + RETROK_AT = 64, + RETROK_LEFTBRACKET = 91, + RETROK_BACKSLASH = 92, + RETROK_RIGHTBRACKET = 93, + RETROK_CARET = 94, + RETROK_UNDERSCORE = 95, + RETROK_BACKQUOTE = 96, + RETROK_a = 97, + RETROK_b = 98, + RETROK_c = 99, + RETROK_d = 100, + RETROK_e = 101, + RETROK_f = 102, + RETROK_g = 103, + RETROK_h = 104, + RETROK_i = 105, + RETROK_j = 106, + RETROK_k = 107, + RETROK_l = 108, + RETROK_m = 109, + RETROK_n = 110, + RETROK_o = 111, + RETROK_p = 112, + RETROK_q = 113, + RETROK_r = 114, + RETROK_s = 115, + RETROK_t = 116, + RETROK_u = 117, + RETROK_v = 118, + RETROK_w = 119, + RETROK_x = 120, + RETROK_y = 121, + RETROK_z = 122, + RETROK_LEFTBRACE = 123, + RETROK_BAR = 124, + RETROK_RIGHTBRACE = 125, + RETROK_TILDE = 126, + RETROK_DELETE = 127, + + RETROK_KP0 = 256, + RETROK_KP1 = 257, + RETROK_KP2 = 258, + RETROK_KP3 = 259, + RETROK_KP4 = 260, + RETROK_KP5 = 261, + RETROK_KP6 = 262, + RETROK_KP7 = 263, + RETROK_KP8 = 264, + RETROK_KP9 = 265, + RETROK_KP_PERIOD = 266, + RETROK_KP_DIVIDE = 267, + RETROK_KP_MULTIPLY = 268, + RETROK_KP_MINUS = 269, + RETROK_KP_PLUS = 270, + RETROK_KP_ENTER = 271, + RETROK_KP_EQUALS = 272, + + RETROK_UP = 273, + RETROK_DOWN = 274, + RETROK_RIGHT = 275, + RETROK_LEFT = 276, + RETROK_INSERT = 277, + RETROK_HOME = 278, + RETROK_END = 279, + RETROK_PAGEUP = 280, + RETROK_PAGEDOWN = 281, + + RETROK_F1 = 282, + RETROK_F2 = 283, + RETROK_F3 = 284, + RETROK_F4 = 285, + RETROK_F5 = 286, + RETROK_F6 = 287, + RETROK_F7 = 288, + RETROK_F8 = 289, + RETROK_F9 = 290, + RETROK_F10 = 291, + RETROK_F11 = 292, + RETROK_F12 = 293, + RETROK_F13 = 294, + RETROK_F14 = 295, + RETROK_F15 = 296, + + RETROK_NUMLOCK = 300, + RETROK_CAPSLOCK = 301, + RETROK_SCROLLOCK = 302, + RETROK_RSHIFT = 303, + RETROK_LSHIFT = 304, + RETROK_RCTRL = 305, + RETROK_LCTRL = 306, + RETROK_RALT = 307, + RETROK_LALT = 308, + RETROK_RMETA = 309, + RETROK_LMETA = 310, + RETROK_LSUPER = 311, + RETROK_RSUPER = 312, + RETROK_MODE = 313, + RETROK_COMPOSE = 314, + + RETROK_HELP = 315, + RETROK_PRINT = 316, + RETROK_SYSREQ = 317, + RETROK_BREAK = 318, + RETROK_MENU = 319, + RETROK_POWER = 320, + RETROK_EURO = 321, + RETROK_UNDO = 322, + RETROK_OEM_102 = 323, + + RETROK_LAST, + + RETROK_DUMMY = INT_MAX +}; + +enum retro_mod +{ + RETROKMOD_NONE = 0x0000, + RETROKMOD_SHIFT = 0x01, + RETROKMOD_CTRL = 0x02, + RETROKMOD_ALT = 0x04, + RETROKMOD_META = 0x08, + RETROKMOD_NUMLOCK = 0x10, + RETROKMOD_CAPSLOCK = 0x20, + RETROKMOD_SCROLLOCK = 0x40, + + RETROKMOD_DUMMY = INT_MAX +}; + +#define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000 +#define RETRO_ENVIRONMENT_PRIVATE 0x20000 + +#define RETRO_ENVIRONMENT_SET_ROTATION 1 +#define RETRO_ENVIRONMENT_GET_OVERSCAN 2 +#define RETRO_ENVIRONMENT_GET_CAN_DUPE 3 +#define RETRO_ENVIRONMENT_SET_MESSAGE 6 +#define RETRO_ENVIRONMENT_SHUTDOWN 7 +#define RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL 8 +#define RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY 9 +#define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10 +#define RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS 11 +#define RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK 12 +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE 13 +#define RETRO_ENVIRONMENT_SET_HW_RENDER 14 +#define RETRO_ENVIRONMENT_GET_VARIABLE 15 +#define RETRO_ENVIRONMENT_SET_VARIABLES 16 +#define RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE 17 +#define RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME 18 +#define RETRO_ENVIRONMENT_GET_LIBRETRO_PATH 19 +#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 +#define RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK 22 +#define RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE 23 +#define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24 +#define RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE (25 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE (26 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_LOG_INTERFACE 27 +#define RETRO_ENVIRONMENT_GET_PERF_INTERFACE 28 +#define RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE 29 +#define RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY 30 +#define RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY 30 +#define RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY 31 +#define RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO 32 +#define RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK 33 +#define RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO 34 +#define RETRO_ENVIRONMENT_SET_CONTROLLER_INFO 35 +#define RETRO_ENVIRONMENT_SET_MEMORY_MAPS (36 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_SET_GEOMETRY 37 +#define RETRO_ENVIRONMENT_GET_USERNAME 38 +#define RETRO_ENVIRONMENT_GET_LANGUAGE 39 +#define RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER (40 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE (41 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS (42 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE (43 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS 44 +#define RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT (44 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_VFS_INTERFACE (45 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_LED_INTERFACE (46 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE (47 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_MIDI_INTERFACE (48 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_FASTFORWARDING (49 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE (50 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_INPUT_BITMASKS (51 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION 52 +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS 53 +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL 54 +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY 55 +#define RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER 56 +#define RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION 57 +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE 58 +#define RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION 59 +#define RETRO_ENVIRONMENT_SET_MESSAGE_EXT 60 +#define RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS 61 +#define RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK 62 +#define RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY 63 +#define RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE 64 +#define RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE 65 +#define RETRO_ENVIRONMENT_GET_GAME_INFO_EXT 66 +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 67 +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL 68 +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK 69 +#define RETRO_ENVIRONMENT_SET_VARIABLE 70 +#define RETRO_ENVIRONMENT_GET_THROTTLE_STATE (71 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT (72 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT (73 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_JIT_CAPABLE 74 +#define RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE (75 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE 78 +#define RETRO_ENVIRONMENT_GET_DEVICE_POWER (77 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY 79 +#define RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY 80 + +/* VFS API */ +#define RETRO_VFS_FILE_ACCESS_READ (1 << 0) +#define RETRO_VFS_FILE_ACCESS_WRITE (1 << 1) +#define RETRO_VFS_FILE_ACCESS_READ_WRITE (RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE) +#define RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING (1 << 2) + +#define RETRO_VFS_FILE_ACCESS_HINT_NONE (0) +#define RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS (1 << 0) + +#define RETRO_VFS_SEEK_POSITION_START 0 +#define RETRO_VFS_SEEK_POSITION_CURRENT 1 +#define RETRO_VFS_SEEK_POSITION_END 2 + +#define RETRO_VFS_STAT_IS_VALID (1 << 0) +#define RETRO_VFS_STAT_IS_DIRECTORY (1 << 1) +#define RETRO_VFS_STAT_IS_CHARACTER_SPECIAL (1 << 2) + +struct retro_vfs_file_handle; +struct retro_vfs_dir_handle; + +typedef const char *(RETRO_CALLCONV *retro_vfs_get_path_t)(struct retro_vfs_file_handle *stream); +typedef struct retro_vfs_file_handle *(RETRO_CALLCONV *retro_vfs_open_t)(const char *path, unsigned mode, unsigned hints); +typedef int (RETRO_CALLCONV *retro_vfs_close_t)(struct retro_vfs_file_handle *stream); +typedef int64_t (RETRO_CALLCONV *retro_vfs_size_t)(struct retro_vfs_file_handle *stream); +typedef int64_t (RETRO_CALLCONV *retro_vfs_tell_t)(struct retro_vfs_file_handle *stream); +typedef int64_t (RETRO_CALLCONV *retro_vfs_seek_t)(struct retro_vfs_file_handle *stream, int64_t offset, int seek_position); +typedef int64_t (RETRO_CALLCONV *retro_vfs_read_t)(struct retro_vfs_file_handle *stream, void *s, uint64_t len); +typedef int64_t (RETRO_CALLCONV *retro_vfs_write_t)(struct retro_vfs_file_handle *stream, const void *s, uint64_t len); +typedef int (RETRO_CALLCONV *retro_vfs_flush_t)(struct retro_vfs_file_handle *stream); +typedef int (RETRO_CALLCONV *retro_vfs_remove_t)(const char *path); +typedef int (RETRO_CALLCONV *retro_vfs_rename_t)(const char *old_path, const char *new_path); +typedef int64_t (RETRO_CALLCONV *retro_vfs_truncate_t)(struct retro_vfs_file_handle *stream, int64_t length); +typedef int (RETRO_CALLCONV *retro_vfs_stat_t)(const char *path, int32_t *size); +typedef int (RETRO_CALLCONV *retro_vfs_mkdir_t)(const char *dir); +typedef struct retro_vfs_dir_handle *(RETRO_CALLCONV *retro_vfs_opendir_t)(const char *dir, bool include_hidden); +typedef bool (RETRO_CALLCONV *retro_vfs_readdir_t)(struct retro_vfs_dir_handle *dirstream); +typedef const char *(RETRO_CALLCONV *retro_vfs_dirent_get_name_t)(struct retro_vfs_dir_handle *dirstream); +typedef bool (RETRO_CALLCONV *retro_vfs_dirent_is_dir_t)(struct retro_vfs_dir_handle *dirstream); +typedef int (RETRO_CALLCONV *retro_vfs_closedir_t)(struct retro_vfs_dir_handle *dirstream); + +struct retro_vfs_interface +{ + retro_vfs_get_path_t get_path; + retro_vfs_open_t open; + retro_vfs_close_t close; + retro_vfs_size_t size; + retro_vfs_tell_t tell; + retro_vfs_seek_t seek; + retro_vfs_read_t read; + retro_vfs_write_t write; + retro_vfs_flush_t flush; + retro_vfs_remove_t remove; + retro_vfs_rename_t rename; + retro_vfs_truncate_t truncate; + retro_vfs_stat_t stat; + retro_vfs_mkdir_t mkdir; + retro_vfs_opendir_t opendir; + retro_vfs_readdir_t readdir; + retro_vfs_dirent_get_name_t dirent_get_name; + retro_vfs_dirent_is_dir_t dirent_is_dir; + retro_vfs_closedir_t closedir; +}; + +#define RETRO_VFS_INTERFACE_VERSION 3 + +struct retro_vfs_interface_info +{ + uint32_t required_interface_version; + struct retro_vfs_interface *iface; +}; + +enum retro_pixel_format +{ + RETRO_PIXEL_FORMAT_0RGB1555 = 0, + RETRO_PIXEL_FORMAT_XRGB8888 = 1, + RETRO_PIXEL_FORMAT_RGB565 = 2, + RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX +}; + +struct retro_message +{ + const char *msg; + unsigned frames; +}; + +enum retro_message_target +{ + RETRO_MESSAGE_TARGET_ALL = 0, + RETRO_MESSAGE_TARGET_OSD, + RETRO_MESSAGE_TARGET_LOG +}; + +enum retro_message_type +{ + RETRO_MESSAGE_TYPE_NOTIFICATION = 0, + RETRO_MESSAGE_TYPE_NOTIFICATION_ALT, + RETRO_MESSAGE_TYPE_STATUS, + RETRO_MESSAGE_TYPE_PROGRESS +}; + +struct retro_message_ext +{ + const char *msg; + unsigned duration; + unsigned priority; + enum retro_message_target target; + enum retro_message_type type; + int8_t progress; +}; + +struct retro_input_descriptor +{ + unsigned port; + unsigned device; + unsigned index; + unsigned id; + const char *description; +}; + +struct retro_system_info +{ + const char *library_name; + const char *library_version; + const char *valid_extensions; + bool need_fullpath; + bool block_extract; +}; + +struct retro_game_geometry +{ + unsigned base_width; + unsigned base_height; + unsigned max_width; + unsigned max_height; + float aspect_ratio; +}; + +struct retro_system_timing +{ + double fps; + double sample_rate; +}; + +struct retro_system_av_info +{ + struct retro_game_geometry geometry; + struct retro_system_timing timing; +}; + +struct retro_variable +{ + const char *key; + const char *value; +}; + +struct retro_core_option_display +{ + const char *key; + bool visible; +}; + +#define RETRO_NUM_CORE_OPTION_VALUES_MAX 128 + +struct retro_core_option_value +{ + const char *value; + const char *label; +}; + +struct retro_core_option_definition +{ + const char *key; + const char *desc; + const char *info; + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; + const char *default_value; +}; + +struct retro_core_options_intl +{ + struct retro_core_option_definition *us; + struct retro_core_option_definition *local; +}; + +struct retro_core_option_v2_category +{ + const char *key; + const char *desc; + const char *info; +}; + +struct retro_core_option_v2_definition +{ + const char *key; + const char *desc; + const char *desc_categorized; + const char *info; + const char *info_categorized; + const char *category_key; + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; + const char *default_value; +}; + +struct retro_core_options_v2 +{ + struct retro_core_option_v2_category *categories; + struct retro_core_option_v2_definition *definitions; +}; + +struct retro_core_options_v2_intl +{ + struct retro_core_options_v2 *us; + struct retro_core_options_v2 *local; +}; + +struct retro_core_options_update_display_callback +{ + bool (RETRO_CALLCONV *callback)(void); +}; + +struct retro_game_info +{ + const char *path; + const void *data; + size_t size; + const char *meta; +}; + +struct retro_game_info_ext +{ + const char *full_path; + const char *archive_path; + const char *archive_file; + const char *dir; + const char *name; + const char *ext; + const char *meta; + const void *data; + size_t size; + bool file_in_archive; + bool persistent_data; +}; + +#define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1) + +typedef void (RETRO_CALLCONV *retro_hw_context_reset_t)(void); +typedef uintptr_t (RETRO_CALLCONV *retro_hw_get_current_framebuffer_t)(void); +typedef void (*retro_proc_address_t)(void); +typedef retro_proc_address_t (RETRO_CALLCONV *retro_hw_get_proc_address_t)(const char *sym); + +enum retro_hw_context_type +{ + RETRO_HW_CONTEXT_NONE = 0, + RETRO_HW_CONTEXT_OPENGL = 1, + RETRO_HW_CONTEXT_OPENGLES2 = 2, + RETRO_HW_CONTEXT_OPENGL_CORE = 3, + RETRO_HW_CONTEXT_OPENGLES3 = 4, + RETRO_HW_CONTEXT_OPENGLES_VERSION = 5, + RETRO_HW_CONTEXT_VULKAN = 6, + RETRO_HW_CONTEXT_D3D11 = 7, + RETRO_HW_CONTEXT_D3D10 = 8, + RETRO_HW_CONTEXT_D3D12 = 9, + RETRO_HW_CONTEXT_D3D9 = 10, + RETRO_HW_CONTEXT_DUMMY = INT_MAX +}; + +struct retro_hw_render_callback +{ + enum retro_hw_context_type context_type; + retro_hw_context_reset_t context_reset; + retro_hw_get_current_framebuffer_t get_current_framebuffer; + retro_hw_get_proc_address_t get_proc_address; + bool depth; + bool stencil; + bool bottom_left_origin; + unsigned version_major; + unsigned version_minor; + bool cache_context; + retro_hw_context_reset_t context_destroy; + bool debug_context; +}; + +typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode, + uint32_t character, uint16_t key_modifiers); + +struct retro_keyboard_callback +{ + retro_keyboard_event_t callback; +}; + +typedef bool (RETRO_CALLCONV *retro_set_eject_state_t)(bool ejected); +typedef bool (RETRO_CALLCONV *retro_get_eject_state_t)(void); +typedef unsigned (RETRO_CALLCONV *retro_get_image_index_t)(void); +typedef bool (RETRO_CALLCONV *retro_set_image_index_t)(unsigned index); +typedef unsigned (RETRO_CALLCONV *retro_get_num_images_t)(void); +typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index, const struct retro_game_info *info); +typedef bool (RETRO_CALLCONV *retro_add_image_index_t)(void); +typedef bool (RETRO_CALLCONV *retro_set_initial_image_t)(unsigned index, const char *path); +typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *path, size_t len); +typedef bool (RETRO_CALLCONV *retro_get_image_label_t)(unsigned index, char *label, size_t len); + +struct retro_disk_control_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; +}; + +struct retro_disk_control_ext_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; + retro_set_initial_image_t set_initial_image; + retro_get_image_path_t get_image_path; + retro_get_image_label_t get_image_label; +}; + +enum retro_hw_render_interface_type +{ + RETRO_HW_RENDER_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_INTERFACE_D3D9 = 1, + RETRO_HW_RENDER_INTERFACE_D3D10 = 2, + RETRO_HW_RENDER_INTERFACE_D3D11 = 3, + RETRO_HW_RENDER_INTERFACE_D3D12 = 4, + RETRO_HW_RENDER_INTERFACE_GSKIT_PS2 = 5, + RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX +}; + +struct retro_hw_render_interface +{ + enum retro_hw_render_interface_type interface_type; + unsigned interface_version; +}; + +enum retro_hw_render_context_negotiation_interface_type +{ + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_DUMMY = INT_MAX +}; + +struct retro_hw_render_context_negotiation_interface +{ + enum retro_hw_render_context_negotiation_interface_type interface_type; + unsigned interface_version; +}; + +typedef void (RETRO_CALLCONV *retro_frame_time_callback_t)(int64_t usec); + +struct retro_frame_time_callback +{ + retro_frame_time_callback_t callback; + int64_t reference; +}; + +typedef int64_t retro_usec_t; + +struct retro_audio_callback +{ + void (RETRO_CALLCONV *callback)(void); + bool (RETRO_CALLCONV *set_state)(bool enabled); +}; + +typedef bool (RETRO_CALLCONV *retro_set_rumble_state_t)(unsigned port, enum retro_rumble_effect effect, uint16_t strength); + +enum retro_rumble_effect +{ + RETRO_RUMBLE_STRONG = 0, + RETRO_RUMBLE_WEAK = 1, + RETRO_RUMBLE_DUMMY = INT_MAX +}; + +struct retro_rumble_interface +{ + retro_set_rumble_state_t set_rumble_state; +}; + +typedef void (RETRO_CALLCONV *retro_audio_sample_t)(int16_t left, int16_t right); +typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_t)(const int16_t *data, size_t frames); +typedef int16_t (RETRO_CALLCONV *retro_input_state_t)(unsigned port, unsigned device, unsigned index, unsigned id); +typedef void (RETRO_CALLCONV *retro_input_poll_t)(void); + +typedef bool (RETRO_CALLCONV *retro_environment_t)(unsigned cmd, void *data); +typedef void (RETRO_CALLCONV *retro_video_refresh_t)(const void *data, unsigned width, unsigned height, size_t pitch); + +typedef void (RETRO_CALLCONV *retro_log_printf_t)(enum retro_log_level level, const char *fmt, ...); + +enum retro_log_level +{ + RETRO_LOG_DEBUG = 0, + RETRO_LOG_INFO, + RETRO_LOG_WARN, + RETRO_LOG_ERROR, + RETRO_LOG_DUMMY = INT_MAX +}; + +struct retro_log_callback +{ + retro_log_printf_t log; +}; + +enum retro_sensor_action +{ + RETRO_SENSOR_ACCELEROMETER_ENABLE = 0, + RETRO_SENSOR_ACCELEROMETER_DISABLE, + RETRO_SENSOR_GYROSCOPE_ENABLE, + RETRO_SENSOR_GYROSCOPE_DISABLE, + RETRO_SENSOR_ILLUMINANCE_ENABLE, + RETRO_SENSOR_ILLUMINANCE_DISABLE, + RETRO_SENSOR_DUMMY = INT_MAX +}; + +#define RETRO_SENSOR_ACCELEROMETER_X 0 +#define RETRO_SENSOR_ACCELEROMETER_Y 1 +#define RETRO_SENSOR_ACCELEROMETER_Z 2 +#define RETRO_SENSOR_GYROSCOPE_X 3 +#define RETRO_SENSOR_GYROSCOPE_Y 4 +#define RETRO_SENSOR_GYROSCOPE_Z 5 +#define RETRO_SENSOR_ILLUMINANCE 6 + +typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port, enum retro_sensor_action action, unsigned rate); +typedef float (RETRO_CALLCONV *retro_sensor_get_input_t)(unsigned port, unsigned id); + +struct retro_sensor_interface +{ + retro_set_sensor_state_t set_sensor_state; + retro_sensor_get_input_t get_sensor_input; +}; + +enum retro_camera_buffer +{ + RETRO_CAMERA_BUFFER_OPENGL_TEXTURE = 0, + RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER, + RETRO_CAMERA_BUFFER_DUMMY = INT_MAX +}; + +typedef bool (RETRO_CALLCONV *retro_camera_start_t)(void); +typedef void (RETRO_CALLCONV *retro_camera_stop_t)(void); +typedef void (RETRO_CALLCONV *retro_camera_lifetime_status_t)(void); +typedef void (RETRO_CALLCONV *retro_camera_frame_raw_framebuffer_t)(const uint32_t *buffer, unsigned width, unsigned height, size_t pitch); +typedef void (RETRO_CALLCONV *retro_camera_frame_opengl_texture_t)(unsigned texture_id, unsigned texture_target, const float *affine); + +struct retro_camera_callback +{ + uint64_t caps; + unsigned width; + unsigned height; + retro_camera_start_t start; + retro_camera_stop_t stop; + retro_camera_frame_raw_framebuffer_t frame_raw_framebuffer; + retro_camera_frame_opengl_texture_t frame_opengl_texture; + retro_camera_lifetime_status_t initialized; + retro_camera_lifetime_status_t deinitialized; +}; + +typedef void (RETRO_CALLCONV *retro_location_set_interval_t)(unsigned interval_ms, unsigned interval_distance); +typedef bool (RETRO_CALLCONV *retro_location_start_t)(void); +typedef void (RETRO_CALLCONV *retro_location_stop_t)(void); +typedef bool (RETRO_CALLCONV *retro_location_get_position_t)(double *lat, double *lon, double *horiz_accuracy, double *vert_accuracy); +typedef void (RETRO_CALLCONV *retro_location_lifetime_status_t)(void); + +struct retro_location_callback +{ + retro_location_start_t start; + retro_location_stop_t stop; + retro_location_get_position_t get_position; + retro_location_set_interval_t set_interval; + retro_location_lifetime_status_t initialized; + retro_location_lifetime_status_t deinitialized; +}; + +struct retro_perf_counter +{ + const char *ident; + uint64_t start; + uint64_t total; + uint64_t call_cnt; + bool registered; +}; + +typedef int64_t retro_perf_tick_t; +typedef int64_t retro_time_t; + +typedef retro_time_t (RETRO_CALLCONV *retro_perf_get_time_usec_t)(void); +typedef retro_perf_tick_t (RETRO_CALLCONV *retro_perf_get_counter_t)(void); +typedef uint64_t (RETRO_CALLCONV *retro_get_cpu_features_t)(void); +typedef void (RETRO_CALLCONV *retro_perf_log_t)(void); +typedef void (RETRO_CALLCONV *retro_perf_register_t)(struct retro_perf_counter *counter); +typedef void (RETRO_CALLCONV *retro_perf_start_t)(struct retro_perf_counter *counter); +typedef void (RETRO_CALLCONV *retro_perf_stop_t)(struct retro_perf_counter *counter); + +struct retro_perf_callback +{ + retro_perf_get_time_usec_t get_time_usec; + retro_perf_get_counter_t get_cpu_features; + retro_perf_get_counter_t get_perf_counter; + retro_perf_register_t perf_register; + retro_perf_start_t perf_start; + retro_perf_stop_t perf_stop; + retro_perf_log_t perf_log; +}; + +#define RETRO_SIMD_SSE (1 << 0) +#define RETRO_SIMD_SSE2 (1 << 1) +#define RETRO_SIMD_VMX (1 << 2) +#define RETRO_SIMD_VMX128 (1 << 3) +#define RETRO_SIMD_AVX (1 << 4) +#define RETRO_SIMD_NEON (1 << 5) +#define RETRO_SIMD_SSE3 (1 << 6) +#define RETRO_SIMD_SSSE3 (1 << 7) +#define RETRO_SIMD_MMX (1 << 8) +#define RETRO_SIMD_MMXEXT (1 << 9) +#define RETRO_SIMD_SSE4 (1 << 10) +#define RETRO_SIMD_SSE42 (1 << 11) +#define RETRO_SIMD_AVX2 (1 << 12) +#define RETRO_SIMD_VFPU (1 << 13) +#define RETRO_SIMD_PS (1 << 14) +#define RETRO_SIMD_AES (1 << 15) +#define RETRO_SIMD_VFPV3 (1 << 16) +#define RETRO_SIMD_VFPV4 (1 << 17) +#define RETRO_SIMD_POPCNT (1 << 18) +#define RETRO_SIMD_MOVBE (1 << 19) +#define RETRO_SIMD_CMOV (1 << 20) +#define RETRO_SIMD_ASIMD (1 << 21) + +struct retro_subsystem_memory_info +{ + const char *extension; + unsigned type; +}; + +struct retro_subsystem_rom_info +{ + const char *desc; + const char *valid_extensions; + bool need_fullpath; + bool block_extract; + bool required; + struct retro_subsystem_memory_info *memory; + unsigned num_memory; +}; + +struct retro_subsystem_info +{ + const char *desc; + const char *ident; + struct retro_subsystem_rom_info *roms; + unsigned num_roms; + unsigned id; +}; + +struct retro_controller_description +{ + const char *desc; + unsigned id; +}; + +struct retro_controller_info +{ + const struct retro_controller_description *types; + unsigned num_types; +}; + +struct retro_memory_descriptor +{ + uint64_t flags; + void *ptr; + size_t offset; + size_t start; + size_t select; + size_t disconnect; + size_t len; + const char *addrspace; +}; + +#define RETRO_MEMDESC_CONST (1 << 0) +#define RETRO_MEMDESC_BIGENDIAN (1 << 1) +#define RETRO_MEMDESC_SYSTEM_RAM (1 << 2) +#define RETRO_MEMDESC_SAVE_RAM (1 << 3) +#define RETRO_MEMDESC_VIDEO_RAM (1 << 4) +#define RETRO_MEMDESC_ALIGN_2 (1 << 16) +#define RETRO_MEMDESC_ALIGN_4 (2 << 16) +#define RETRO_MEMDESC_ALIGN_8 (3 << 16) +#define RETRO_MEMDESC_MINSIZE_2 (1 << 24) +#define RETRO_MEMDESC_MINSIZE_4 (2 << 24) +#define RETRO_MEMDESC_MINSIZE_8 (3 << 24) + +struct retro_memory_map +{ + const struct retro_memory_descriptor *descriptors; + unsigned num_descriptors; +}; + +struct retro_framebuffer +{ + void *data; + unsigned width; + unsigned height; + size_t pitch; + enum retro_pixel_format format; + unsigned access_flags; + unsigned memory_flags; +}; + +#define RETRO_MEMORY_ACCESS_WRITE (1 << 0) +#define RETRO_MEMORY_ACCESS_READ (1 << 1) +#define RETRO_MEMORY_TYPE_CACHED (1 << 0) + +enum retro_savestate_context +{ + RETRO_SAVESTATE_CONTEXT_NORMAL = 0, + RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_INSTANCE = 1, + RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_BINARY = 2, + RETRO_SAVESTATE_CONTEXT_ROLLBACK_NETPLAY = 3, + RETRO_SAVESTATE_CONTEXT_UNKNOWN = INT_MAX +}; + +#define RETRO_THROTTLE_NONE 0 +#define RETRO_THROTTLE_FRAME_STEPPING 1 +#define RETRO_THROTTLE_FAST_FORWARD 2 +#define RETRO_THROTTLE_SLOW_MOTION 3 +#define RETRO_THROTTLE_REWINDING 4 +#define RETRO_THROTTLE_VSYNC 5 +#define RETRO_THROTTLE_UNBLOCKED 6 + +struct retro_throttle_state +{ + unsigned mode; + float rate; +}; + +enum retro_language +{ + RETRO_LANGUAGE_ENGLISH = 0, + RETRO_LANGUAGE_JAPANESE = 1, + RETRO_LANGUAGE_FRENCH = 2, + RETRO_LANGUAGE_SPANISH = 3, + RETRO_LANGUAGE_GERMAN = 4, + RETRO_LANGUAGE_ITALIAN = 5, + RETRO_LANGUAGE_DUTCH = 6, + RETRO_LANGUAGE_PORTUGUESE_BRAZIL = 7, + RETRO_LANGUAGE_PORTUGUESE_PORTUGAL = 8, + RETRO_LANGUAGE_RUSSIAN = 9, + RETRO_LANGUAGE_KOREAN = 10, + RETRO_LANGUAGE_CHINESE_TRADITIONAL = 11, + RETRO_LANGUAGE_CHINESE_SIMPLIFIED = 12, + RETRO_LANGUAGE_ESPERANTO = 13, + RETRO_LANGUAGE_POLISH = 14, + RETRO_LANGUAGE_VIETNAMESE = 15, + RETRO_LANGUAGE_ARABIC = 16, + RETRO_LANGUAGE_GREEK = 17, + RETRO_LANGUAGE_TURKISH = 18, + RETRO_LANGUAGE_SLOVAK = 19, + RETRO_LANGUAGE_PERSIAN = 20, + RETRO_LANGUAGE_HEBREW = 21, + RETRO_LANGUAGE_ASTURIAN = 22, + RETRO_LANGUAGE_FINNISH = 23, + RETRO_LANGUAGE_INDONESIAN = 24, + RETRO_LANGUAGE_SWEDISH = 25, + RETRO_LANGUAGE_UKRAINIAN = 26, + RETRO_LANGUAGE_CZECH = 27, + RETRO_LANGUAGE_CATALAN_VALENCIA = 28, + RETRO_LANGUAGE_CATALAN = 29, + RETRO_LANGUAGE_BRITISH_ENGLISH = 30, + RETRO_LANGUAGE_HUNGARIAN = 31, + RETRO_LANGUAGE_BELARUSIAN = 32, + RETRO_LANGUAGE_GALICIAN = 33, + RETRO_LANGUAGE_NORWEGIAN = 34, + RETRO_LANGUAGE_LAST, + RETRO_LANGUAGE_DUMMY = INT_MAX +}; + +#define RETRO_SERIALIZATION_QUIRK_INCOMPLETE (1 << 0) +#define RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE (1 << 1) +#define RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE (1 << 2) +#define RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE (1 << 3) +#define RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION (1 << 4) +#define RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT (1 << 5) +#define RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT (1 << 6) + +/* Libretro API entry points */ +RETRO_API void retro_set_environment(retro_environment_t); +RETRO_API void retro_set_video_refresh(retro_video_refresh_t); +RETRO_API void retro_set_audio_sample(retro_audio_sample_t); +RETRO_API void retro_set_audio_sample_batch(retro_audio_sample_batch_t); +RETRO_API void retro_set_input_poll(retro_input_poll_t); +RETRO_API void retro_set_input_state(retro_input_state_t); +RETRO_API void retro_init(void); +RETRO_API void retro_deinit(void); +RETRO_API unsigned retro_api_version(void); +RETRO_API void retro_get_system_info(struct retro_system_info *info); +RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info); +RETRO_API void retro_set_controller_port_device(unsigned port, unsigned device); +RETRO_API void retro_reset(void); +RETRO_API void retro_run(void); +RETRO_API size_t retro_serialize_size(void); +RETRO_API bool retro_serialize(void *data, size_t size); +RETRO_API bool retro_unserialize(const void *data, size_t size); +RETRO_API void retro_cheat_reset(void); +RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *code); +RETRO_API bool retro_load_game(const struct retro_game_info *game); +RETRO_API bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info); +RETRO_API void retro_unload_game(void); +RETRO_API unsigned retro_get_region(void); +RETRO_API void *retro_get_memory_data(unsigned id); +RETRO_API size_t retro_get_memory_size(unsigned id); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBRETRO_H__ */ diff --git a/src/eden_libretro/libretro_core.cpp b/src/eden_libretro/libretro_core.cpp new file mode 100644 index 0000000000..29f75e7e2d --- /dev/null +++ b/src/eden_libretro/libretro_core.cpp @@ -0,0 +1,730 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/fs/path_util.h" + +#include + +#include "eden_libretro/libretro.h" +#include "eden_libretro/libretro_vfs.h" +#include "eden_libretro/emu_window_libretro.h" + +#include "common/common_types.h" +#include "common/detached_tasks.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "common/settings.h" +#include "common/string_util.h" + +#include "core/core.h" +#include "core/cpu_manager.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/vfs/vfs_real.h" +#include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "core/perf_stats.h" + +#include "video_core/renderer_base.h" +#include "video_core/gpu.h" + +#include "hid_core/hid_core.h" + +#include "audio_core/audio_core.h" + +#include "input_common/main.h" +#include "input_common/drivers/virtual_gamepad.h" + +// Global state +namespace { + +// Libretro callbacks +retro_environment_t environ_cb = nullptr; +retro_video_refresh_t video_cb = nullptr; +retro_audio_sample_t audio_sample_cb = nullptr; +retro_audio_sample_batch_t audio_batch_cb = nullptr; +retro_input_poll_t input_poll_cb = nullptr; +retro_input_state_t input_state_cb = nullptr; +retro_log_printf_t log_cb = nullptr; + +// Hardware rendering +retro_hw_render_callback hw_render = {}; +bool hw_context_ready = false; + +// Core state +std::unique_ptr emu_system; +std::unique_ptr emu_window; +std::unique_ptr detached_tasks; +std::unique_ptr input_subsystem; + +std::string game_path; +std::string system_directory; +std::string save_directory; + +std::atomic is_running{false}; +std::atomic game_loaded{false}; +std::atomic is_initialized{false}; +std::atomic frame_count{0}; +std::atomic has_new_frame{false}; +std::mutex emu_mutex; + +// Audio buffer +std::vector audio_buffer; +constexpr size_t AUDIO_BUFFER_SIZE = 2048 * 2; // Stereo samples + +// Screen dimensions +constexpr unsigned SCREEN_WIDTH = 1280; +constexpr unsigned SCREEN_HEIGHT = 720; +constexpr double FPS = 60.0; +constexpr double SAMPLE_RATE = 48000.0; + +// Input mapping: libretro -> Switch +struct InputMapping { + unsigned retro_id; + int switch_button; +}; + +// Log callback wrapper +void LibretroLog(retro_log_level level, const char* fmt, ...) { + if (!log_cb) return; + + char buffer[4096]; + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + + log_cb(level, "%s", buffer); +} + +// Context reset callback - called when OpenGL context is ready +void ContextReset() { + try { + LOG_INFO(Frontend, "Libretro: OpenGL context reset"); + hw_context_ready = true; + + if (emu_window) { + emu_window->SetContextReady(true); + } + + LibretroLog(RETRO_LOG_INFO, "Eden: OpenGL context ready\n"); + + // Initialize OpenGL function pointers via GLAD + if (hw_render.get_proc_address) { + if (!gladLoadGLLoader(reinterpret_cast(hw_render.get_proc_address))) { + LOG_ERROR(Frontend, "Libretro: Failed to load OpenGL functions via GLAD"); + LibretroLog(RETRO_LOG_ERROR, "Eden: Failed to initialize OpenGL\n"); + return; + } + LOG_INFO(Frontend, "Libretro: OpenGL functions loaded via GLAD - GL {}.{}", + GLVersion.major, GLVersion.minor); + LibretroLog(RETRO_LOG_INFO, "Eden: OpenGL %d.%d initialized\n", GLVersion.major, GLVersion.minor); + } + + // Load game now that OpenGL context is ready + if (game_loaded && emu_system && !is_running && !game_path.empty()) { + LOG_INFO(Frontend, "Libretro: Loading game with OpenGL context ready"); + + Service::AM::FrontendAppletParameters load_params{ + .applet_id = Service::AM::AppletId::Application, + }; + + const Core::SystemResultStatus load_result = emu_system->Load(*emu_window, game_path, load_params); + + if (load_result != Core::SystemResultStatus::Success) { + LOG_ERROR(Frontend, "Libretro: Failed to load game in context reset, error: {}", static_cast(load_result)); + LibretroLog(RETRO_LOG_ERROR, "Eden: Failed to load game, error: %u\n", static_cast(load_result)); + return; + } + + // Enable deferred GPU mode - commands processed on main thread during retro_run + LOG_INFO(Frontend, "Libretro: Enabling deferred GPU mode"); + emu_system->GPU().SetDeferredMode(true); + + // Start GPU and emulation + LOG_INFO(Frontend, "Libretro: Starting GPU after successful load"); + emu_system->GPU().Start(); + emu_system->GetCpuManager().OnGpuReady(); + emu_system->Run(); + is_running = true; + LibretroLog(RETRO_LOG_INFO, "Eden: Emulation started\n"); + } + } catch (const std::exception& e) { + LOG_CRITICAL(Frontend, "EXCEPTION in ContextReset: {}", e.what()); + LibretroLog(RETRO_LOG_ERROR, "Eden: CRITICAL - Exception in ContextReset: %s\n", e.what()); + } catch (...) { + LOG_CRITICAL(Frontend, "UNKNOWN EXCEPTION in ContextReset"); + LibretroLog(RETRO_LOG_ERROR, "Eden: CRITICAL - Unknown exception in ContextReset\n"); + } +} + +// Context destroy callback +void ContextDestroy() { + try { + LOG_INFO(Frontend, "Libretro: OpenGL context destroyed"); + hw_context_ready = false; + + if (emu_window) { + emu_window->SetContextReady(false); + } + + LibretroLog(RETRO_LOG_INFO, "Eden: OpenGL context destroyed\n"); + } catch (...) { + // Ignore exceptions during cleanup + } +} + +// Get current framebuffer +uintptr_t GetCurrentFramebuffer() { + if (emu_window) { + return emu_window->GetCurrentFramebuffer(); + } + return 0; +} + +// Initialize hardware rendering +bool InitHWRender() { + hw_render = {}; + hw_render.context_type = RETRO_HW_CONTEXT_OPENGL_CORE; + hw_render.version_major = 4; + hw_render.version_minor = 6; + hw_render.context_reset = ContextReset; + hw_render.context_destroy = ContextDestroy; + hw_render.get_current_framebuffer = GetCurrentFramebuffer; + hw_render.depth = true; + hw_render.stencil = true; + hw_render.bottom_left_origin = true; + hw_render.cache_context = true; + hw_render.debug_context = false; + + if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) { + LOG_ERROR(Frontend, "Libretro: Failed to set HW render callback for OpenGL 4.6 Core"); + + // Try OpenGL 4.3 Core + hw_render.version_major = 4; + hw_render.version_minor = 3; + if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) { + LOG_ERROR(Frontend, "Libretro: Failed to set HW render callback for OpenGL 4.3 Core"); + + // Try OpenGL 3.3 Core + hw_render.version_major = 3; + hw_render.version_minor = 3; + if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) { + LOG_ERROR(Frontend, "Libretro: Failed to set HW render - no suitable OpenGL version"); + return false; + } + } + } + + LOG_INFO(Frontend, "Libretro: HW render initialized with OpenGL {}.{} Core", + hw_render.version_major, hw_render.version_minor); + return true; +} + +// Update input state using VirtualGamepad +void UpdateInput() { + if (!input_subsystem || !input_poll_cb || !input_state_cb) { + return; + } + + try { + + input_poll_cb(); + + auto* virtual_gamepad = input_subsystem->GetVirtualGamepad(); + if (!virtual_gamepad) return; + + using VB = InputCommon::VirtualGamepad::VirtualButton; + using VS = InputCommon::VirtualGamepad::VirtualStick; + + // Map libretro buttons to VirtualGamepad buttons + // Face buttons + virtual_gamepad->SetButtonState(0, VB::ButtonA, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) != 0); + virtual_gamepad->SetButtonState(0, VB::ButtonB, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) != 0); + virtual_gamepad->SetButtonState(0, VB::ButtonX, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X) != 0); + virtual_gamepad->SetButtonState(0, VB::ButtonY, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y) != 0); + + // D-Pad + virtual_gamepad->SetButtonState(0, VB::ButtonUp, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP) != 0); + virtual_gamepad->SetButtonState(0, VB::ButtonDown, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN) != 0); + virtual_gamepad->SetButtonState(0, VB::ButtonLeft, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT) != 0); + virtual_gamepad->SetButtonState(0, VB::ButtonRight, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT) != 0); + + // Shoulder buttons + virtual_gamepad->SetButtonState(0, VB::TriggerL, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L) != 0); + virtual_gamepad->SetButtonState(0, VB::TriggerR, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R) != 0); + virtual_gamepad->SetButtonState(0, VB::TriggerZL, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2) != 0); + virtual_gamepad->SetButtonState(0, VB::TriggerZR, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2) != 0); + + // Stick buttons + virtual_gamepad->SetButtonState(0, VB::StickL, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3) != 0); + virtual_gamepad->SetButtonState(0, VB::StickR, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) != 0); + + // Start/Select (Plus/Minus) + virtual_gamepad->SetButtonState(0, VB::ButtonPlus, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START) != 0); + virtual_gamepad->SetButtonState(0, VB::ButtonMinus, + input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT) != 0); + + // Analog sticks (normalize from -32768..32767 to -1.0..1.0) + auto normalize_axis = [](int16_t value) -> float { + return static_cast(value) / 32767.0f; + }; + + float left_x = normalize_axis(input_state_cb(0, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_LEFT, + RETRO_DEVICE_ID_ANALOG_X)); + float left_y = normalize_axis(-input_state_cb(0, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_LEFT, + RETRO_DEVICE_ID_ANALOG_Y)); + virtual_gamepad->SetStickPosition(0, VS::Left, left_x, left_y); + + float right_x = normalize_axis(input_state_cb(0, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_RIGHT, + RETRO_DEVICE_ID_ANALOG_X)); + float right_y = normalize_axis(-input_state_cb(0, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_RIGHT, + RETRO_DEVICE_ID_ANALOG_Y)); + virtual_gamepad->SetStickPosition(0, VS::Right, right_x, right_y); + } catch (const std::exception& e) { + LOG_ERROR(Frontend, "Exception in UpdateInput: {}", e.what()); + } +} + +// Render audio +void RenderAudio() { + if (!emu_system || !audio_batch_cb) return; + + // Get audio samples from the audio core + // TODO: Integrate with emu_system->AudioCore() for actual audio output + const size_t samples_to_render = 800; // ~60fps at 48000Hz + + if (audio_buffer.size() < samples_to_render * 2) { + audio_buffer.resize(samples_to_render * 2); + } + + // Fill with silence for now - actual audio integration requires more work + std::fill(audio_buffer.begin(), audio_buffer.end(), 0); + + audio_batch_cb(audio_buffer.data(), samples_to_render); +} + +} // anonymous namespace + +// Libretro API implementation +extern "C" { + +void retro_set_environment(retro_environment_t cb) { + environ_cb = cb; + + // Get log interface + struct retro_log_callback log_callback; + if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log_callback)) { + log_cb = log_callback.log; + } + + // We need fullpath for ROM loading + bool need_fullpath = true; + environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, &need_fullpath); + + // Set core options + static const struct retro_variable variables[] = { + { "eden_resolution_scale", "Resolution Scale; 1x|2x|3x|4x" }, + { "eden_use_vsync", "VSync; On|Off" }, + { "eden_use_async_gpu", "Async GPU; On|Off" }, + { "eden_use_multicore", "Multicore CPU; On|Off" }, + { "eden_shader_backend", "Shader Backend; GLSL|SPIRV" }, + { nullptr, nullptr } + }; + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)variables); + + // Set input descriptors + static const struct retro_input_descriptor input_desc[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "X" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Y" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "ZL" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "ZR" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Left Stick" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Right Stick" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Plus" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT,"Minus" }, + { 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" }, + { 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" }, + { 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" }, + { 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" }, + { 0, 0, 0, 0, nullptr } + }; + environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, (void*)input_desc); + + LibretroLog(RETRO_LOG_INFO, "Eden: Environment set\n"); +} + +void retro_set_video_refresh(retro_video_refresh_t cb) { + video_cb = cb; +} + +void retro_set_audio_sample(retro_audio_sample_t cb) { + audio_sample_cb = cb; +} + +void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { + audio_batch_cb = cb; +} + +void retro_set_input_poll(retro_input_poll_t cb) { + input_poll_cb = cb; +} + +void retro_set_input_state(retro_input_state_t cb) { + input_state_cb = cb; +} + +void retro_init(void) { + LOG_INFO(Frontend, "Libretro: retro_init called"); + + // Set main thread ID for OpenGL context tracking + Libretro::LibretroGraphicsContext::SetMainThreadId(); + + // Initialize logging + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); + Common::Log::Start(); + + // Initialize detached tasks + detached_tasks = std::make_unique(); + + // Initialize input subsystem + input_subsystem = std::make_unique(); + input_subsystem->Initialize(); + + // Get system directory + const char* dir = nullptr; + if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) { + system_directory = dir; + // Tell Eden's path manager to use RetroArch's system directory + Common::FS::SetAppDirectory(system_directory); + } else { + system_directory = "."; + } + + // Get save directory + if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir) { + save_directory = dir; + } else { + save_directory = "."; + } + + // Set pixel format + enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; + environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt); + + // Request shared context for multi-threaded GL + bool shared_context = true; + environ_cb(RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT, &shared_context); + + is_initialized = true; + + LibretroLog(RETRO_LOG_INFO, "Eden: Initialized - System dir: %s, Save dir: %s\n", + system_directory.c_str(), save_directory.c_str()); +} + +void retro_deinit(void) { + try { + LOG_INFO(Frontend, "Libretro: retro_deinit called"); + LibretroLog(RETRO_LOG_INFO, "Eden: Deinitializing (frame_count: %llu)\n", frame_count.load()); + + is_initialized = false; + is_running = false; + + if (emu_system) { + LOG_INFO(Frontend, "Shutting down emulation system"); + emu_system->ShutdownMainProcess(); + emu_system.reset(); + LOG_INFO(Frontend, "System shutdown complete"); + } + + emu_window.reset(); + + if (input_subsystem) { + input_subsystem->Shutdown(); + input_subsystem.reset(); + } + + if (detached_tasks) { + detached_tasks->WaitForAllTasks(); + detached_tasks.reset(); + } + + LibretroLog(RETRO_LOG_INFO, "Eden: Deinitialized\n"); + } catch (const std::exception& e) { + LOG_CRITICAL(Frontend, "EXCEPTION in retro_deinit: {}", e.what()); + if (log_cb) { + log_cb(RETRO_LOG_ERROR, "Eden: Exception in deinit: %s\n", e.what()); + } + } catch (...) { + LOG_CRITICAL(Frontend, "UNKNOWN EXCEPTION in retro_deinit"); + if (log_cb) { + log_cb(RETRO_LOG_ERROR, "Eden: Unknown exception in deinit\n"); + } + } +} + +unsigned retro_api_version(void) { + return RETRO_API_VERSION; +} + +void retro_get_system_info(struct retro_system_info* info) { + std::memset(info, 0, sizeof(*info)); + info->library_name = "Eden"; + info->library_version = Common::g_scm_desc; + info->valid_extensions = "nsp|xci|nca|nso|nro"; + info->need_fullpath = true; + info->block_extract = true; +} + +void retro_get_system_av_info(struct retro_system_av_info* info) { + std::memset(info, 0, sizeof(*info)); + + info->geometry.base_width = SCREEN_WIDTH; + info->geometry.base_height = SCREEN_HEIGHT; + info->geometry.max_width = SCREEN_WIDTH * 4; + info->geometry.max_height = SCREEN_HEIGHT * 4; + info->geometry.aspect_ratio = static_cast(SCREEN_WIDTH) / static_cast(SCREEN_HEIGHT); + + info->timing.fps = FPS; + info->timing.sample_rate = SAMPLE_RATE; +} + +void retro_set_controller_port_device(unsigned port, unsigned device) { + LOG_INFO(Frontend, "Libretro: Set controller port {} to device {}", port, device); +} + +void retro_reset(void) { + LOG_INFO(Frontend, "Libretro: retro_reset called"); + + // Reset the emulation + if (emu_system && is_running) { + // Pause, reset state, and resume + emu_system->Pause(); + // Full reset would require reloading the game + emu_system->Run(); + } +} + +void retro_run(void) { + frame_count++; + + if (!emu_system || !is_running) { + if (frame_count % 300 == 0) { + LOG_WARNING(Frontend, "retro_run called but emulation not running (frame {})", frame_count.load()); + } + if (video_cb) { + video_cb(RETRO_HW_FRAME_BUFFER_VALID, SCREEN_WIDTH, SCREEN_HEIGHT, 0); + } + return; + } + + try { + // Update input - wrap in try/catch for safety + if (input_subsystem) { + UpdateInput(); + } + + // Process pending GPU commands and composites on main thread (deferred mode) + // Poll multiple times to allow game threads to make progress + if (emu_system && hw_context_ready) { + try { + auto& gpu = emu_system->GPU(); + + // Process commands multiple times with short sleeps to allow game threads to queue more + for (int i = 0; i < 10; i++) { + gpu.ProcessPendingCommands(); + gpu.ProcessPendingComposites(); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + + has_new_frame = false; + } catch (...) { + // GPU might not be ready yet + } + } + + // Present the frame via HW rendering + if (hw_context_ready && video_cb) { + uintptr_t ra_fbo = hw_render.get_current_framebuffer ? hw_render.get_current_framebuffer() : 0; + glBindFramebuffer(GL_FRAMEBUFFER, static_cast(ra_fbo)); + video_cb(RETRO_HW_FRAME_BUFFER_VALID, SCREEN_WIDTH, SCREEN_HEIGHT, 0); + } + + // Render audio + if (audio_batch_cb) { + RenderAudio(); + } + + if (frame_count % 600 == 0) { + LOG_INFO(Frontend, "retro_run: frame {} rendered", frame_count.load()); + } + } catch (const std::exception& e) { + LOG_ERROR(Frontend, "Exception in retro_run: {}", e.what()); + is_running = false; + } catch (...) { + LOG_ERROR(Frontend, "Unknown exception in retro_run"); + is_running = false; + } +} + +size_t retro_serialize_size(void) { + // Save states not yet supported + return 0; +} + +bool retro_serialize(void* data, size_t size) { + (void)data; + (void)size; + return false; +} + +bool retro_unserialize(const void* data, size_t size) { + (void)data; + (void)size; + return false; +} + +void retro_cheat_reset(void) { + // Cheats not yet supported +} + +void retro_cheat_set(unsigned index, bool enabled, const char* code) { + (void)index; + (void)enabled; + (void)code; +} + +bool retro_load_game(const struct retro_game_info* game) { + if (!game || !game->path) { + LOG_ERROR(Frontend, "Libretro: No game provided"); + return false; + } + + LOG_INFO(Frontend, "Libretro: Loading game: {}", game->path); + LibretroLog(RETRO_LOG_INFO, "Eden: Loading game: %s\n", game->path); + + game_path = game->path; + + // Initialize hardware rendering + if (!InitHWRender()) { + LOG_ERROR(Frontend, "Libretro: Failed to initialize hardware rendering"); + return false; + } + + // Create emulator window + emu_window = std::make_unique(); + emu_window->SetHWRenderCallback(&hw_render); + emu_window->SetFramebufferSize(SCREEN_WIDTH, SCREEN_HEIGHT); + + // Create and initialize the system + emu_system = std::make_unique(); + emu_system->Initialize(); + + // Configure settings for libretro + Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; + Settings::values.use_speed_limit.SetValue(false); + Settings::values.use_multi_core.SetValue(true); + Settings::values.use_disk_shader_cache.SetValue(true); + // CRITICAL: Use sync GPU mode for libretro - OpenGL context is only valid on main thread + Settings::values.use_asynchronous_gpu_emulation.SetValue(false); + + // Apply settings + emu_system->ApplySettings(); + + // Set up filesystem + emu_system->SetContentProvider(std::make_unique()); + emu_system->SetFilesystem(std::make_shared()); + emu_system->GetFileSystemController().CreateFactories(*emu_system->GetFilesystem()); + emu_system->GetUserChannel().clear(); + + // Mark game as ready to load - actual Load() will happen in ContextReset when OpenGL is ready + game_loaded = true; + + LOG_INFO(Frontend, "Libretro: Game setup complete, waiting for OpenGL context"); + LibretroLog(RETRO_LOG_INFO, "Eden: Ready to load game\n"); + + return true; +} + +bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) { + (void)game_type; + (void)info; + (void)num_info; + return false; +} + +void retro_unload_game(void) { + LOG_INFO(Frontend, "Libretro: Unloading game"); + + is_running = false; + game_loaded = false; + + if (emu_system) { + emu_system->Pause(); + emu_system->ShutdownMainProcess(); + emu_system.reset(); + } + + emu_window.reset(); + game_path.clear(); + + LibretroLog(RETRO_LOG_INFO, "Eden: Game unloaded\n"); +} + +unsigned retro_get_region(void) { + return RETRO_REGION_NTSC; +} + +void* retro_get_memory_data(unsigned id) { + (void)id; + return nullptr; +} + +size_t retro_get_memory_size(unsigned id) { + (void)id; + return 0; +} + +} // extern "C" + +// VMA implementation - must be in exactly one translation unit +#define VMA_IMPLEMENTATION +#include "video_core/vulkan_common/vma.h" diff --git a/src/eden_libretro/libretro_vfs.h b/src/eden_libretro/libretro_vfs.h new file mode 100644 index 0000000000..e3377ef84d --- /dev/null +++ b/src/eden_libretro/libretro_vfs.h @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "eden_libretro/libretro.h" + +namespace LibretroVFS { + +// VFS file handle wrapper +struct VFSFileHandle { + FILE* fp = nullptr; + std::string path; + unsigned mode = 0; + + VFSFileHandle() = default; + ~VFSFileHandle() { + if (fp) { + fclose(fp); + fp = nullptr; + } + } +}; + +// VFS directory handle wrapper +struct VFSDirHandle { + std::filesystem::directory_iterator iter; + std::filesystem::directory_iterator end; + std::string current_name; + bool is_current_dir = false; + bool has_entry = false; + + VFSDirHandle() = default; +}; + +// VFS Implementation Functions +inline const char* vfs_get_path(struct retro_vfs_file_handle* stream) { + auto* handle = reinterpret_cast(stream); + if (!handle) return nullptr; + return handle->path.c_str(); +} + +inline struct retro_vfs_file_handle* vfs_open(const char* path, unsigned mode, unsigned hints) { + (void)hints; + + if (!path) return nullptr; + + std::string mode_str; + if (mode & RETRO_VFS_FILE_ACCESS_READ) { + if (mode & RETRO_VFS_FILE_ACCESS_WRITE) { + if (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) { + mode_str = "r+b"; + } else { + mode_str = "w+b"; + } + } else { + mode_str = "rb"; + } + } else if (mode & RETRO_VFS_FILE_ACCESS_WRITE) { + if (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) { + mode_str = "r+b"; + } else { + mode_str = "wb"; + } + } else { + return nullptr; + } + + FILE* fp = fopen(path, mode_str.c_str()); + if (!fp) return nullptr; + + auto* handle = new VFSFileHandle(); + handle->fp = fp; + handle->path = path; + handle->mode = mode; + + return reinterpret_cast(handle); +} + +inline int vfs_close(struct retro_vfs_file_handle* stream) { + auto* handle = reinterpret_cast(stream); + if (!handle) return -1; + + delete handle; + return 0; +} + +inline int64_t vfs_size(struct retro_vfs_file_handle* stream) { + auto* handle = reinterpret_cast(stream); + if (!handle || !handle->fp) return -1; + + long current = ftell(handle->fp); + if (current < 0) return -1; + + if (fseek(handle->fp, 0, SEEK_END) != 0) return -1; + long size = ftell(handle->fp); + + if (fseek(handle->fp, current, SEEK_SET) != 0) return -1; + + return static_cast(size); +} + +inline int64_t vfs_tell(struct retro_vfs_file_handle* stream) { + auto* handle = reinterpret_cast(stream); + if (!handle || !handle->fp) return -1; + + return static_cast(ftell(handle->fp)); +} + +inline int64_t vfs_seek(struct retro_vfs_file_handle* stream, int64_t offset, int seek_position) { + auto* handle = reinterpret_cast(stream); + if (!handle || !handle->fp) return -1; + + int whence; + switch (seek_position) { + case RETRO_VFS_SEEK_POSITION_START: whence = SEEK_SET; break; + case RETRO_VFS_SEEK_POSITION_CURRENT: whence = SEEK_CUR; break; + case RETRO_VFS_SEEK_POSITION_END: whence = SEEK_END; break; + default: return -1; + } + + if (fseek(handle->fp, static_cast(offset), whence) != 0) return -1; + + return static_cast(ftell(handle->fp)); +} + +inline int64_t vfs_read(struct retro_vfs_file_handle* stream, void* s, uint64_t len) { + auto* handle = reinterpret_cast(stream); + if (!handle || !handle->fp || !s) return -1; + + return static_cast(fread(s, 1, static_cast(len), handle->fp)); +} + +inline int64_t vfs_write(struct retro_vfs_file_handle* stream, const void* s, uint64_t len) { + auto* handle = reinterpret_cast(stream); + if (!handle || !handle->fp || !s) return -1; + + return static_cast(fwrite(s, 1, static_cast(len), handle->fp)); +} + +inline int vfs_flush(struct retro_vfs_file_handle* stream) { + auto* handle = reinterpret_cast(stream); + if (!handle || !handle->fp) return -1; + + return fflush(handle->fp); +} + +inline int vfs_remove(const char* path) { + if (!path) return -1; + return std::remove(path); +} + +inline int vfs_rename(const char* old_path, const char* new_path) { + if (!old_path || !new_path) return -1; + return std::rename(old_path, new_path); +} + +inline int64_t vfs_truncate(struct retro_vfs_file_handle* stream, int64_t length) { + auto* handle = reinterpret_cast(stream); + if (!handle || !handle->fp) return -1; + +#ifdef _WIN32 + if (_chsize(_fileno(handle->fp), static_cast(length)) != 0) return -1; +#else + if (ftruncate(fileno(handle->fp), static_cast(length)) != 0) return -1; +#endif + + return 0; +} + +inline int vfs_stat(const char* path, int32_t* size) { + if (!path) return 0; + + std::error_code ec; + auto status = std::filesystem::status(path, ec); + if (ec) return 0; + + int flags = 0; + if (std::filesystem::exists(status)) { + flags |= RETRO_VFS_STAT_IS_VALID; + + if (std::filesystem::is_directory(status)) { + flags |= RETRO_VFS_STAT_IS_DIRECTORY; + } + + if (std::filesystem::is_character_file(status)) { + flags |= RETRO_VFS_STAT_IS_CHARACTER_SPECIAL; + } + + if (size && std::filesystem::is_regular_file(status)) { + auto file_size = std::filesystem::file_size(path, ec); + if (!ec) { + *size = static_cast(file_size); + } + } + } + + return flags; +} + +inline int vfs_mkdir(const char* dir) { + if (!dir) return -1; + + std::error_code ec; + if (std::filesystem::create_directories(dir, ec)) { + return 0; + } + return ec ? -1 : 0; +} + +inline struct retro_vfs_dir_handle* vfs_opendir(const char* dir, bool include_hidden) { + (void)include_hidden; + + if (!dir) return nullptr; + + std::error_code ec; + auto iter = std::filesystem::directory_iterator(dir, ec); + if (ec) return nullptr; + + auto* handle = new VFSDirHandle(); + handle->iter = std::move(iter); + handle->end = std::filesystem::directory_iterator(); + + return reinterpret_cast(handle); +} + +inline bool vfs_readdir(struct retro_vfs_dir_handle* dirstream) { + auto* handle = reinterpret_cast(dirstream); + if (!handle) return false; + + if (handle->iter == handle->end) { + handle->has_entry = false; + return false; + } + + handle->current_name = handle->iter->path().filename().string(); + handle->is_current_dir = handle->iter->is_directory(); + handle->has_entry = true; + + std::error_code ec; + handle->iter.increment(ec); + + return true; +} + +inline const char* vfs_dirent_get_name(struct retro_vfs_dir_handle* dirstream) { + auto* handle = reinterpret_cast(dirstream); + if (!handle || !handle->has_entry) return nullptr; + + return handle->current_name.c_str(); +} + +inline bool vfs_dirent_is_dir(struct retro_vfs_dir_handle* dirstream) { + auto* handle = reinterpret_cast(dirstream); + if (!handle || !handle->has_entry) return false; + + return handle->is_current_dir; +} + +inline int vfs_closedir(struct retro_vfs_dir_handle* dirstream) { + auto* handle = reinterpret_cast(dirstream); + if (!handle) return -1; + + delete handle; + return 0; +} + +// Get the VFS interface +inline struct retro_vfs_interface* GetVFSInterface() { + static struct retro_vfs_interface vfs_interface = { + vfs_get_path, + vfs_open, + vfs_close, + vfs_size, + vfs_tell, + vfs_seek, + vfs_read, + vfs_write, + vfs_flush, + vfs_remove, + vfs_rename, + vfs_truncate, + vfs_stat, + vfs_mkdir, + vfs_opendir, + vfs_readdir, + vfs_dirent_get_name, + vfs_dirent_is_dir, + vfs_closedir + }; + return &vfs_interface; +} + +} // namespace LibretroVFS diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 36d726c929..4b47597330 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -125,6 +125,12 @@ struct GPU::Impl { } void WaitForSyncOperation(const u64 fence) { + // In deferred mode, process sync operations immediately since we're on main thread + if (gpu_thread.IsDeferredMode()) { + LOG_DEBUG(HW_GPU, "WaitForSyncOperation: deferred mode, calling TickWork"); + TickWork(); + return; + } std::unique_lock lck{sync_request_mutex}; sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; }); } @@ -132,6 +138,10 @@ struct GPU::Impl { /// Tick pending requests within the GPU. void TickWork() { std::unique_lock lck{sync_request_mutex}; + size_t request_count = sync_requests.size(); + if (request_count > 0) { + LOG_DEBUG(HW_GPU, "TickWork: processing {} sync requests", request_count); + } while (!sync_requests.empty()) { auto request = std::move(sync_requests.front()); sync_requests.pop_front(); @@ -290,6 +300,17 @@ struct GPU::Impl { void RequestComposite(std::vector&& layers, std::vector&& fences) { + LOG_INFO(HW_GPU, "RequestComposite called with {} layers, {} fences, deferred_mode={}", + layers.size(), fences.size(), gpu_thread.IsDeferredMode()); + + // In deferred mode, queue composite for main thread execution + if (gpu_thread.IsDeferredMode()) { + std::lock_guard lk(pending_composites_mutex); + pending_composites.push_back(std::move(layers)); + LOG_INFO(HW_GPU, "Deferred mode: queued composite, {} pending", pending_composites.size()); + return; + } + size_t num_fences{fences.size()}; size_t current_request_counter{}; { @@ -366,6 +387,10 @@ struct GPU::Impl { u64 last_sync_fence{}; std::mutex sync_request_mutex; std::condition_variable sync_request_cv; + + // Pending composites for deferred mode (executed on main thread) + std::vector> pending_composites; + std::mutex pending_composites_mutex; const bool is_async; @@ -527,6 +552,28 @@ void GPU::ObtainContext() { impl->ObtainContext(); } +void GPU::SetDeferredMode(bool enabled) { + impl->gpu_thread.SetDeferredMode(enabled); +} + +void GPU::ProcessPendingCommands() { + impl->gpu_thread.ProcessPendingCommands(); +} + +void GPU::ProcessPendingComposites() { + std::vector> composites_to_process; + { + std::lock_guard lk(impl->pending_composites_mutex); + composites_to_process = std::move(impl->pending_composites); + impl->pending_composites.clear(); + } + + for (auto& layers : composites_to_process) { + LOG_INFO(HW_GPU, "Processing queued composite with {} layers", layers.size()); + impl->renderer->Composite(layers); + } +} + void GPU::ReleaseContext() { impl->ReleaseContext(); } diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 538c4da85a..abeb6c3f19 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -224,6 +224,15 @@ public: /// This can be used to launch any necessary threads and register any necessary /// core timing events. void Start(); + + /// Enable deferred GPU mode for libretro - commands processed on main thread + void SetDeferredMode(bool enabled); + + /// Process pending GPU commands (for deferred mode) + void ProcessPendingCommands(); + + /// Process pending composites (for deferred mode - called from main thread) + void ProcessPendingComposites(); /// Performs any additional necessary steps to shutdown GPU emulation. void NotifyShutdown(); diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 8d8d857a02..dd61f1f3c9 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -67,6 +67,14 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::Control::Scheduler& scheduler) { rasterizer = renderer.ReadRasterizer(); + scheduler_ptr = &scheduler; + + // In deferred mode, don't start the GPU thread - commands will be processed on main thread + if (deferred_mode) { + LOG_INFO(HW_GPU, "GPU deferred mode enabled - thread not started"); + return; + } + thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context), std::ref(scheduler), std::ref(state)); } @@ -97,6 +105,17 @@ void ThreadManager::FlushAndInvalidateRegion(DAddr addr, u64 size) { } u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { + // In deferred mode, queue commands for later processing on main thread + // Signal fence immediately so game threads don't block waiting + if (deferred_mode) { + std::lock_guard lk(deferred_mutex); + const u64 fence{++state.last_fence}; + deferred_commands.emplace_back(std::move(command_data), fence, false); // Never block in deferred mode + // Signal fence immediately so blocking callers don't wait forever + state.signaled_fence.store(fence, std::memory_order_release); + return fence; + } + if (!is_async) { // In synchronous GPU mode, block the caller until the command has executed block = true; @@ -115,4 +134,38 @@ u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { return fence; } +void ThreadManager::ProcessPendingCommands() { + if (!deferred_mode || !scheduler_ptr) { + return; + } + + std::vector commands_to_process; + { + std::lock_guard lk(deferred_mutex); + commands_to_process = std::move(deferred_commands); + deferred_commands.clear(); + } + + if (!commands_to_process.empty()) { + LOG_DEBUG(HW_GPU, "ProcessPendingCommands: processing {} commands", commands_to_process.size()); + } + + for (auto& cmd : commands_to_process) { + if (auto* submit_list = std::get_if(&cmd.data)) { + scheduler_ptr->Push(submit_list->channel, std::move(submit_list->entries)); + } else if (std::holds_alternative(cmd.data)) { + system.GPU().TickWork(); + } else if (const auto* flush = std::get_if(&cmd.data)) { + if (rasterizer) { + rasterizer->FlushRegion(flush->addr, flush->size); + } + } else if (const auto* invalidate = std::get_if(&cmd.data)) { + if (rasterizer) { + rasterizer->OnCacheInvalidation(invalidate->addr, invalidate->size); + } + } + state.signaled_fence.store(cmd.fence); + } +} + } // namespace VideoCommon::GPUThread diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index dc0fce9f82..da48e941db 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -106,6 +106,13 @@ public: /// Creates and starts the GPU thread. void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::Control::Scheduler& scheduler); + + /// Enable deferred mode - commands are queued for main thread processing (for libretro) + void SetDeferredMode(bool enabled) { deferred_mode = enabled; } + bool IsDeferredMode() const { return deferred_mode; } + + /// Process pending commands on the current thread (for deferred mode) + void ProcessPendingCommands(); /// Push GPU command entries to be processed void SubmitList(s32 channel, Tegra::CommandList&& entries); @@ -127,10 +134,16 @@ private: Core::System& system; const bool is_async; + bool deferred_mode = false; VideoCore::RasterizerInterface* rasterizer = nullptr; + Tegra::Control::Scheduler* scheduler_ptr = nullptr; SynchState state; std::jthread thread; + + // Deferred command queue for libretro mode + std::vector deferred_commands; + std::mutex deferred_mutex; }; } // namespace VideoCommon::GPUThread diff --git a/src/video_core/renderer_opengl/gl_blit_screen.cpp b/src/video_core/renderer_opengl/gl_blit_screen.cpp index 4b75e1b949..e5ae34ee08 100644 --- a/src/video_core/renderer_opengl/gl_blit_screen.cpp +++ b/src/video_core/renderer_opengl/gl_blit_screen.cpp @@ -7,8 +7,10 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/logging/log.h" #include "common/settings.h" #include "common/settings_enums.h" +#include "core/frontend/framebuffer_layout.h" #include "video_core/present.h" #include "video_core/renderer_opengl/gl_blit_screen.h" #include "video_core/renderer_opengl/gl_state_tracker.h" @@ -55,7 +57,6 @@ void BlitScreen::DrawScreen(std::span framebuffe glDisable(GL_STENCIL_TEST); glDisable(GL_POLYGON_OFFSET_FILL); glDisable(GL_RASTERIZER_DISCARD); - glDisable(GL_ALPHA_TEST); glDisablei(GL_BLEND, 0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glCullFace(GL_BACK); diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 59829b667f..efd7f4d8e9 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -114,7 +114,9 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, : device{device_}, staging_buffer_pool{staging_buffer_pool_}, has_fast_buffer_sub_data{device.HasFastBufferSubData()}, use_assembly_shaders{device.UseAssemblyShaders()}, - has_unified_vertex_buffers{device.HasVertexBufferUnifiedMemory()}, + // NOTE: Disable NV_vertex_buffer_unified_memory for libretro compatibility + // This extension causes GL_INVALID_OPERATION errors in libretro's OpenGL context + has_unified_vertex_buffers{false}, // was: device.HasVertexBufferUnifiedMemory() stream_buffer{has_fast_buffer_sub_data ? std::nullopt : std::make_optional()} { GLint gl_max_attributes; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_max_attributes); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 602509bfdb..e59a89fa8f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -80,7 +80,10 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra program_manager, state_tracker, gpu.ShaderNotify()), query_cache(*this, device_memory_), accelerate_dma(buffer_cache, texture_cache), fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache), - blit_image(program_manager_) {} + blit_image(program_manager_) { + // Create VAO for GPU rendering - required for draw calls in libretro + glGenVertexArrays(1, &gpu_vao); +} RasterizerOpenGL::~RasterizerOpenGL() = default; @@ -227,6 +230,9 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) { gpu.TickWork(); }; gpu_memory->FlushCaching(); + + // Ensure GPU VAO is bound - libretro unbinds it after presentation + glBindVertexArray(gpu_vao); GraphicsPipeline* const pipeline{shader_cache.CurrentGraphicsPipeline()}; if (!pipeline) { @@ -263,11 +269,13 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) { const auto& draw_state = maxwell3d->draw_manager->GetDrawState(); const GLuint base_instance = static_cast(draw_state.base_instance); const GLsizei num_instances = static_cast(instance_count); + if (is_indexed) { const GLint base_vertex = static_cast(draw_state.base_index); const GLsizei num_vertices = static_cast(draw_state.index_buffer.count); const GLvoid* const offset = buffer_cache_runtime.IndexOffset(); const GLenum format = MaxwellToGL::IndexFormat(draw_state.index_buffer.format); + if (num_instances == 1 && base_instance == 0 && base_vertex == 0) { glDrawElements(primitive_mode, num_vertices, format, offset); } else if (num_instances == 1 && base_instance == 0) { @@ -289,6 +297,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) { } else { const GLint base_vertex = static_cast(draw_state.vertex_buffer.first); const GLsizei num_vertices = static_cast(draw_state.vertex_buffer.count); + if (num_instances == 1 && base_instance == 0) { glDrawArrays(primitive_mode, base_vertex, num_vertices); } else if (base_instance == 0) { @@ -747,6 +756,7 @@ std::optional RasterizerOpenGL::AccelerateDisplay( info.height = image_view->size.height; info.scaled_width = scaled ? resolution.ScaleUp(info.width) : info.width; info.scaled_height = scaled ? resolution.ScaleUp(info.height) : info.height; + return info; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 6eae51ff7d..c9a97dfe9b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -255,6 +255,9 @@ private: BlitImageHelper blit_image; + /// VAO for GPU rendering - must be bound for draw calls + GLuint gpu_vao = 0; + boost::container::static_vector image_view_indices; std::array image_view_ids; boost::container::static_vector sampler_handles; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 03d4b9d061..b1a59fe5c4 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -3,6 +3,7 @@ #include +#include "common/logging/log.h" #include "video_core/host_shaders/opengl_lmem_warmup_comp.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_shader_util.h" @@ -112,6 +113,13 @@ void ProgramManager::LocalMemoryWarmup() { } void ProgramManager::BindPipeline() { + // For libretro compatibility: Always verify actual GL state matches our tracked state + // RetroArch may unbind our pipeline between frames + GLint actual_pipeline = 0; + glGetIntegerv(GL_PROGRAM_PIPELINE_BINDING, &actual_pipeline); + if (is_pipeline_bound && actual_pipeline != static_cast(pipeline.handle)) { + is_pipeline_bound = false; // Reset to force rebind + } if (!is_pipeline_bound) { is_pipeline_bound = true; glBindProgramPipeline(pipeline.handle); diff --git a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp index 37fb766139..1ec4ecd7a3 100644 --- a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp +++ b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp @@ -25,10 +25,11 @@ WindowAdaptPass::WindowAdaptPass(const Device& device_, OGLSampler&& sampler_, vert = CreateProgram(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER); frag = CreateProgram(frag_source, GL_FRAGMENT_SHADER); - // Generate VBO handle for drawing + // Generate VAO and VBO for drawing + glCreateVertexArrays(1, &vao_handle); vertex_buffer.Create(); - // Attach vertex data to VAO + // Attach vertex data to buffer glNamedBufferData(vertex_buffer.handle, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW); // Query vertex buffer address when the driver supports unified vertex attributes @@ -70,6 +71,11 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li glViewportIndexedf(0, 0.0f, 0.0f, static_cast(layout.width), static_cast(layout.height)); + // Save RetroArch's VAO state before binding ours + GLint saved_vao = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao); + + glBindVertexArray(vao_handle); glEnableVertexAttribArray(PositionLocation); glEnableVertexAttribArray(TexCoordLocation); glVertexAttribDivisor(PositionLocation, 0); @@ -80,17 +86,13 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li offsetof(ScreenRectVertex, tex_coord)); glVertexAttribBinding(PositionLocation, 0); glVertexAttribBinding(TexCoordLocation, 0); - if (device.HasVertexBufferUnifiedMemory()) { - glBindVertexBuffer(0, 0, 0, sizeof(ScreenRectVertex)); - glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 0, vertex_buffer_address, - sizeof(decltype(vertices)::value_type)); - } else { - glBindVertexBuffer(0, vertex_buffer.handle, 0, sizeof(ScreenRectVertex)); - } + + // Always use standard VBO binding for libretro compatibility + // NV_vertex_buffer_unified_memory extension causes crashes in libretro's OpenGL context + glBindVertexBuffer(0, vertex_buffer.handle, 0, sizeof(ScreenRectVertex)); glBindSampler(0, sampler.handle); - // Update background color before drawing glClearColor(Settings::values.bg_red.GetValue() / 255.0f, Settings::values.bg_green.GetValue() / 255.0f, Settings::values.bg_blue.GetValue() / 255.0f, 1.0f); @@ -122,6 +124,9 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i])); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } + + // Restore RetroArch's VAO + glBindVertexArray(saved_vao); } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/present/window_adapt_pass.h b/src/video_core/renderer_opengl/present/window_adapt_pass.h index 0a8bcef2f5..317b9ec0f0 100644 --- a/src/video_core/renderer_opengl/present/window_adapt_pass.h +++ b/src/video_core/renderer_opengl/present/window_adapt_pass.h @@ -39,6 +39,7 @@ private: OGLProgram vert; OGLProgram frag; OGLBuffer vertex_buffer; + GLuint vao_handle = 0; // GPU address of the vertex buffer GLuint64EXT vertex_buffer_address = 0; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 3412b1d998..a2d7c6d0a7 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -142,14 +142,28 @@ void RendererOpenGL::Composite(std::span framebu RenderAppletCaptureLayer(framebuffers); RenderScreenshot(framebuffers); - state_tracker.BindFramebuffer(0); - blit_screen->DrawScreen(framebuffers, emu_window.GetFramebufferLayout(), false); + u32 present_fbo = render_window.GetPresentationFramebuffer(); + const auto& layout = emu_window.GetFramebufferLayout(); + + glViewport(0, 0, layout.width, layout.height); + glDisable(GL_SCISSOR_TEST); + + state_tracker.BindFramebuffer(present_fbo); + blit_screen->DrawScreen(framebuffers, layout, false); ++m_current_frame; gpu.RendererFrameEndNotify(); rasterizer.TickFrame(); + // Clean up GL state for libretro compatibility + // Libretro docs: "Don't leave buffers and global objects bound when calling video_cb" + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glUseProgram(0); + glBindTexture(GL_TEXTURE_2D, 0); + context->SwapBuffers(); render_window.OnFrameDisplayed(); }