diff --git a/run/driar.run b/run/driar.run index 6bbf95f..d6b874d 100644 --- a/run/driar.run +++ b/run/driar.run @@ -15,6 +15,7 @@ set unzip [check_installed unzip] set build_components { core init app/retro_frontend + drivers/audio drivers/framebuffer drivers/input drivers/timer @@ -29,7 +30,8 @@ proc platform_drv_policy {} { return { - } + + } } append_platform_drv_build_components @@ -95,6 +97,10 @@ append_if [have_spec ps2] config { } append config { + + + + @@ -112,10 +118,13 @@ append config { - - - - + + + + + + + @@ -166,6 +175,7 @@ if {![file exist bin/Driar.nes]} { # generic modules set boot_modules { core init ld.lib.so + audio_drv fb_upscale input_remap libc.lib.so diff --git a/run/superbossgaiden.run b/run/superbossgaiden.run index 57bbdcf..c17b468 100644 --- a/run/superbossgaiden.run +++ b/run/superbossgaiden.run @@ -15,6 +15,7 @@ set unzip [check_installed unzip] set build_components { core init app/retro_frontend + drivers/audio drivers/framebuffer drivers/input drivers/timer @@ -29,7 +30,9 @@ proc platform_drv_policy {} { return { - } + + } + } append_platform_drv_build_components @@ -95,6 +98,10 @@ append_if [have_spec ps2] config { } append config { + + + + @@ -112,12 +119,13 @@ append config { - - - - - - + + + + + + + @@ -168,6 +176,7 @@ if {![file exist "bin/superbossgaiden.sfc"]} { # generic modules set boot_modules { core init ld.lib.so + audio_drv fb_upscale input_remap libc.lib.so diff --git a/src/app/retro_frontend/audio.h b/src/app/retro_frontend/audio.h new file mode 100644 index 0000000..c36b7a8 --- /dev/null +++ b/src/app/retro_frontend/audio.h @@ -0,0 +1,259 @@ +/* + * \brief Retro_frontend audio + * \author Emery Hemingway + * \date 2016-12-13 + */ + +/* + * 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__AUDIO_H_ +#define _RETRO_FRONTEND__AUDIO_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include + +namespace Retro_frontend { + #include + + template + struct Ring_buffer; + struct Stereo_out; + + enum { LEFT, RIGHT, NUM_CHANNELS }; + + enum { + SHIFT = 16, + SHIFT_ONE = 1 << SHIFT + }; + + unsigned audio_shift_factor = SHIFT_ONE; + static unsigned audio_input_period = 0; +} + + +/** + * Ring buffer using virtual addressing and Buffer Overflowâ„¢ technology + */ +template +struct Retro_frontend::Ring_buffer : Genode::Lock +{ + enum { BUFFER_SIZE = sizeof(TYPE)*CAPACITY }; + + Genode::Env &env; + + Genode::addr_t map_first; + Genode::addr_t map_second; + + TYPE *buffer; + + Genode::size_t wpos = 0; + Genode::size_t rpos = 0; + + Genode::Ram_dataspace_capability buffer_ds = env.ram().alloc(BUFFER_SIZE); + + Ring_buffer(Genode::Env &env) : env(env) + { + { + /* a hack to find the right sized void in the address space */ + Genode::Attached_ram_dataspace filler(env.ram(), env.rm(), BUFFER_SIZE*2); + map_first = (Genode::addr_t)filler.local_addr(); + } + + map_second = map_first+BUFFER_SIZE; + + /* attach the buffer in two consecutive regions */ + map_first = env.rm().attach_at(buffer_ds, map_first, BUFFER_SIZE); + map_second = env.rm().attach_at(buffer_ds, map_second, BUFFER_SIZE); + if ((map_first+BUFFER_SIZE) != map_second) { + Genode::error("failed to map ring buffer to consecutive regions"); + throw Genode::Exception(); + } + + buffer = (TYPE *)map_first; + } + + ~Ring_buffer() + { + env.rm().detach(map_second); + env.rm().detach(map_first); + env.ram().free(buffer_ds); + } + + 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(TYPE const *src, Genode::size_t len) + { + using Genode::size_t; + + len = Genode::min(len, write_avail()); + + TYPE *wbuf = &buffer[wpos]; + + for (size_t i = 0; i < len; ++i) + wbuf[i] = src[i]; + + wpos = (wpos + len) % CAPACITY; + return len; + } + + void drain_period(float *periodl, float *periodr) + { + using namespace Genode; + + size_t const avail = read_avail(); + + if (avail < audio_input_period*2) { + Genode::memset(periodl, 0x00, sizeof(float)*Audio_out::PERIOD); + Genode::memset(periodr, 0x00, sizeof(float)*Audio_out::PERIOD); + return; + } + + int16_t *rbuf = &buffer[rpos]; + + size_t buf_off; + for (size_t pkt_off = 0; pkt_off < Audio_out::PERIOD; ++pkt_off) + { + buf_off = 2*((pkt_off*audio_shift_factor)>>SHIFT); + periodl[pkt_off] = rbuf[buf_off+0] / 32768.0f; + periodr[pkt_off] = rbuf[buf_off+1] / 32768.0f; + } + + rpos = (rpos+audio_input_period*2) % CAPACITY; + } +}; + + +/** + * Thread for converting samples at the core sample rate + * to the native sample rate + */ +struct Retro_frontend::Stereo_out : Genode::Thread +{ + Audio_out::Connection left; + Audio_out::Connection right; + + Ring_buffer buffer; + + Genode::Lock run_lock { Genode::Lock::LOCKED }; + + bool running = false; + + void entry() override; + + Stereo_out(Genode::Env &env) + : + Genode::Thread(env, "audio-sync", 8*1024, + env.cpu().affinity_space().location_of_index(1), + Weight(Genode::Cpu_session::Weight::DEFAULT_WEIGHT-1), + env.cpu()), + left( env, "left", false, true), + right(env, "right", false, true), + buffer(env) + { + start(); + } + + void start_stream() + { + running = true; + run_lock.unlock(); + } + + void stop_stream() + { + running = false; + } +}; + + +static Genode::Constructible stereo_out; + + +static +void audio_sample_noop(int16_t left, int16_t right) { } + + +static /* not called in pratice */ +void audio_sample_callback(int16_t left, int16_t right) +{ + stereo_out->buffer.lock(); + stereo_out->buffer.write(&left, 1); + stereo_out->buffer.write(&right, 1); + stereo_out->buffer.unlock(); +} + + +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) +{ + Genode::Lock::Guard guard(stereo_out->buffer); + return stereo_out->buffer.write(data, frames*2)/2; +} + + +void Retro_frontend::Stereo_out::entry() +{ + Audio_out::Packet *p[NUM_CHANNELS]; + + for (;;) { + run_lock.lock(); + + p[LEFT] = left.stream()->next(); + + /* stuff the buffer a bit */ + for (auto i = 0; i < 4; ++i) + p[LEFT] = left.stream()->next(p[LEFT]); + + left.start(); + right.start(); + + while (running) { + unsigned const ppos = left.stream()->packet_position(p[LEFT]); + p[RIGHT] = right.stream()->get(ppos); + + buffer.lock(); + buffer.drain_period(p[LEFT]->content(), p[RIGHT]->content()); + buffer.unlock(); + + left.submit(p[LEFT]); + right.submit(p[RIGHT]); + + p[LEFT] = left.stream()->next(p[LEFT]); + + left.wait_for_progress(); + } + + /* empty the buffer to resync when started again */ + while (!p[LEFT]->played()) + left.wait_for_progress(); + + left.stop(); + right.stop(); + } +} + +#endif diff --git a/src/app/retro_frontend/callbacks.h b/src/app/retro_frontend/callbacks.h index f32d664..3e67e1f 100644 --- a/src/app/retro_frontend/callbacks.h +++ b/src/app/retro_frontend/callbacks.h @@ -16,10 +16,6 @@ #include "frontend.h" -namespace Libretro { - -#include - /** * Doesn't work @@ -49,6 +45,8 @@ void log_callback(enum retro_log_level level, const char *fmt, ...) static bool environment_callback(unsigned cmd, void *data) { + using namespace Retro_frontend; + switch (cmd) { case RETRO_ENVIRONMENT_GET_OVERSCAN: @@ -232,14 +230,14 @@ bool environment_callback(unsigned cmd, void *data) { retro_framebuffer *fb = (retro_framebuffer*)data; - if (!global_frontend->framebuffer.constructed()) + if (!framebuffer.constructed()) return false; - Framebuffer::Mode mode = global_frontend->framebuffer->mode; + ::Framebuffer::Mode mode = framebuffer->mode; fb->width = (unsigned)mode.width(); fb->height = (unsigned)mode.height(); - fb->data = global_frontend->framebuffer->ds.local_addr(); + fb->data = framebuffer->ds.local_addr(); fb->pitch = mode.width() * 2; fb->format = RETRO_PIXEL_FORMAT_RGB565; fb->memory_flags = RETRO_MEMORY_TYPE_CACHED; @@ -257,15 +255,17 @@ void video_refresh_callback(const void *data, unsigned src_width, unsigned src_height, size_t src_pitch) { + using namespace Retro_frontend; 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 *dst = framebuffer->ds.local_addr(); - unsigned const dst_width = global_frontend->framebuffer->mode.width(); - unsigned const dst_height = global_frontend->framebuffer->mode.height(); + unsigned const dst_width = framebuffer->mode.width(); + unsigned const dst_height = framebuffer->mode.height(); unsigned const width = min(src_width, dst_width); unsigned const height = min(src_height, dst_height); @@ -277,179 +277,7 @@ void video_refresh_callback(const void *data, memcpy(&dst[y*dst_pitch], &src[y*src_pitch], dst_pitch); } - global_frontend->framebuffer->session.refresh(0, 0, width, height); -} - - -template -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; + framebuffer->session.refresh(0, 0, width, height); } @@ -461,10 +289,10 @@ static int16_t input_state_callback(unsigned port, unsigned device, unsigned index, unsigned id) { + using namespace Retro_frontend; + Controller *controller = &global_frontend->controller; return controller ? controller->event(device, index, id) : 0; } -} - #endif diff --git a/src/app/retro_frontend/component.cc b/src/app/retro_frontend/component.cc index 4d4bf4e..1010287 100644 --- a/src/app/retro_frontend/component.cc +++ b/src/app/retro_frontend/component.cc @@ -11,10 +11,13 @@ * under the terms of the GNU General Public License version 2. */ +/* libc includes */ +#include + #include "frontend.h" #include "callbacks.h" -Libretro::Frontend::Frontend(Genode::Env &env) : env(env) +Retro_frontend::Frontend::Frontend(Libc::Env &env) : env(env) { /* set the global frontend pointer for callbacks */ global_frontend = this; @@ -55,7 +58,6 @@ Libretro::Frontend::Frontend(Genode::Env &env) : env(env) shared_object.lookup ("retro_set_audio_sample_batch")(&audio_sample_batch_callback); - } catch (...) { shared_object.lookup @@ -143,18 +145,17 @@ Libretro::Frontend::Frontend(Genode::Env &env) : env(env) } -Genode::size_t Component::stack_size() { return 16*1024*sizeof(Genode::addr_t); } +/* each core will drive the stack differently, so be generous */ +Genode::size_t Component::stack_size() { return 64*1024*sizeof(Genode::addr_t); } -void Component::construct(Genode::Env &env) +void Libc::Component::construct(Libc::Env &env) { using namespace Genode; + using namespace Retro_frontend; - Libretro::init_keyboard_map(); + init_keyboard_map(); - try { - static Libretro::Frontend inst(env); - return; - } + try { static Frontend inst(env); } catch (Shared_object::Invalid_rom_module) { error("failed to load core"); } @@ -162,6 +163,8 @@ void Component::construct(Genode::Env &env) catch (Shared_object::Invalid_symbol) { error("failed to load required symbols from core"); } - catch (...) { error("failed to init core"); } - env.parent().exit(-1); + catch (...) { + error("failed to init core"); + env.parent().exit(-1); + } } diff --git a/src/app/retro_frontend/frontend.h b/src/app/retro_frontend/frontend.h index 33fc1a5..78c6034 100644 --- a/src/app/retro_frontend/frontend.h +++ b/src/app/retro_frontend/frontend.h @@ -15,7 +15,6 @@ #define _RETRO_FRONTEND__FRONTEND_H_ /* Genode includes */ -#include #include #include #include @@ -36,20 +35,41 @@ #include /* Local includes */ +#include "audio.h" #include "input.h" -namespace Libretro { -#include +namespace Retro_frontend { + #include struct Frontend; + struct Framebuffer; } /* Global frontend instance */ -static Libretro::Frontend *global_frontend = (Libretro::Frontend*)(Genode::addr_t)666; +static Retro_frontend::Frontend *global_frontend = nullptr; + +struct Retro_frontend::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(); } +}; + + +static Genode::Constructible framebuffer; + /** * Object to encapsulate Genode services */ -struct Libretro::Frontend +struct Retro_frontend::Frontend { typedef void (*Retro_set_environment)(retro_environment_t); typedef void (*Retro_set_video_refresh)(retro_video_refresh_t); @@ -80,11 +100,12 @@ struct Libretro::Frontend 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() }; + Libc::Env &env; Genode::Attached_rom_dataspace config_rom { env, "config" }; + Genode::Heap heap { env.ram(), env.rm() }; + typedef Genode::String<128> Name; Name const name { config_rom.xml().attribute_value("core", Name()) }; @@ -284,42 +305,11 @@ struct Libretro::Frontend Genode::Signal_handler core_runner { env.ep(), *this, &Frontend::run }; - Timer::Connection timer { env }; + 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::Constructible 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::Constructible stereo_out; - - Genode::Reporter variable_reporter { "variables" }; - Genode::Reporter subsystem_reporter { "subsystems" }; - Genode::Reporter controller_reporter { "controllers" }; + Genode::Reporter variable_reporter { env, "variables" }; + Genode::Reporter subsystem_reporter { env, "subsystems" }; + Genode::Reporter controller_reporter { env, "controllers" }; Controller controller { env }; @@ -361,6 +351,10 @@ struct Libretro::Frontend Genode::log("using framebuffer sync as timing source"); framebuffer->session.sync_sigh(core_runner); } + + /* start the audio streams */ + if (stereo_out.constructed()) + stereo_out->start_stream(); } } @@ -384,7 +378,7 @@ struct Libretro::Frontend Genode::Signal_handler resume_handler { env.ep(), *this, &Frontend::handle_input }; - Frontend(Genode::Env &env); + Frontend(Libc::Env &env); ~Frontend() { @@ -394,6 +388,8 @@ struct Libretro::Frontend void set_av_info(retro_system_av_info &av_info) { + using namespace Retro_frontend; + framebuffer.construct( env, ::Framebuffer::Mode(av_info.geometry.base_width, av_info.geometry.base_height, @@ -401,14 +397,16 @@ struct Libretro::Frontend framebuffer->session.mode_sigh(mode_handler); - /* start audio streams */ if (stereo_out.constructed()) { - stereo_out->left.start(); - stereo_out->right.start(); + double ratio = (double)Audio_out::SAMPLE_RATE / av_info.timing.sample_rate; + + audio_shift_factor = SHIFT_ONE / ratio; + audio_input_period = Audio_out::PERIOD * (1.0f / ratio); } quarter_fps = av_info.timing.fps / 4; fb_sync_sample_count = 0; + framebuffer->session.sync_sigh(fb_sync_sampler); } @@ -420,6 +418,9 @@ struct Libretro::Frontend framebuffer->session.sync_sigh(Genode::Signal_context_capability()); timer.sigh(Genode::Signal_context_capability()); + /* stop playpack */ + stereo_out->stop_stream(); + /* set signal handler for unpausing */ controller.input.sigh(resume_handler); diff --git a/src/app/retro_frontend/input.h b/src/app/retro_frontend/input.h index ebe9d2e..aafd598 100644 --- a/src/app/retro_frontend/input.h +++ b/src/app/retro_frontend/input.h @@ -18,7 +18,7 @@ #include #include -namespace Libretro { +namespace Retro_frontend { #include struct Controller; @@ -27,7 +27,7 @@ namespace Libretro { } -struct Libretro::Controller +struct Retro_frontend::Controller { enum { RETRO_JOYPAD_MAX = RETRO_DEVICE_ID_JOYPAD_R3+1, @@ -152,7 +152,7 @@ struct Libretro::Controller }; -namespace Libretro { +namespace Retro_frontend { void init_keyboard_map() {