Files
genode-world/src/app/retro_frontend/callbacks.h
Emery Hemingway f3e052f65f app/retro_frontend: Libretro frontend
Native fronted for Libretro cores.

https://www.libretro.com/

Ref #52
2016-12-06 18:45:39 +01:00

471 lines
12 KiB
C++

/*
* \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