feat(libretro_binding): some advancement but broken on godot side

This commit is contained in:
XargonWan 2024-12-11 16:29:31 +09:00
parent 9c612af548
commit 6f7436e8d1
13 changed files with 111 additions and 439 deletions

1
.gitignore vendored
View file

@ -9,3 +9,4 @@ godot-cpp
.sconsign.dblite
libretro_binding/libretro_binding.os
libretro_binding/libretro_binding.so
.sconsign.dblite

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "external/gdlibretro"]
path = external/gdlibretro
url = https://github.com/gabrielmedici/gdlibretro

View file

@ -1,40 +0,0 @@
# Dichiarazione del progetto
cmake_minimum_required(VERSION 3.15)
project(gdretroplay)
# Imposta il C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Percorsi per Godot C++ bindings
message(STATUS "Godot C++ Bindings Path: $ENV{GODOT_CPP_BINDINGS_PATH}")
include_directories($ENV{GODOT_CPP_BINDINGS_PATH}/include)
include_directories($ENV{GODOT_CPP_BINDINGS_PATH}/include/core)
include_directories($ENV{GODOT_CPP_BINDINGS_PATH}/include/gen)
# File sorgenti
file(GLOB SOURCES "src/*.cpp")
message(STATUS "Sources found: ${SOURCES}")
# Creazione della libreria condivisa
add_library(${PROJECT_NAME} SHARED ${SOURCES})
# Configurazioni specifiche per piattaforma
if(UNIX AND NOT ANDROID)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_SUFFIX ".so")
endif()
if(ANDROID)
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_SUFFIX ".so")
endif()
# Imposta le proprietà del target
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "gdretroplay")
# Linka le librerie di Godot
link_directories($ENV{GODOT_CPP_BINDINGS_PATH}/bin)
target_link_libraries(${PROJECT_NAME} godot-cpp)

View file

@ -1,13 +0,0 @@
#!/bin/bash
GODOT_CPP_BINDINGS_PATH="$HOME/gits/godot-cpp"
cd "$GODOT_CPP_BINDINGS_PATH"
scons platform=linux target=template_release generate_bindings=yes
cd -
rm -rf build_linux
mkdir build_linux
cd build_linux
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .

View file

@ -1,15 +0,0 @@
{
"entry_symbol": "gdextension_gdretroplay_library_init",
"library": [
{
"platform": "Linux",
"debug": "build/gdretroplay.linux.debug.so",
"release": "build/gdretroplay.linux.release.so"
},
{
"platform": "Android",
"debug": "build/gdretroplay.android.debug.so",
"release": "build/gdretroplay.android.release.so"
}
]
}

View file

@ -1,185 +0,0 @@
#include "emulator_core.h"
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/classes/image.hpp>
#include <dlfcn.h>
namespace EmulatorSystem {
EmulatorCore::EmulatorCore() {
core = std::make_unique<CoreFunctions>();
frame_texture.instantiate();
audio_stream.instantiate();
input_state.fill(0);
}
EmulatorCore::~EmulatorCore() {
cleanup();
}
bool EmulatorCore::initialize_core(const godot::String& core_path) {
cleanup();
core->handle = dlopen(core_path.utf8().get_data(), RTLD_LAZY);
if (!core->handle) return false;
if (!load_core_functions()) {
cleanup();
return false;
}
core->init();
setup_av_system();
return true;
}
bool EmulatorCore::load_core_functions() {
if (!core->handle) return false;
#define LOAD_SYM(sym) \
core->sym = (decltype(core->sym))dlsym(core->handle, "retro_" #sym)
LOAD_SYM(init);
LOAD_SYM(deinit);
LOAD_SYM(run);
LOAD_SYM(load_game);
LOAD_SYM(save_game_state);
LOAD_SYM(load_game_state);
LOAD_SYM(get_memory_data);
LOAD_SYM(set_memory_data);
LOAD_SYM(set_controller_port_device);
LOAD_SYM(serialize_size);
#undef LOAD_SYM
return (core->init && core->run && core->load_game);
}
bool EmulatorCore::load_game(const godot::String& game_path) {
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(game_path, godot::FileAccess::READ);
if (f.is_null()) return false;
save_path = game_path.get_basename() + ".srm";
game_data.resize(f->get_length());
f->get_buffer(game_data.data(), game_data.size());
if (!core->load_game(game_data.data(), game_data.size())) {
return false;
}
// Inizializza il buffer per gli states
if (core->serialize_size) {
state_buffer.resize(core->serialize_size());
}
// Carica SRAM se esiste
load_sram();
return true;
}
void EmulatorCore::process_frame() {
if (core->run) {
core->run();
}
}
bool EmulatorCore::save_state(const godot::String& path) {
if (!core->save_game_state || state_buffer.empty()) return false;
if (!core->save_game_state(state_buffer.data(), state_buffer.size())) {
return false;
}
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(path, godot::FileAccess::WRITE);
if (f.is_null()) return false;
f->store_buffer(state_buffer.data(), state_buffer.size());
return true;
}
bool EmulatorCore::load_state(const godot::String& path) {
if (!core->load_game_state || state_buffer.empty()) return false;
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(path, godot::FileAccess::READ);
if (f.is_null()) return false;
f->get_buffer(state_buffer.data(), state_buffer.size());
return core->load_game_state(state_buffer.data(), state_buffer.size());
}
bool EmulatorCore::load_sram() {
if (!core->get_memory_data) return false;
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(save_path, godot::FileAccess::READ);
if (f.is_null()) return false;
save_data.resize(f->get_length());
f->get_buffer(save_data.data(), save_data.size());
return core->set_memory_data(0, save_data.data());
}
bool EmulatorCore::save_sram() {
if (!core->get_memory_data) return false;
if (!core->get_memory_data(0, save_data.data())) {
return false;
}
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(save_path, godot::FileAccess::WRITE);
if (f.is_null()) return false;
f->store_buffer(save_data.data(), save_data.size());
return true;
}
void EmulatorCore::set_button_pressed(int port, int button, bool pressed) {
if (port >= 0 && port < system_info.input_ports && button < 16) {
if (pressed) {
input_state[port] |= (1 << button);
} else {
input_state[port] &= ~(1 << button);
}
}
}
void EmulatorCore::set_axis_value(int port, int axis, float value) {
// Implementa la gestione degli assi analogici se necessario
}
void EmulatorCore::video_callback(const void* data, unsigned width,
unsigned height, size_t pitch) {
// Implementa il rendering del frame
// ...
}
void EmulatorCore::audio_callback(int16_t left, int16_t right) {
// Implementa la gestione dell'audio
// ...
}
void EmulatorCore::_bind_methods() {
using namespace godot;
ClassDB::bind_method(D_METHOD("initialize_core", "core_path"),
&EmulatorCore::initialize_core);
ClassDB::bind_method(D_METHOD("load_game", "game_path"),
&EmulatorCore::load_game);
ClassDB::bind_method(D_METHOD("process_frame"),
&EmulatorCore::process_frame);
ClassDB::bind_method(D_METHOD("save_state", "path"),
&EmulatorCore::save_state);
ClassDB::bind_method(D_METHOD("load_state", "path"),
&EmulatorCore::load_state);
ClassDB::bind_method(D_METHOD("set_button_pressed", "port", "button", "pressed"),
&EmulatorCore::set_button_pressed);
ClassDB::bind_method(D_METHOD("set_axis_value", "port", "axis", "value"),
&EmulatorCore::set_axis_value);
ClassDB::bind_method(D_METHOD("get_texture"),
&EmulatorCore::get_texture);
ClassDB::bind_method(D_METHOD("get_audio_stream"),
&EmulatorCore::get_audio_stream);
}
} // namespace EmulatorSystem

View file

@ -1,108 +0,0 @@
#ifndef EMULATOR_CORE_H
#define EMULATOR_CORE_H
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/image_texture.hpp>
#include <godot_cpp/classes/audio_stream_generator.hpp>
#include <godot_cpp/classes/audio_stream_generator_playback.hpp>
#include <memory>
#include <string>
#include <vector>
#include <array>
namespace EmulatorSystem {
class EmulatorCore : public godot::Node {
GDCLASS(EmulatorCore, godot::Node)
private:
// Strutture per la gestione del core
struct CoreFunctions {
void* handle{nullptr};
void (*init)();
void (*deinit)();
void (*run)();
bool (*load_game)(const void* data, size_t size);
bool (*save_game_state)(void* data, size_t size);
bool (*load_game_state)(const void* data, size_t size);
bool (*get_memory_data)(unsigned id, void* data);
bool (*set_memory_data)(unsigned id, const void* data);
void (*set_controller_port_device)(unsigned port, unsigned device);
size_t (*serialize_size)();
};
// Strutture per la gestione dell'audio/video
struct SystemInfo {
unsigned width{0};
unsigned height{0};
double fps{60.0};
double sample_rate{44100.0};
unsigned input_ports{2};
};
// Membri privati
std::unique_ptr<CoreFunctions> core;
SystemInfo system_info;
std::vector<uint8_t> game_data;
std::vector<uint8_t> save_data;
std::string save_path;
godot::Ref<godot::ImageTexture> frame_texture;
godot::Ref<godot::AudioStreamGenerator> audio_stream;
godot::Ref<godot::AudioStreamGeneratorPlayback> audio_playback;
std::vector<uint8_t> frame_buffer;
std::array<uint16_t, 16> input_state;
// Buffer per states
std::vector<uint8_t> state_buffer;
// Funzioni private
bool load_core_functions();
void setup_av_system();
void cleanup();
bool load_sram();
bool save_sram();
// Callback handlers
static void video_callback(const void* data, unsigned width,
unsigned height, size_t pitch);
static void audio_callback(int16_t left, int16_t right);
static void input_poll_callback();
static int16_t input_state_callback(unsigned port, unsigned device,
unsigned index, unsigned id);
protected:
static void _bind_methods();
public:
EmulatorCore();
~EmulatorCore();
// Funzioni principali
bool initialize_core(const godot::String& core_path);
bool load_game(const godot::String& game_path);
void process_frame();
void reset();
void close_game();
// Gestione stati
bool save_state(const godot::String& path);
bool load_state(const godot::String& path);
// Input
void set_button_pressed(int port, int button, bool pressed);
void set_axis_value(int port, int axis, float value);
// Getters
godot::Ref<godot::ImageTexture> get_texture() const { return frame_texture; }
godot::Ref<godot::AudioStreamGenerator> get_audio_stream() const { return audio_stream; }
// Configurazione
void set_audio_buffer_size(float seconds);
void set_controller_type(int port, int device_type);
};
} // namespace EmulatorSystem
#endif // EMULATOR_CORE_H

1
external/gdlibretro vendored Submodule

@ -0,0 +1 @@
Subproject commit 7ebbf840187f54466988c0b5b20524ad21ca1d58

View file

@ -0,0 +1,6 @@
{
"entry_class": "LibretroCoreBinding",
"library_path": "res://libretro_binding/libretro_binding.so",
"singleton": false,
"autoload": false
}

24
main.gd
View file

@ -1,20 +1,26 @@
extends Node3D
var xr_interface: XRInterface
var emulator_script: Object
func _ready():
# Inizializzazione OpenXR
xr_interface = XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_initialized():
print("OpenXR initialized succesfully")
print("OpenXR initialized successfully")
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
get_viewport().use_xr = true
else:
print("OpenXR not initialized, please check if your headset is connected")
# Configurazione SubViewport
$SubViewport.size = Vector2(320, 240) # Risoluzione tipica retro
$SubViewport/TextureRect.expand = true
$SubViewport/TextureRect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
# Carica lo script dell'emulatore
emulator_script = preload("res://scripts/emulate.gd").new()
func launch_game():
var core_path = "res://cores/genesis_plus_gx_libretro.so"
var rom_path = "res://roms/megadrive/Sonic the Hedgehog.bin"
if emulator_script.start_game(core_path, rom_path):
print("Il gioco è stato avviato con successo!")
else:
print("Errore durante l'avvio del gioco.")

View file

@ -25,6 +25,10 @@ XrSimulator="*res://addons/xr-simulator/XRSimulator.tscn"
enabled=PackedStringArray("res://addons/godot-xr-tools/plugin.cfg")
[gd_extension]
extensions=PackedStringArray("res://libretro_binding/libretro_binding.gdextension")
[rendering]
renderer/rendering_method="gl_compatibility"

46
scripts/emulate.gd Normal file
View file

@ -0,0 +1,46 @@
extends Node
var libretro_core = LibretroCoreBinding.new() # Use the class directly
func start_game(core_path: String, rom_path: String) -> bool:
"""
Inizializza il core libretro e carica la ROM specificata.
Restituisce true se il gioco è stato caricato correttamente, altrimenti false.
"""
# Carica il gestore libretro
var libretro_handler = preload("res://scripts/libretro_loader.gd").new()
# Inizializzazione del core
if not libretro_handler.initialize(core_path):
print("Errore: impossibile caricare il core libretro:", core_path)
return false
print("Core caricato con successo:", core_path)
# Caricamento della ROM
var rom_data: PackedByteArray = load_rom_data(rom_path)
if not libretro_handler.load_game(rom_data):
print("Errore: impossibile caricare la ROM:", rom_path)
return false
print("Gioco caricato con successo:", rom_path)
return true
func load_rom_data(rom_path: String) -> PackedByteArray:
"""
Carica i dati di una ROM da file.
"""
var file = FileAccess.open(rom_path, FileAccess.READ)
if not file:
push_error("Error opening file: " + rom_path)
return PackedByteArray()
var data = file.get_buffer(file.get_length())
file.close()
return data
func initialize_core(core_path: String) -> bool:
if not libretro_core.initialize(core_path):
print("Errore: impossibile caricare il core libretro")
return false
return true

View file

@ -1,91 +1,57 @@
# libretro_loader.gd
extends Node
# Configurazione SubViewport TV
@onready var tv_viewport: SubViewport = $CRTTV/SubViewport
@onready var tv_texture_rect: TextureRect = $CRTTV/SubViewport/TextureRect
# Stato dell'emulazione
var current_core = null
var current_rom = null
# Funzione per caricare un core
func load_libretro_core(core_path: String):
# Caricamento dinamico del core
func load_libretro_core(core_path: String) -> bool:
current_core = load(core_path)
if not current_core:
push_error("Impossibile caricare il core: " + core_path)
push_error("Failed to load core: " + core_path)
return false
# Inizializzazione base del core
current_core.initialize()
return true
# Funzione per caricare una ROM
func load_rom(rom_path: String):
# Verifica esistenza ROM
func load_rom(rom_path: String) -> bool:
if not FileAccess.file_exists(rom_path):
push_error("ROM non trovata: " + rom_path)
push_error("ROM not found: " + rom_path)
return false
# Caricamento dati ROM
var rom_data = FileAccess.get_file_as_bytes(rom_path)
var file = FileAccess.open(rom_path, FileAccess.READ)
if not file:
push_error("Error opening ROM: " + rom_path)
return false
# Caricamento ROM nel core
var load_result = current_core.load_game(rom_data)
var rom_data = file.get_buffer(file.get_length())
file.close()
if load_result:
current_rom = rom_path
if not current_core.load_game(rom_data):
push_error("Failed to load ROM: " + rom_path)
return false
return load_result
current_rom = rom_path
return true
# Funzione per avviare l'emulazione
func start_emulation(core_path: String, rom_path: String):
# Caricamento core
if not load_libretro_core(core_path):
return false
func start_emulation(core_path: String, rom_path: String) -> bool:
if not load_libretro_core(core_path):
return false
if not load_rom(rom_path):
return false
current_core.run()
return true
# Caricamento ROM
if not load_rom(rom_path):
return false
# Avvio emulazione
current_core.run()
return true
# Processo di rendering
func _process(delta):
if current_core:
# Recupero frame corrente
var frame_data = current_core.get_frame_buffer()
if frame_data:
# Conversione in texture
var image = Image.create_from_data(
current_core.get_frame_width(),
current_core.get_frame_height(),
false,
Image.FORMAT_RGB8,
frame_data
)
var texture = ImageTexture.create_from_image(image)
# Applicazione texture alla TV
tv_texture_rect.texture = texture
# Esempio di utilizzo
func _ready():
# Percorsi di esempio
var CORE_PATH = "res://cores/genesis_plus_gx_libretro.so"
var ROM_PATH = "res://roms/megadrive/Sonic.bin"
# Avvio emulazione
start_emulation(CORE_PATH, ROM_PATH)
# Gestione input
func _input(event):
if current_core:
# Conversione input Godot in input Libretro
current_core.handle_input(event)
func _process(delta):
if current_core:
var frame_data = current_core.get_frame_buffer()
if frame_data:
var image = Image.create_from_data(
current_core.get_frame_width(),
current_core.get_frame_height(),
false,
Image.FORMAT_RGB8,
frame_data
)
var texture = ImageTexture.create_from_image(image)
tv_texture_rect.texture = texture