app/retro_frontend: Libretro frontend

Native fronted for Libretro cores.

https://www.libretro.com/

Ref #52
This commit is contained in:
Emery Hemingway
2016-12-02 13:43:54 +01:00
committed by Norman Feske
parent 26926d2687
commit f3e052f65f
9 changed files with 1372 additions and 0 deletions

View File

@@ -0,0 +1,470 @@
/*
* \brief Interface to Genode services
* \author Emery Hemingway
* \date 2016-07-14
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _RETRO_FRONTEND__CALLBACKS_H_
#define _RETRO_FRONTEND__CALLBACKS_H_
#include "frontend.h"
namespace Libretro {
#include <libretro.h>
/**
* Doesn't work
static
void log_callback(enum retro_log_level level, const char *fmt, ...)
{
char buf[Genode::Log_session::MAX_STRING_LEN];
va_list vp;
va_start(vp, fmt);
Genode::snprintf(buf, sizeof(buf), fmt, vp);
va_end(vp);
char const *msg = buf;
switch (level) {
case RETRO_LOG_DEBUG: Genode::log("Debug: ", msg); return;
case RETRO_LOG_INFO: Genode::log(msg); return;
case RETRO_LOG_WARN: Genode::warning(msg); return;
case RETRO_LOG_ERROR: Genode::error(msg); return;
case RETRO_LOG_DUMMY: Genode::log("Dummy: ", msg); return;
}
}
*/
static
bool environment_callback(unsigned cmd, void *data)
{
switch (cmd) {
case RETRO_ENVIRONMENT_GET_OVERSCAN:
Genode::warning("instructing core not to overscan");
*((bool *)data) = false;
return true;
case RETRO_ENVIRONMENT_GET_CAN_DUPE:
{
bool *b = (bool*)data;
*b = true;
return true;
}
case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
{
const unsigned *level = (const unsigned*)data;
Genode::warning("frontend reports a suggested performance level of \"", *level, "\"");
return true;
}
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
{
char **path = (char **)data;
*path = (char *)"/";
return true;
}
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
{
const retro_pixel_format *format = (retro_pixel_format *)data;
if (*format == RETRO_PIXEL_FORMAT_RGB565)
return true;
else {
char const *s = "";
switch (*format) {
case RETRO_PIXEL_FORMAT_0RGB1555: s = "0RGB1555"; break;
case RETRO_PIXEL_FORMAT_XRGB8888: s = "XRGB8888"; break;
case RETRO_PIXEL_FORMAT_RGB565: s = "RGB565"; break;
case RETRO_PIXEL_FORMAT_UNKNOWN: break;
}
Genode::error("core uses unsupported pixel format ", s);
return false;
}
}
case RETRO_ENVIRONMENT_SHUTDOWN:
global_frontend->env.parent().exit(0);
return true;
case RETRO_ENVIRONMENT_GET_VARIABLE:
{
/********************************************
** TODO: Retrieve variables set by parent **
********************************************/
const retro_variable *var = (retro_variable*)data;
Genode::warning("RETRO_ENVIRONMENT_GET_VARIABLE ", var->key);
return false;
}
case RETRO_ENVIRONMENT_SET_VARIABLES:
{
/**********************************
** Report variables set by core **
**********************************/
try {
global_frontend->variable_reporter.enabled(true);
} catch (...) {
Genode::error("core variables not reported");
return false;
}
Genode::Reporter::Xml_generator gen(global_frontend->variable_reporter, [&] () {
for (const struct retro_variable *var = (retro_variable*)data;
(var && (var->key != NULL) && (var->value != NULL)); ++var)
{
gen.node("variable", [&] () {
gen.attribute("key", var->key);
gen.attribute("value", var->value);
});
}
});
return true;
}
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
return false;
//case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
// global_frontend->core.supports_null_load = data;
// return true;
/*
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
{
retro_log_callback *cb = ( retro_log_callback*)data;
cb->log = log_callback;
return true;
}
*/
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
{
try {
global_frontend->subsystem_reporter.enabled(true);
} catch (...) {
Genode::error("core subsystems not reported");
return false;
}
Genode::Reporter::Xml_generator gen(global_frontend->subsystem_reporter, [&] () {
for (const retro_subsystem_info *info = (retro_subsystem_info*)data;
(info && (info->desc)); ++info)
{
gen.node("subsystem", [&] () {
gen.attribute("desc", info->desc);
gen.attribute("num_roms", info->num_roms);
gen.attribute("id", info->id);
for (unsigned i = 0; i < info->num_roms; ++i) {
const retro_subsystem_rom_info *rom_info =
&info->roms[i];
gen.node("rom", [&] () {
gen.attribute("desc", rom_info->desc);
gen.attribute("valid_extensions", rom_info->valid_extensions);
gen.attribute("need_fullpath", rom_info->need_fullpath);
gen.attribute("block_extract", rom_info->block_extract);
gen.attribute("required", rom_info->required);
for (unsigned j = 0; j < rom_info->num_memory; ++j) {
const retro_subsystem_memory_info *memory =
&rom_info->memory[i];
gen.node("memory", [&] () {
gen.attribute("extension", memory->extension);
gen.attribute("type", memory->type);
});
}
});
}
});
}
});
return true;
}
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
{
try {
global_frontend->controller_reporter.enabled(true);
} catch (...) {
Genode::error("core controllers not reported");
return false;
}
Genode::Reporter::Xml_generator gen(global_frontend->controller_reporter, [&] () {
unsigned index = 0;
for (const retro_controller_info *info = (retro_controller_info*)data;
(info && (info->types)); ++info)
{
gen.node("controller", [&] () {
gen.attribute("port", index);
for (unsigned i = 0; i < info->num_types; ++i) {
const retro_controller_description *type =
&info->types[i];
gen.node("type", [&] () {
gen.attribute("desc", type->desc);
gen.attribute("id", type->id);
});
}
});
}
});
return true;
}
case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER:
{
retro_framebuffer *fb = (retro_framebuffer*)data;
if (!global_frontend->framebuffer.constructed())
return false;
Framebuffer::Mode mode = global_frontend->framebuffer->mode;
fb->width = (unsigned)mode.width();
fb->height = (unsigned)mode.height();
fb->data = global_frontend->framebuffer->ds.local_addr<void>();
fb->pitch = mode.width() * 2;
fb->format = RETRO_PIXEL_FORMAT_RGB565;
fb->memory_flags = RETRO_MEMORY_TYPE_CACHED;
return true;
}
}
Genode::warning(__func__, "(",cmd,") is unhandled");
return false;
}
static
void video_refresh_callback(const void *data,
unsigned src_width, unsigned src_height,
size_t src_pitch)
{
using namespace Genode;
if (data == NULL) /* frame duping? */
return;
uint8_t const *src = (uint8_t const*)data;
uint8_t *dst = global_frontend->framebuffer->ds.local_addr<uint8_t>();
unsigned const dst_width = global_frontend->framebuffer->mode.width();
unsigned const dst_height = global_frontend->framebuffer->mode.height();
unsigned const width = min(src_width, dst_width);
unsigned const height = min(src_height, dst_height);
if (dst != src) {
::size_t dst_pitch = dst_width<<1; /* x2 */
for (unsigned y = 0; y < height; ++y)
memcpy(&dst[y*dst_pitch], &src[y*src_pitch], dst_pitch);
}
global_frontend->framebuffer->session.refresh(0, 0, width, height);
}
template <Genode::size_t CAPACITY>
struct Ring_buffer
{
Genode::size_t wpos { 0 };
Genode::size_t rpos { 0 };
char _data[CAPACITY];
Ring_buffer() { }
Genode::size_t read_avail() const
{
if (wpos > rpos) return wpos - rpos;
else return (wpos - rpos + CAPACITY) % CAPACITY;
}
Genode::size_t write_avail() const
{
if (wpos > rpos) return ((rpos - wpos + CAPACITY) % CAPACITY) - 2;
else if (wpos < rpos) return rpos - wpos;
else return CAPACITY - 2;
}
Genode::size_t write(void const *src, Genode::size_t len)
{
Genode::size_t const avail = write_avail();
if (avail == 0) return 0;
Genode::size_t const limit_len = len > avail ? avail : len;
Genode::size_t const total = wpos + len;
Genode::size_t first, rest;
if (total > CAPACITY) {
first = CAPACITY - wpos;
rest = total % CAPACITY;
} else {
first = limit_len;
rest = 0;
}
Genode::memcpy(&_data[wpos], src, first);
wpos = (wpos + first) % CAPACITY;
if (rest) {
Genode::memcpy(&_data[wpos], ((char const*)src) + first, rest);
wpos = (wpos + rest) % CAPACITY;
}
return limit_len;
}
Genode::size_t read(void *dst, Genode::size_t len)
{
Genode::size_t const avail = read_avail();
if (avail == 0) return 0;
Genode::size_t const limit_len = len > avail ? avail : len;
Genode::size_t const total = rpos + len;
Genode::size_t first, rest;
if (total > CAPACITY) {
first = CAPACITY - rpos;
rest = total % CAPACITY;
} else {
first = limit_len;
rest = 0;
}
Genode::memcpy(dst, &_data[rpos], first);
rpos = (rpos + first) % CAPACITY;
if (rest) {
Genode::memcpy(((char*)dst) + first, &_data[rpos], rest);
rpos = (rpos + rest) % CAPACITY;
}
return limit_len;
}
};
static Ring_buffer<4*1024> audio_frame_buffer;
static Audio_out::Packet *audio_alloc_pos;
static
void audio_sample_noop(int16_t left, int16_t right) { }
static
void audio_sample_callback(int16_t left, int16_t right)
{
Audio_out::Stream *sl = global_frontend->stereo_out->left.stream();
Audio_out::Stream *sr = global_frontend->stereo_out->right.stream();
Audio_out::Packet *pl = 0;
Audio_out::Packet *pr = 0;
if (audio_alloc_pos == nullptr)
audio_alloc_pos = global_frontend->stereo_out->left.stream()->next();
try {
int16_t tmp[Audio_out::PERIOD * 2];
size_t const n = audio_frame_buffer.read(tmp, sizeof(tmp));
(void)n;
pl = sl->next(audio_alloc_pos);
pr = sr->next(audio_alloc_pos);
float *left_content = pl->content();
float *right_content = pr->content();
for (int i = 0; i < Audio_out::PERIOD; i++) {
left_content[i] = (float)(left) / 32768.0f;
right_content[i] = (float)(right) / 32768.0f;
}
global_frontend->stereo_out->left.submit(pl);
global_frontend->stereo_out->right.submit(pr);
audio_alloc_pos = pl;
} catch (...) {
Genode::error(__func__, " failed to submit audio frames");
}
}
static
size_t audio_sample_batch_noop(const int16_t *data, size_t frames) { return 0; }
static
size_t audio_sample_batch_callback(const int16_t *data, size_t frames)
{
audio_frame_buffer.write(data, frames * 2 * 2);
if (audio_frame_buffer.read_avail() > Audio_out::PERIOD * 2 * 2) {
Audio_out::Stream *sl = global_frontend->stereo_out->left.stream();
Audio_out::Stream *sr = global_frontend->stereo_out->right.stream();
Audio_out::Packet *pl = 0;
Audio_out::Packet *pr = 0;
if (audio_alloc_pos == nullptr) {
audio_alloc_pos = global_frontend->stereo_out->left.stream()->next();
}
try {
int16_t tmp[Audio_out::PERIOD * 2];
size_t const n = audio_frame_buffer.read(tmp, sizeof(tmp));
(void)n;
pl = sl->next(audio_alloc_pos);
pr = sr->next(audio_alloc_pos);
float *left_content = pl->content();
float *right_content = pr->content();
for (int i = 0; i < Audio_out::PERIOD; i++) {
left_content[i] = (float)(tmp[i * 2 + 0]) / 32768.0f;
right_content[i] = (float)(tmp[i * 2 + 1]) / 32768.0f;
}
global_frontend->stereo_out->left.submit(pl);
global_frontend->stereo_out->right.submit(pr);
audio_alloc_pos = pl;
} catch (...) {
Genode::error(__func__, " failed to submit audio frames");
}
}
return frames;
}
static
void input_poll_callback() { global_frontend->flush_input(); }
static
int16_t input_state_callback(unsigned port, unsigned device,
unsigned index, unsigned id)
{
Controller *controller = &global_frontend->controller;
return controller ? controller->event(device, index, id) : 0;
}
}
#endif

View File

@@ -0,0 +1,167 @@
/*
* \brief Libretro frontend
* \author Emery Hemingway
* \date 2016-07-03
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#include "frontend.h"
#include "callbacks.h"
Libretro::Frontend::Frontend(Genode::Env &env) : env(env)
{
/* set the global frontend pointer for callbacks */
global_frontend = this;
/****************
** Initialize **
****************/
retro_system_info sys_info;
shared_object.lookup<Retro_get_system_info>
("retro_get_system_info")(&sys_info);
Genode::log("Name: ", sys_info.library_name,
"\nVersion: ", sys_info.library_version,
"\nExtensions: ", sys_info.valid_extensions ?
sys_info.valid_extensions : "");
shared_object.lookup<Retro_set_environment>
("retro_set_environment")(&environment_callback);
shared_object.lookup<Retro_init>("retro_init")();
/*******************
** Set callbacks **
*******************/
shared_object.lookup<Retro_set_video_refresh>
("retro_set_video_refresh")(video_refresh_callback);
try {
stereo_out.construct(env);
shared_object.lookup<Retro_set_audio_sample>
("retro_set_audio_sample")(&audio_sample_callback);
shared_object.lookup<Retro_set_audio_sample_batch>
("retro_set_audio_sample_batch")(&audio_sample_batch_callback);
} catch (...) {
shared_object.lookup<Retro_set_audio_sample>
("retro_set_audio_sample")(&audio_sample_noop);
shared_object.lookup<Retro_set_audio_sample_batch>
("retro_set_audio_sample_batch")(&audio_sample_batch_noop);
}
shared_object.lookup<Retro_set_input_poll>
("retro_set_input_poll")(&input_poll_callback);
shared_object.lookup<Retro_set_input_state>
("retro_set_input_state")(&input_state_callback);
/********************
** Load game data **
********************/
game_info.path = NULL;
game_info.data = NULL;
game_info.size = 0;
game_info.meta = "";
try {
Genode::Xml_node game_node = config_rom.xml().sub_node("game");
rom_name = game_node.attribute_value("rom", Rom_name());
game_path = game_node.attribute_value("path", Game_path());
rom_meta = game_node.attribute_value("name", Rom_name());
if (rom_name != "") {
game_rom.construct(env, rom_name.string());
game_info.data = game_rom->local_addr<const void>();
game_info.size = game_rom->size();
game_info.path = "";
Genode::log("loading game from ROM '", rom_name, "'");
} else if (game_path != "") {
game_info.path = game_path.string();
Genode::log("loading game from path '", game_path, "'");
}
game_info.meta =
(rom_meta != "") ? rom_meta.string() :
(rom_name != "") ? rom_name.string() :
game_path.string();
}
catch (...) { }
if (sys_info.need_fullpath && (game_info.path == NULL))
game_info.path = "/";
if (!(game_info.path || game_info.data)) {
/* some cores don't need game data */
Genode::error("no game content loaded");
}
if (!(retro_load_game(&game_info))) {
Genode::error("failed to load game data");
retro_deinit();
throw ~0;
}
load_memory();
/******************
** Get A/V info **
******************/
retro_system_av_info av_info;
shared_object.lookup<Retro_get_system_av_info>
("retro_get_system_av_info")(&av_info);
Genode::log("FPS of video content: ", (int)av_info.timing.fps);
Genode::log("Sampling rate of audio: ", (int)av_info.timing.sample_rate);
set_av_info(av_info);
framebuffer->session.mode_sigh(mode_handler);
/* flush out the input events that may have piled up */
flush_input();
}
Genode::size_t Component::stack_size() { return 16*1024*sizeof(Genode::addr_t); }
void Component::construct(Genode::Env &env)
{
using namespace Genode;
Libretro::init_keyboard_map();
try {
static Libretro::Frontend inst(env);
return;
}
catch (Shared_object::Invalid_rom_module) {
error("failed to load core"); }
catch (Shared_object::Invalid_symbol) {
error("failed to load required symbols from core"); }
catch (...) { error("failed to init core"); }
env.parent().exit(-1);
}

View File

@@ -0,0 +1,432 @@
/*
* \brief Interface to Genode services
* \author Emery Hemingway
* \date 2016-07-14
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _RETRO_FRONTEND__FRONTEND_H_
#define _RETRO_FRONTEND__FRONTEND_H_
/* Genode includes */
#include <gems/file.h>
#include <audio_out_session/connection.h>
#include <input_session/connection.h>
#include <input/event_queue.h>
#include <framebuffer_session/connection.h>
#include <timer_session/connection.h>
#include <os/reporter.h>
#include <base/attached_rom_dataspace.h>
#include <base/shared_object.h>
#include <base/heap.h>
#include <base/component.h>
#include <base/log.h>
#include <log_session/log_session.h>
/* Libc includes */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
/* Local includes */
#include "input.h"
namespace Libretro {
#include <libretro.h>
struct Frontend;
}
/* Global frontend instance */
static Libretro::Frontend *global_frontend = (Libretro::Frontend*)(Genode::addr_t)666;
/**
* Object to encapsulate Genode services
*/
struct Libretro::Frontend
{
typedef void (*Retro_set_environment)(retro_environment_t);
typedef void (*Retro_set_video_refresh)(retro_video_refresh_t);
typedef void (*Retro_set_audio_sample)(retro_audio_sample_t);
typedef void (*Retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
typedef void (*Retro_set_input_poll)(retro_input_poll_t);
typedef void (*Retro_set_input_state)(retro_input_state_t);
typedef void (*Retro_init)(void);
typedef void (*Retro_deinit)(void);
typedef unsigned (*Retro_api_version)(void);
typedef void (*Retro_get_system_info)(struct retro_system_info *info);
typedef void (*Retro_get_system_av_info)(struct retro_system_av_info *info);
typedef void (*Retro_set_controller_port_device)(unsigned port, unsigned device);
typedef void (*Retro_reset)(void);
typedef void (*Retro_run)(void);
typedef size_t (*Retro_serialize_size)(void);
typedef bool (*Retro_serialize)(void *data, size_t size);
typedef bool (*Retro_unserialize)(const void *data, size_t size);
typedef void (*Retro_cheat_reset)(void);
typedef void (*Retro_cheat_set)(unsigned index, bool enabled, const char *code);
typedef bool (*Retro_load_game)(const struct retro_game_info *game);
typedef bool (*Retro_load_game_special)(
unsigned game_type,
const struct retro_game_info *info, size_t num_info
);
typedef void (*Retro_unload_game)(void);
typedef unsigned (*Retro_get_region)(void);
typedef void *(*Retro_get_memory_data)(unsigned id);
typedef size_t (*Retro_get_memory_size)(unsigned id);
Genode::Env &env;
Genode::Heap heap { env.ram(), env.rm() };
Genode::Attached_rom_dataspace config_rom { env, "config" };
typedef Genode::String<128> Name;
Name const name { config_rom.xml().attribute_value("core", Name()) };
struct Dynamic_core : Genode::Shared_object
{
Dynamic_core(Genode::Env &env, Genode::Allocator &alloc, Name const &name)
: Genode::Shared_object(env, alloc, name.string(), BIND_NOW, DONT_KEEP)
{
unsigned api_version = lookup<Retro_api_version>
("retro_api_version")();
if (api_version != RETRO_API_VERSION) {
Genode::error("core ", name.string(),
" uses unsupported API version ", api_version);
throw Genode::Shared_object::Invalid_rom_module();
}
}
} shared_object { env, heap, name };
Retro_init retro_init =
shared_object.lookup<Retro_init>("retro_init");
Retro_deinit retro_deinit =
shared_object.lookup<Retro_deinit>("retro_deinit");
Retro_load_game retro_load_game =
shared_object.lookup<Retro_load_game>("retro_load_game");
Retro_unload_game retro_unload_game =
shared_object.lookup<Retro_unload_game>("retro_unload_game");
Retro_get_memory_data retro_get_memory_data =
shared_object.lookup<Retro_get_memory_data>("retro_get_memory_data");
Retro_get_memory_size retro_get_memory_size =
shared_object.lookup<Retro_get_memory_size>("retro_get_memory_size");
/***************
** Game data **
***************/
typedef Genode::String<64> Rom_name;
typedef Genode::String<128> Game_path;
Rom_name rom_name;
Game_path game_path;
Rom_name rom_meta;
Genode::Lazy_volatile_object<Genode::Attached_rom_dataspace> game_rom;
retro_game_info game_info;
/******************************
** Persistent memory states **
******************************/
struct Memory_file
{
void *data = nullptr;
size_t size = 0;
unsigned const id;
char const *path;
int fd = -1;
Genode::size_t file_size()
{
struct stat s;
s.st_size = 0;
stat(path, &s);
return s.st_size;
}
bool open_file_for_data()
{
if (!(data && size))
return false;
if (fd == -1)
fd = ::open(path, O_RDWR|O_CREAT);
return fd != -1;
}
Memory_file(unsigned id, char const *filename)
: id(id), path(filename)
{ }
~Memory_file() { if (fd != -1) close(fd); }
void read()
{
if (open_file_for_data()) {
lseek(fd, 0, SEEK_SET);
size_t remain = Genode::min(size, file_size());
size_t offset = 0;
do {
ssize_t n = ::read(fd, ((char*)data)+offset, remain);
if (n == -1) {
Genode::error("failed to read from ", path);
break;
}
remain -= n;
offset += n;
} while (remain);
}
}
void write()
{
if (open_file_for_data()) {
lseek(fd, 0, SEEK_SET);
ftruncate(fd, size);
size_t remain = size;
size_t offset = 0;
do {
ssize_t n = ::write(fd, ((char const *)data)+offset, remain);
if (n == -1) {
Genode::error("failed to write to ", path);
break;
}
remain -= n;
offset += n;
} while (remain);
}
}
};
Memory_file save_file { RETRO_MEMORY_SAVE_RAM, "save" };
Memory_file rtc_file { RETRO_MEMORY_RTC, "rtc" };
Memory_file system_file { RETRO_MEMORY_SYSTEM_RAM, "system" };
void refresh(Memory_file &memory)
{
memory.data = retro_get_memory_data(memory.id);
memory.size = retro_get_memory_size(memory.id);
}
void load_memory()
{
refresh(save_file);
refresh(rtc_file);
refresh(system_file);
save_file.read();
rtc_file.read();
system_file.read();
}
void save_memory()
{
refresh(save_file);
save_file.write();
refresh(rtc_file);
rtc_file.write();
refresh(system_file);
system_file.write();
}
/******************
** Fronted exit **
******************/
void exit()
{
save_memory();
retro_unload_game();
retro_deinit();
}
/**
* Exit when the framebuffer is resized to zero
*/
void handle_mode()
{
framebuffer->update_mode();
if ((framebuffer->mode.width() == 0) &&
(framebuffer->mode.height() == 0))
{
Genode::error("zero framebuffer mode received, exiting");
exit();
env.parent().exit(0);
}
}
Genode::Signal_handler<Frontend> mode_handler
{ env.ep(), *this, &Frontend::handle_mode };
/*************************************************
** Signal handler to advance core by one frame **
*************************************************/
Retro_run retro_run =
shared_object.lookup<Retro_run>("retro_run");
void run() { retro_run(); }
Genode::Signal_handler<Frontend> core_runner
{ env.ep(), *this, &Frontend::run };
Timer::Connection timer { env };
struct Framebuffer
{
::Framebuffer::Connection session;
::Framebuffer::Mode mode;
Genode::Attached_dataspace ds;
Framebuffer(Genode::Env &env, ::Framebuffer::Mode mode)
: session(env, mode), ds(env.rm(), session.dataspace())
{ update_mode(); }
void update_mode() { mode = session.mode(); }
};
Genode::Lazy_volatile_object<Framebuffer> framebuffer;
struct Stereo_out
{
Audio_out::Connection left;
Audio_out::Connection right;
Stereo_out(Genode::Env &env)
:
left(env, "left", false, false),
right(env, "right", false, false)
{ }
};
Genode::Lazy_volatile_object<Stereo_out> stereo_out;
Genode::Reporter variable_reporter { "variables" };
Genode::Reporter subsystem_reporter { "subsystems" };
Genode::Reporter controller_reporter { "controllers" };
Controller controller { env };
int quarter_fps = 0;
unsigned long sample_start;
/***********************************
** Frame counting signal handler **
***********************************/
int fb_sync_sample_count = 0;
/**
* Sample the framebuffer sync for a quarter-second
* to determine if it matches the FPS
*/
void fb_sync_sample()
{
++fb_sync_sample_count;
if (fb_sync_sample_count == 1) {
sample_start = timer.elapsed_ms();
} else if (fb_sync_sample_count == quarter_fps) {
fb_sync_sample_count = 0;
int sync_ms = (timer.elapsed_ms() - sample_start)/quarter_fps;
int want_ms = 250.0 / quarter_fps;
int diff = want_ms - sync_ms;
if (diff > 10 || diff < -10) {
framebuffer->session.sync_sigh(Genode::Signal_context_capability());
Genode::warning("framebuffer sync unsuitable, "
"using alternative timing source");
timer.sigh(core_runner);
timer.trigger_periodic(250000 / quarter_fps);
} else {
Genode::log("using framebuffer sync as timing source");
framebuffer->session.sync_sigh(core_runner);
}
}
}
Genode::Signal_handler<Frontend> fb_sync_sampler
{ env.ep(), *this, &Frontend::fb_sync_sample };
/***************
** Unpausing **
***************/
void handle_input()
{
if (controller.unpaused()) {
controller.input.sigh(Genode::Signal_context_capability());
fb_sync_sample_count = 0;
framebuffer->session.sync_sigh(fb_sync_sampler);
}
}
Genode::Signal_handler<Frontend> resume_handler
{ env.ep(), *this, &Frontend::handle_input };
Frontend(Genode::Env &env);
~Frontend()
{
exit();
global_frontend = nullptr;
}
void set_av_info(retro_system_av_info &av_info)
{
framebuffer.construct(
env, ::Framebuffer::Mode(av_info.geometry.base_width,
av_info.geometry.base_height,
::Framebuffer::Mode::RGB565));
framebuffer->session.mode_sigh(mode_handler);
/* start audio streams */
if (stereo_out.constructed()) {
stereo_out->left.start();
stereo_out->right.start();
}
quarter_fps = av_info.timing.fps / 4;
fb_sync_sample_count = 0;
framebuffer->session.sync_sigh(fb_sync_sampler);
}
void flush_input()
{
controller.flush();
if (controller.paused) {
/* unset signal handler for rendering */
framebuffer->session.sync_sigh(Genode::Signal_context_capability());
timer.sigh(Genode::Signal_context_capability());
/* set signal handler for unpausing */
controller.input.sigh(resume_handler);
/* a good time to flush memory to the FS */
save_memory();
}
}
};
#endif

View File

@@ -0,0 +1,286 @@
/*
* \brief Libretro input mapping
* \author Emery Hemingway
* \date 2016-07-08
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _RETRO_FRONTEND__INPUT_H_
#define _RETRO_FRONTEND__INPUT_H_
/* Genode includes */
#include <input_session/connection.h>
#include <input/event_queue.h>
namespace Libretro {
#include <libretro.h>
struct Controller;
static unsigned genode_map[Input::Keycode::KEY_MAX];
}
struct Libretro::Controller
{
enum {
RETRO_JOYPAD_MAX = RETRO_DEVICE_ID_JOYPAD_R3+1,
RETRO_MOUSE_MAX = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN
};
int16_t joypad_state[RETRO_JOYPAD_MAX];
int16_t mouse_state[RETRO_MOUSE_MAX];
int16_t keyboard_state[RETROK_LAST];
Input::Connection input;
Genode::Attached_dataspace input_ds;
Input::Event const * const events =
input_ds.local_addr<Input::Event>();
bool paused = false;
Controller(Genode::Env &env)
: input(env), input_ds(env.rm(), input.dataspace())
{
Genode::memset( joypad_state, 0x00, sizeof( joypad_state));
Genode::memset( mouse_state, 0x00, sizeof( mouse_state));
Genode::memset(keyboard_state, 0x00, sizeof(keyboard_state));
}
bool unpaused()
{
using namespace Input;
unsigned num_events = input.flush();
for (unsigned i = 0; i < num_events; ++i) {
Input::Event const &ev = events[i];
if ((ev.code() == KEY_PAUSE) && (ev.type() == Event::Type::RELEASE)) {
paused = false;
return true;
}
}
return false;
}
void flush()
{
using namespace Input;
mouse_state[RETRO_DEVICE_ID_MOUSE_X] =
mouse_state[RETRO_DEVICE_ID_MOUSE_Y] = 0;
unsigned num_events = input.flush();
for (unsigned i = 0; i < num_events; ++i) {
Input::Event const &ev = events[i];
uint16_t v = 0; /* assume a release */
switch (ev.type()) {
case Event::Type::MOTION:
if (ev.relative_motion()) {
mouse_state[RETRO_DEVICE_ID_MOUSE_X] += ev.rx();
mouse_state[RETRO_DEVICE_ID_MOUSE_Y] += ev.ry();
} else if (ev.absolute_motion()) {
mouse_state[RETRO_DEVICE_ID_MOUSE_X] += ev.ax();
mouse_state[RETRO_DEVICE_ID_MOUSE_Y] += ev.ay();
} break;
case Event::Type::PRESS:
v = 1; /* not a release */
case Event::Type::RELEASE:
switch (ev.code()) {
case BTN_MIDDLE: mouse_state[RETRO_DEVICE_ID_MOUSE_MIDDLE] = v; break;
case BTN_GEAR_DOWN: mouse_state[RETRO_DEVICE_ID_MOUSE_WHEELDOWN] = v; break;
case BTN_GEAR_UP: mouse_state[RETRO_DEVICE_ID_MOUSE_WHEELUP] = v; break;
case BTN_B: joypad_state[RETRO_DEVICE_ID_JOYPAD_B] = v; break;
case BTN_Y: joypad_state[RETRO_DEVICE_ID_JOYPAD_Y] = v; break;
case BTN_SELECT: joypad_state[RETRO_DEVICE_ID_JOYPAD_SELECT] = v; break;
case BTN_START: joypad_state[RETRO_DEVICE_ID_JOYPAD_START] = v; break;
case BTN_FORWARD: joypad_state[RETRO_DEVICE_ID_JOYPAD_UP] = v; break;
case BTN_BACK: joypad_state[RETRO_DEVICE_ID_JOYPAD_DOWN] = v; break;
case BTN_LEFT: joypad_state[RETRO_DEVICE_ID_JOYPAD_LEFT] =
mouse_state[RETRO_DEVICE_ID_MOUSE_LEFT] = v; break;
case BTN_RIGHT: joypad_state[RETRO_DEVICE_ID_JOYPAD_RIGHT] =
mouse_state[RETRO_DEVICE_ID_MOUSE_RIGHT] = v; break;
case BTN_A: joypad_state[RETRO_DEVICE_ID_JOYPAD_A] = v; break;
case BTN_X: joypad_state[RETRO_DEVICE_ID_JOYPAD_X] = v; break;
case BTN_TL: joypad_state[RETRO_DEVICE_ID_JOYPAD_L] = v; break;
case BTN_TR: joypad_state[RETRO_DEVICE_ID_JOYPAD_R] = v; break;
case BTN_TL2: joypad_state[RETRO_DEVICE_ID_JOYPAD_L2] = v; break;
case BTN_TR2: joypad_state[RETRO_DEVICE_ID_JOYPAD_R2] = v; break;
case BTN_THUMBL: joypad_state[RETRO_DEVICE_ID_JOYPAD_L3] = v; break;
case BTN_THUMBR: joypad_state[RETRO_DEVICE_ID_JOYPAD_R3] = v; break;
case KEY_PAUSE: paused = true; return; /* stop handling events */
default:
keyboard_state[genode_map[ev.code()]] = v; break;
} break;
/*
case Event::Type::FOCUS:
if (ev.code() == 0) {
paused = true;
return;
}
*/
default: break;
}
}
}
int16_t event(unsigned device, unsigned index, unsigned id)
{
switch (device) {
case RETRO_DEVICE_JOYPAD: return joypad_state[id];
case RETRO_DEVICE_MOUSE: return mouse_state[id];
case RETRO_DEVICE_KEYBOARD: return keyboard_state[id];
default: return 0;
}
}
};
namespace Libretro {
void init_keyboard_map()
{
using namespace Input;
for (unsigned i = 0; i < Input::Keycode::KEY_MAX; ++i) switch (i) {
case KEY_ESC: genode_map[i] = RETROK_ESCAPE; break;
case KEY_1: genode_map[i] = RETROK_1; break;
case KEY_2: genode_map[i] = RETROK_2; break;
case KEY_3: genode_map[i] = RETROK_3; break;
case KEY_4: genode_map[i] = RETROK_4; break;
case KEY_5: genode_map[i] = RETROK_5; break;
case KEY_6: genode_map[i] = RETROK_6; break;
case KEY_7: genode_map[i] = RETROK_7; break;
case KEY_8: genode_map[i] = RETROK_8; break;
case KEY_9: genode_map[i] = RETROK_9; break;
case KEY_0: genode_map[i] = RETROK_0; break;
case KEY_MINUS: genode_map[i] = RETROK_MINUS; break;
case KEY_EQUAL: genode_map[i] = RETROK_EQUALS; break;
case KEY_BACKSPACE: genode_map[i] = RETROK_BACKSPACE; break;
case KEY_TAB: genode_map[i] = RETROK_TAB; break;
case KEY_Q: genode_map[i] = RETROK_q; break;
case KEY_W: genode_map[i] = RETROK_w; break;
case KEY_E: genode_map[i] = RETROK_e; break;
case KEY_R: genode_map[i] = RETROK_r; break;
case KEY_T: genode_map[i] = RETROK_t; break;
case KEY_Y: genode_map[i] = RETROK_y; break;
case KEY_U: genode_map[i] = RETROK_u; break;
case KEY_I: genode_map[i] = RETROK_i; break;
case KEY_O: genode_map[i] = RETROK_o; break;
case KEY_P: genode_map[i] = RETROK_p; break;
case KEY_LEFTBRACE: genode_map[i] = RETROK_LEFTBRACKET; break;
case KEY_RIGHTBRACE: genode_map[i] = RETROK_RIGHTBRACKET; break;
case KEY_ENTER: genode_map[i] = RETROK_RETURN; break;
case KEY_LEFTCTRL: genode_map[i] = RETROK_LCTRL; break;
case KEY_A: genode_map[i] = RETROK_a; break;
case KEY_S: genode_map[i] = RETROK_s; break;
case KEY_D: genode_map[i] = RETROK_d; break;
case KEY_F: genode_map[i] = RETROK_f; break;
case KEY_G: genode_map[i] = RETROK_g; break;
case KEY_H: genode_map[i] = RETROK_h; break;
case KEY_J: genode_map[i] = RETROK_j; break;
case KEY_K: genode_map[i] = RETROK_k; break;
case KEY_L: genode_map[i] = RETROK_l; break;
case KEY_SEMICOLON: genode_map[i] = RETROK_SEMICOLON; break;
case KEY_APOSTROPHE: genode_map[i] = RETROK_QUOTE; break;
case KEY_GRAVE: genode_map[i] = RETROK_BACKQUOTE; break;
case KEY_LEFTSHIFT: genode_map[i] = RETROK_LSHIFT; break;
case KEY_BACKSLASH: genode_map[i] = RETROK_BACKSLASH; break;
case KEY_Z: genode_map[i] = RETROK_z; break;
case KEY_X: genode_map[i] = RETROK_x; break;
case KEY_C: genode_map[i] = RETROK_c; break;
case KEY_V: genode_map[i] = RETROK_v; break;
case KEY_B: genode_map[i] = RETROK_b; break;
case KEY_N: genode_map[i] = RETROK_n; break;
case KEY_M: genode_map[i] = RETROK_m; break;
case KEY_COMMA: genode_map[i] = RETROK_COMMA; break;
case KEY_DOT: genode_map[i] = RETROK_PERIOD; break;
case KEY_SLASH: genode_map[i] = RETROK_SLASH; break;
case KEY_RIGHTSHIFT: genode_map[i] = RETROK_RSHIFT; break;
case KEY_KPASTERISK: genode_map[i] = RETROK_KP_MULTIPLY; break;
case KEY_LEFTALT: genode_map[i] = RETROK_LALT; break;
case KEY_SPACE: genode_map[i] = RETROK_SPACE; break;
case KEY_CAPSLOCK: genode_map[i] = RETROK_CAPSLOCK; break;
case KEY_F1: genode_map[i] = RETROK_F1; break;
case KEY_F2: genode_map[i] = RETROK_F2; break;
case KEY_F3: genode_map[i] = RETROK_F3; break;
case KEY_F4: genode_map[i] = RETROK_F4; break;
case KEY_F5: genode_map[i] = RETROK_F5; break;
case KEY_F6: genode_map[i] = RETROK_F6; break;
case KEY_F7: genode_map[i] = RETROK_F7; break;
case KEY_F8: genode_map[i] = RETROK_F8; break;
case KEY_F9: genode_map[i] = RETROK_F9; break;
case KEY_F10: genode_map[i] = RETROK_F10; break;
case KEY_NUMLOCK: genode_map[i] = RETROK_NUMLOCK; break;
case KEY_SCROLLLOCK: genode_map[i] = RETROK_SCROLLOCK; break;
case KEY_KP7: genode_map[i] = RETROK_KP7; break;
case KEY_KP8: genode_map[i] = RETROK_KP8; break;
case KEY_KP9: genode_map[i] = RETROK_KP9; break;
case KEY_KPMINUS: genode_map[i] = RETROK_KP_MINUS; break;
case KEY_KP4: genode_map[i] = RETROK_KP4; break;
case KEY_KP5: genode_map[i] = RETROK_KP5; break;
case KEY_KP6: genode_map[i] = RETROK_KP6; break;
case KEY_KPPLUS: genode_map[i] = RETROK_KP_PLUS; break;
case KEY_KP1: genode_map[i] = RETROK_KP1; break;
case KEY_KP2: genode_map[i] = RETROK_KP2; break;
case KEY_KP3: genode_map[i] = RETROK_KP3; break;
case KEY_KP0: genode_map[i] = RETROK_KP0; break;
case KEY_KPDOT: genode_map[i] = RETROK_KP_PERIOD; break;
case KEY_F11: genode_map[i] = RETROK_F11; break;
case KEY_F12: genode_map[i] = RETROK_F12; break;
case KEY_KPENTER: genode_map[i] = RETROK_KP_ENTER; break;
case KEY_RIGHTCTRL: genode_map[i] = RETROK_RCTRL; break;
case KEY_KPSLASH: genode_map[i] = RETROK_KP_DIVIDE; break;
case KEY_SYSRQ: genode_map[i] = RETROK_SYSREQ; break;
case KEY_RIGHTALT: genode_map[i] = RETROK_RALT; break;
case KEY_HOME: genode_map[i] = RETROK_HOME; break;
case KEY_UP: genode_map[i] = RETROK_UP; break;
case KEY_PAGEUP: genode_map[i] = RETROK_PAGEUP; break;
case KEY_LEFT: genode_map[i] = RETROK_LEFT; break;
case KEY_RIGHT: genode_map[i] = RETROK_RIGHT; break;
case KEY_END: genode_map[i] = RETROK_END; break;
case KEY_DOWN: genode_map[i] = RETROK_DOWN; break;
case KEY_PAGEDOWN: genode_map[i] = RETROK_PAGEDOWN; break;
case KEY_INSERT: genode_map[i] = RETROK_INSERT; break;
case KEY_DELETE: genode_map[i] = RETROK_DELETE; break;
case KEY_POWER: genode_map[i] = RETROK_POWER; break;
case KEY_KPEQUAL: genode_map[i] = RETROK_KP_EQUALS; break;
case KEY_KPPLUSMINUS: genode_map[i] = RETROK_KP_MINUS; break;
case KEY_LEFTMETA: genode_map[i] = RETROK_LMETA; break;
case KEY_RIGHTMETA: genode_map[i] = RETROK_RMETA; break;
case KEY_COMPOSE: genode_map[i] = RETROK_COMPOSE; break;
case KEY_UNDO: genode_map[i] = RETROK_UNDO; break;
case KEY_HELP: genode_map[i] = RETROK_HELP; break;
case KEY_MENU: genode_map[i] = RETROK_MENU; break;
case KEY_F13: genode_map[i] = RETROK_F13; break;
case KEY_F14: genode_map[i] = RETROK_F14; break;
case KEY_F15: genode_map[i] = RETROK_F15; break;
case KEY_BREAK: genode_map[i] = RETROK_BREAK; break;
default: genode_map[i] = RETROK_UNKNOWN; break;
}
}
};
#endif

View File

@@ -0,0 +1,3 @@
TARGET = retro_frontend
SRC_CC = component.cc
LIBS = base libretro libc file