Browse Source

initial wip

DraVee 3 months ago
parent
commit
23431c9705
  1. 3
      CMakeLists.txt
  2. 4
      src/CMakeLists.txt
  3. 3
      src/core/frontend/emu_window.h
  4. 130
      src/eden_libretro/CMakeLists.txt
  5. 35
      src/eden_libretro/eden_libretro.info.in
  6. 178
      src/eden_libretro/emu_window_libretro.cpp
  7. 77
      src/eden_libretro/emu_window_libretro.h
  8. 1129
      src/eden_libretro/libretro.h
  9. 730
      src/eden_libretro/libretro_core.cpp
  10. 306
      src/eden_libretro/libretro_vfs.h
  11. 47
      src/video_core/gpu.cpp
  12. 9
      src/video_core/gpu.h
  13. 53
      src/video_core/gpu_thread.cpp
  14. 13
      src/video_core/gpu_thread.h
  15. 3
      src/video_core/renderer_opengl/gl_blit_screen.cpp
  16. 4
      src/video_core/renderer_opengl/gl_buffer_cache.cpp
  17. 12
      src/video_core/renderer_opengl/gl_rasterizer.cpp
  18. 3
      src/video_core/renderer_opengl/gl_rasterizer.h
  19. 8
      src/video_core/renderer_opengl/gl_shader_manager.cpp
  20. 25
      src/video_core/renderer_opengl/present/window_adapt_pass.cpp
  21. 1
      src/video_core/renderer_opengl/present/window_adapt_pass.h
  22. 18
      src/video_core/renderer_opengl/renderer_opengl.cpp

3
CMakeLists.txt

@ -187,6 +187,9 @@ cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF
option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}") 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) option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
if(YUZU_ENABLE_LTO) if(YUZU_ENABLE_LTO)
include(UseLTO) include(UseLTO)

4
src/CMakeLists.txt

@ -247,4 +247,8 @@ if (ANDROID)
target_include_directories(yuzu-android PRIVATE android/app/src/main) target_include_directories(yuzu-android PRIVATE android/app/src/main)
endif() endif()
if (ENABLE_LIBRETRO)
add_subdirectory(eden_libretro)
endif()
include(GenerateDepHashes) include(GenerateDepHashes)

3
src/core/frontend/emu_window.h

@ -76,6 +76,9 @@ public:
/// Called from GPU thread when a frame is displayed. /// Called from GPU thread when a frame is displayed.
virtual void OnFrameDisplayed() {} 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. * Returns a GraphicsContext that the frontend provides to be used for rendering.
*/ */

130
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
)

35
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."

178
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 <thread>
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<void*>(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<Core::Frontend::GraphicsContext> 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<LibretroGraphicsContext>(hw_render_callback->get_proc_address);
}
LOG_WARNING(Frontend, "EmuWindowLibretro: No HW render callback, returning null context");
return std::make_unique<LibretroGraphicsContext>(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<int>(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<u32>(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<void()> callback) {
frame_callback = std::move(callback);
LOG_DEBUG(Frontend, "EmuWindowLibretro: Frame callback set");
}
} // namespace Libretro

77
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 <memory>
#include <functional>
#include <atomic>
#include <thread>
#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<Core::Frontend::GraphicsContext> 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<void()> callback);
private:
retro_hw_render_callback* hw_render_callback = nullptr;
std::atomic<bool> context_ready{false};
std::function<void()> frame_callback;
unsigned fb_width = 1280;
unsigned fb_height = 720;
};
} // namespace Libretro

1129
src/eden_libretro/libretro.h
File diff suppressed because it is too large
View File

730
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 <atomic>
#include <cstdarg>
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "common/fs/path_util.h"
#include <glad/glad.h>
#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<Core::System> emu_system;
std::unique_ptr<Libretro::EmuWindowLibretro> emu_window;
std::unique_ptr<Common::DetachedTasks> detached_tasks;
std::unique_ptr<InputCommon::InputSubsystem> input_subsystem;
std::string game_path;
std::string system_directory;
std::string save_directory;
std::atomic<bool> is_running{false};
std::atomic<bool> game_loaded{false};
std::atomic<bool> is_initialized{false};
std::atomic<uint64_t> frame_count{0};
std::atomic<bool> has_new_frame{false};
std::mutex emu_mutex;
// Audio buffer
std::vector<int16_t> 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<GLADloadproc>(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<u32>(load_result));
LibretroLog(RETRO_LOG_ERROR, "Eden: Failed to load game, error: %u\n", static_cast<u32>(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<float>(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<Common::DetachedTasks>();
// Initialize input subsystem
input_subsystem = std::make_unique<InputCommon::InputSubsystem>();
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<float>(SCREEN_WIDTH) / static_cast<float>(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<GLuint>(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<Libretro::EmuWindowLibretro>();
emu_window->SetHWRenderCallback(&hw_render);
emu_window->SetFramebufferSize(SCREEN_WIDTH, SCREEN_HEIGHT);
// Create and initialize the system
emu_system = std::make_unique<Core::System>();
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<FileSys::ContentProviderUnion>());
emu_system->SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
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"

306
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 <cstdio>
#include <cstring>
#include <string>
#include <memory>
#include <filesystem>
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#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<VFSFileHandle*>(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<struct retro_vfs_file_handle*>(handle);
}
inline int vfs_close(struct retro_vfs_file_handle* stream) {
auto* handle = reinterpret_cast<VFSFileHandle*>(stream);
if (!handle) return -1;
delete handle;
return 0;
}
inline int64_t vfs_size(struct retro_vfs_file_handle* stream) {
auto* handle = reinterpret_cast<VFSFileHandle*>(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<int64_t>(size);
}
inline int64_t vfs_tell(struct retro_vfs_file_handle* stream) {
auto* handle = reinterpret_cast<VFSFileHandle*>(stream);
if (!handle || !handle->fp) return -1;
return static_cast<int64_t>(ftell(handle->fp));
}
inline int64_t vfs_seek(struct retro_vfs_file_handle* stream, int64_t offset, int seek_position) {
auto* handle = reinterpret_cast<VFSFileHandle*>(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<long>(offset), whence) != 0) return -1;
return static_cast<int64_t>(ftell(handle->fp));
}
inline int64_t vfs_read(struct retro_vfs_file_handle* stream, void* s, uint64_t len) {
auto* handle = reinterpret_cast<VFSFileHandle*>(stream);
if (!handle || !handle->fp || !s) return -1;
return static_cast<int64_t>(fread(s, 1, static_cast<size_t>(len), handle->fp));
}
inline int64_t vfs_write(struct retro_vfs_file_handle* stream, const void* s, uint64_t len) {
auto* handle = reinterpret_cast<VFSFileHandle*>(stream);
if (!handle || !handle->fp || !s) return -1;
return static_cast<int64_t>(fwrite(s, 1, static_cast<size_t>(len), handle->fp));
}
inline int vfs_flush(struct retro_vfs_file_handle* stream) {
auto* handle = reinterpret_cast<VFSFileHandle*>(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<VFSFileHandle*>(stream);
if (!handle || !handle->fp) return -1;
#ifdef _WIN32
if (_chsize(_fileno(handle->fp), static_cast<long>(length)) != 0) return -1;
#else
if (ftruncate(fileno(handle->fp), static_cast<off_t>(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<int32_t>(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<struct retro_vfs_dir_handle*>(handle);
}
inline bool vfs_readdir(struct retro_vfs_dir_handle* dirstream) {
auto* handle = reinterpret_cast<VFSDirHandle*>(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<VFSDirHandle*>(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<VFSDirHandle*>(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<VFSDirHandle*>(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

47
src/video_core/gpu.cpp

@ -125,6 +125,12 @@ struct GPU::Impl {
} }
void WaitForSyncOperation(const u64 fence) { 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}; std::unique_lock lck{sync_request_mutex};
sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; }); sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; });
} }
@ -132,6 +138,10 @@ struct GPU::Impl {
/// Tick pending requests within the GPU. /// Tick pending requests within the GPU.
void TickWork() { void TickWork() {
std::unique_lock lck{sync_request_mutex}; 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()) { while (!sync_requests.empty()) {
auto request = std::move(sync_requests.front()); auto request = std::move(sync_requests.front());
sync_requests.pop_front(); sync_requests.pop_front();
@ -290,6 +300,17 @@ struct GPU::Impl {
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers, void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
std::vector<Service::Nvidia::NvFence>&& fences) { std::vector<Service::Nvidia::NvFence>&& 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 num_fences{fences.size()};
size_t current_request_counter{}; size_t current_request_counter{};
{ {
@ -367,6 +388,10 @@ struct GPU::Impl {
std::mutex sync_request_mutex; std::mutex sync_request_mutex;
std::condition_variable sync_request_cv; std::condition_variable sync_request_cv;
// Pending composites for deferred mode (executed on main thread)
std::vector<std::vector<Tegra::FramebufferConfig>> pending_composites;
std::mutex pending_composites_mutex;
const bool is_async; const bool is_async;
VideoCommon::GPUThread::ThreadManager gpu_thread; VideoCommon::GPUThread::ThreadManager gpu_thread;
@ -527,6 +552,28 @@ void GPU::ObtainContext() {
impl->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<std::vector<Tegra::FramebufferConfig>> 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() { void GPU::ReleaseContext() {
impl->ReleaseContext(); impl->ReleaseContext();
} }

9
src/video_core/gpu.h

@ -225,6 +225,15 @@ public:
/// core timing events. /// core timing events.
void Start(); 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. /// Performs any additional necessary steps to shutdown GPU emulation.
void NotifyShutdown(); void NotifyShutdown();

53
src/video_core/gpu_thread.cpp

@ -67,6 +67,14 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
Core::Frontend::GraphicsContext& context, Core::Frontend::GraphicsContext& context,
Tegra::Control::Scheduler& scheduler) { Tegra::Control::Scheduler& scheduler) {
rasterizer = renderer.ReadRasterizer(); 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), thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
std::ref(scheduler), std::ref(state)); 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) { 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) { if (!is_async) {
// In synchronous GPU mode, block the caller until the command has executed // In synchronous GPU mode, block the caller until the command has executed
block = true; block = true;
@ -115,4 +134,38 @@ u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) {
return fence; return fence;
} }
void ThreadManager::ProcessPendingCommands() {
if (!deferred_mode || !scheduler_ptr) {
return;
}
std::vector<CommandDataContainer> 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<SubmitListCommand>(&cmd.data)) {
scheduler_ptr->Push(submit_list->channel, std::move(submit_list->entries));
} else if (std::holds_alternative<GPUTickCommand>(cmd.data)) {
system.GPU().TickWork();
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&cmd.data)) {
if (rasterizer) {
rasterizer->FlushRegion(flush->addr, flush->size);
}
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&cmd.data)) {
if (rasterizer) {
rasterizer->OnCacheInvalidation(invalidate->addr, invalidate->size);
}
}
state.signaled_fence.store(cmd.fence);
}
}
} // namespace VideoCommon::GPUThread } // namespace VideoCommon::GPUThread

13
src/video_core/gpu_thread.h

@ -107,6 +107,13 @@ public:
void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
Tegra::Control::Scheduler& scheduler); 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 /// Push GPU command entries to be processed
void SubmitList(s32 channel, Tegra::CommandList&& entries); void SubmitList(s32 channel, Tegra::CommandList&& entries);
@ -127,10 +134,16 @@ private:
Core::System& system; Core::System& system;
const bool is_async; const bool is_async;
bool deferred_mode = false;
VideoCore::RasterizerInterface* rasterizer = nullptr; VideoCore::RasterizerInterface* rasterizer = nullptr;
Tegra::Control::Scheduler* scheduler_ptr = nullptr;
SynchState state; SynchState state;
std::jthread thread; std::jthread thread;
// Deferred command queue for libretro mode
std::vector<CommandDataContainer> deferred_commands;
std::mutex deferred_mutex;
}; };
} // namespace VideoCommon::GPUThread } // namespace VideoCommon::GPUThread

3
src/video_core/renderer_opengl/gl_blit_screen.cpp

@ -7,8 +7,10 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/settings_enums.h" #include "common/settings_enums.h"
#include "core/frontend/framebuffer_layout.h"
#include "video_core/present.h" #include "video_core/present.h"
#include "video_core/renderer_opengl/gl_blit_screen.h" #include "video_core/renderer_opengl/gl_blit_screen.h"
#include "video_core/renderer_opengl/gl_state_tracker.h" #include "video_core/renderer_opengl/gl_state_tracker.h"
@ -55,7 +57,6 @@ void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffe
glDisable(GL_STENCIL_TEST); glDisable(GL_STENCIL_TEST);
glDisable(GL_POLYGON_OFFSET_FILL); glDisable(GL_POLYGON_OFFSET_FILL);
glDisable(GL_RASTERIZER_DISCARD); glDisable(GL_RASTERIZER_DISCARD);
glDisable(GL_ALPHA_TEST);
glDisablei(GL_BLEND, 0); glDisablei(GL_BLEND, 0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glCullFace(GL_BACK); glCullFace(GL_BACK);

4
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_}, : device{device_}, staging_buffer_pool{staging_buffer_pool_},
has_fast_buffer_sub_data{device.HasFastBufferSubData()}, has_fast_buffer_sub_data{device.HasFastBufferSubData()},
use_assembly_shaders{device.UseAssemblyShaders()}, 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<StreamBuffer>()} { stream_buffer{has_fast_buffer_sub_data ? std::nullopt : std::make_optional<StreamBuffer>()} {
GLint gl_max_attributes; GLint gl_max_attributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_max_attributes); glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_max_attributes);

12
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()), program_manager, state_tracker, gpu.ShaderNotify()),
query_cache(*this, device_memory_), accelerate_dma(buffer_cache, texture_cache), query_cache(*this, device_memory_), accelerate_dma(buffer_cache, texture_cache),
fence_manager(*this, gpu, texture_cache, buffer_cache, query_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; RasterizerOpenGL::~RasterizerOpenGL() = default;
@ -228,6 +231,9 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) {
}; };
gpu_memory->FlushCaching(); gpu_memory->FlushCaching();
// Ensure GPU VAO is bound - libretro unbinds it after presentation
glBindVertexArray(gpu_vao);
GraphicsPipeline* const pipeline{shader_cache.CurrentGraphicsPipeline()}; GraphicsPipeline* const pipeline{shader_cache.CurrentGraphicsPipeline()};
if (!pipeline) { if (!pipeline) {
return; return;
@ -263,11 +269,13 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
const auto& draw_state = maxwell3d->draw_manager->GetDrawState(); const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
const GLuint base_instance = static_cast<GLuint>(draw_state.base_instance); const GLuint base_instance = static_cast<GLuint>(draw_state.base_instance);
const GLsizei num_instances = static_cast<GLsizei>(instance_count); const GLsizei num_instances = static_cast<GLsizei>(instance_count);
if (is_indexed) { if (is_indexed) {
const GLint base_vertex = static_cast<GLint>(draw_state.base_index); const GLint base_vertex = static_cast<GLint>(draw_state.base_index);
const GLsizei num_vertices = static_cast<GLsizei>(draw_state.index_buffer.count); const GLsizei num_vertices = static_cast<GLsizei>(draw_state.index_buffer.count);
const GLvoid* const offset = buffer_cache_runtime.IndexOffset(); const GLvoid* const offset = buffer_cache_runtime.IndexOffset();
const GLenum format = MaxwellToGL::IndexFormat(draw_state.index_buffer.format); const GLenum format = MaxwellToGL::IndexFormat(draw_state.index_buffer.format);
if (num_instances == 1 && base_instance == 0 && base_vertex == 0) { if (num_instances == 1 && base_instance == 0 && base_vertex == 0) {
glDrawElements(primitive_mode, num_vertices, format, offset); glDrawElements(primitive_mode, num_vertices, format, offset);
} else if (num_instances == 1 && base_instance == 0) { } else if (num_instances == 1 && base_instance == 0) {
@ -289,6 +297,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
} else { } else {
const GLint base_vertex = static_cast<GLint>(draw_state.vertex_buffer.first); const GLint base_vertex = static_cast<GLint>(draw_state.vertex_buffer.first);
const GLsizei num_vertices = static_cast<GLsizei>(draw_state.vertex_buffer.count); const GLsizei num_vertices = static_cast<GLsizei>(draw_state.vertex_buffer.count);
if (num_instances == 1 && base_instance == 0) { if (num_instances == 1 && base_instance == 0) {
glDrawArrays(primitive_mode, base_vertex, num_vertices); glDrawArrays(primitive_mode, base_vertex, num_vertices);
} else if (base_instance == 0) { } else if (base_instance == 0) {
@ -747,6 +756,7 @@ std::optional<FramebufferTextureInfo> RasterizerOpenGL::AccelerateDisplay(
info.height = image_view->size.height; info.height = image_view->size.height;
info.scaled_width = scaled ? resolution.ScaleUp(info.width) : info.width; info.scaled_width = scaled ? resolution.ScaleUp(info.width) : info.width;
info.scaled_height = scaled ? resolution.ScaleUp(info.height) : info.height; info.scaled_height = scaled ? resolution.ScaleUp(info.height) : info.height;
return info; return info;
} }

3
src/video_core/renderer_opengl/gl_rasterizer.h

@ -255,6 +255,9 @@ private:
BlitImageHelper blit_image; BlitImageHelper blit_image;
/// VAO for GPU rendering - must be bound for draw calls
GLuint gpu_vao = 0;
boost::container::static_vector<u32, MAX_IMAGE_VIEWS> image_view_indices; boost::container::static_vector<u32, MAX_IMAGE_VIEWS> image_view_indices;
std::array<ImageViewId, MAX_IMAGE_VIEWS> image_view_ids; std::array<ImageViewId, MAX_IMAGE_VIEWS> image_view_ids;
boost::container::static_vector<GLuint, MAX_TEXTURES> sampler_handles; boost::container::static_vector<GLuint, MAX_TEXTURES> sampler_handles;

8
src/video_core/renderer_opengl/gl_shader_manager.cpp

@ -3,6 +3,7 @@
#include <glad/glad.h> #include <glad/glad.h>
#include "common/logging/log.h"
#include "video_core/host_shaders/opengl_lmem_warmup_comp.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_manager.h"
#include "video_core/renderer_opengl/gl_shader_util.h" #include "video_core/renderer_opengl/gl_shader_util.h"
@ -112,6 +113,13 @@ void ProgramManager::LocalMemoryWarmup() {
} }
void ProgramManager::BindPipeline() { 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<GLint>(pipeline.handle)) {
is_pipeline_bound = false; // Reset to force rebind
}
if (!is_pipeline_bound) { if (!is_pipeline_bound) {
is_pipeline_bound = true; is_pipeline_bound = true;
glBindProgramPipeline(pipeline.handle); glBindProgramPipeline(pipeline.handle);

25
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); vert = CreateProgram(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER);
frag = CreateProgram(frag_source, GL_FRAGMENT_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(); vertex_buffer.Create();
// Attach vertex data to VAO
// Attach vertex data to buffer
glNamedBufferData(vertex_buffer.handle, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW); glNamedBufferData(vertex_buffer.handle, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW);
// Query vertex buffer address when the driver supports unified vertex attributes // 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<GLfloat>(layout.width), glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width),
static_cast<GLfloat>(layout.height)); static_cast<GLfloat>(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(PositionLocation);
glEnableVertexAttribArray(TexCoordLocation); glEnableVertexAttribArray(TexCoordLocation);
glVertexAttribDivisor(PositionLocation, 0); glVertexAttribDivisor(PositionLocation, 0);
@ -80,17 +86,13 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
offsetof(ScreenRectVertex, tex_coord)); offsetof(ScreenRectVertex, tex_coord));
glVertexAttribBinding(PositionLocation, 0); glVertexAttribBinding(PositionLocation, 0);
glVertexAttribBinding(TexCoordLocation, 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); glBindSampler(0, sampler.handle);
// Update background color before drawing
glClearColor(Settings::values.bg_red.GetValue() / 255.0f, glClearColor(Settings::values.bg_red.GetValue() / 255.0f,
Settings::values.bg_green.GetValue() / 255.0f, Settings::values.bg_green.GetValue() / 255.0f,
Settings::values.bg_blue.GetValue() / 255.0f, 1.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])); glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i]));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
} }
// Restore RetroArch's VAO
glBindVertexArray(saved_vao);
} }
} // namespace OpenGL } // namespace OpenGL

1
src/video_core/renderer_opengl/present/window_adapt_pass.h

@ -39,6 +39,7 @@ private:
OGLProgram vert; OGLProgram vert;
OGLProgram frag; OGLProgram frag;
OGLBuffer vertex_buffer; OGLBuffer vertex_buffer;
GLuint vao_handle = 0;
// GPU address of the vertex buffer // GPU address of the vertex buffer
GLuint64EXT vertex_buffer_address = 0; GLuint64EXT vertex_buffer_address = 0;

18
src/video_core/renderer_opengl/renderer_opengl.cpp

@ -142,14 +142,28 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
RenderAppletCaptureLayer(framebuffers); RenderAppletCaptureLayer(framebuffers);
RenderScreenshot(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; ++m_current_frame;
gpu.RendererFrameEndNotify(); gpu.RendererFrameEndNotify();
rasterizer.TickFrame(); 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(); context->SwapBuffers();
render_window.OnFrameDisplayed(); render_window.OnFrameDisplayed();
} }

Loading…
Cancel
Save