Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runtime Video and Audio

Runtime is the per-frame handle passed to Core::run. It provides typed helpers for polling input, submitting software frames, submitting hardware frames, sending audio, showing frontend messages, and querying services.

It is also passed to a few lifecycle methods, such as load_game and hw_context_reset, when those methods need frontend access. Do not store Runtime in your core struct. Store your own state, and use the Runtime borrow only during the current callback.

What Runtime Offers

Runtime groups the frontend operations that a running core commonly needs:

JobExamples
Inputpoll_input, joypad_pressed, joypad_buttons, analog_axis, mouse_axis, pointer_pressed
Videovideo_refresh_frame_with_audio, video_refresh_hw_with_audio, video_refresh_dupe_with_audio
Audioaudio_sample, audio_sample_batch, combined video/audio helpers
Hardware renderingcurrent_framebuffer, hw_proc_address through Gl::init
Messages and logsset_message, logger
Environmentenvironment() for runtime-valid environment commands
Frontend servicesrumble, LED, sensors, camera, microphone, MIDI, VFS, performance, netplay helpers

Runtime is not the core state, a scheduler, or a long-lived service object. The frontend creates the callback context, the library lends it to your core, and the borrow ends when the callback returns. Keep emulator state, frame buffers, audio queues, GL object handles, and user settings on your core struct. Use Runtime to communicate with the frontend during the current callback.

The most common frame shape is:

fn run(&mut self, runtime: &mut Runtime<'_>) {
    runtime.poll_input();
    self.update_from_input(runtime);
    self.advance_one_frame();
    self.submit_frame(runtime);
}

Input From Runtime

Calling poll_input tells the frontend to refresh its input state for this frame. After that, typed input helpers read the updated state:

runtime.poll_input();

if runtime.joypad_pressed(0, JoypadButton::A) {
    self.jump();
}

let x = runtime.analog_axis(0, AnalogStick::Left, AnalogAxis::X);

Ports are player slots. Port 0 is player 1, port 1 is player 2, and so on. Joypad helpers read the RetroPad abstraction. Analog, mouse, pointer, and lightgun helpers expose libretro-space raw values so the core can choose its own normalization and deadzone policy.

For more detail, see Input and Input and Events.

Video From Runtime

Software cores usually submit one frame and one audio batch together:

runtime.poll_input();
let pitch = width as usize * core::mem::size_of::<u16>();
let _ = runtime.video_refresh_frame_with_audio(
    &framebuffer,
    width,
    height,
    pitch,
    &audio_frames,
);

The software frame slice, dimensions, and pitch must describe the same image. Keep the framebuffer in your core state so run can update it without allocating every frame.

Hardware Rendering From Runtime

Hardware cores render into the frontend-provided framebuffer and then submit a hardware frame:

let Some(framebuffer) = runtime.current_framebuffer() else {
    let _ = runtime.video_refresh_dupe_with_audio(width, height, &audio_frames);
    return;
};

gl.bind_framebuffer(GlFramebufferTarget::Framebuffer, GlFramebuffer::from_raw(framebuffer))?;
runtime.video_refresh_hw_with_audio(width, height, 0, &audio_frames);

current_framebuffer is valid only while the frontend has an active hardware context for the current frame. OpenGL function loading is also runtime-backed: initialize the typed Gl facade from the runtime context reset path, then keep the resulting facade on your core while the context is alive.

For more detail, see OpenGL and Hardware Rendering and OpenGL.

Audio From Runtime

Prefer the combined video/audio helpers where possible. They make frame pacing visible in the call site and reduce the chance that a frame returns without submitting audio.

Helper choices:

SituationHelper
Software pixels plus audiovideo_refresh_frame_with_audio
Hardware-rendered frame plus audiovideo_refresh_hw_with_audio
Duplicate previous frame plus audiovideo_refresh_dupe_with_audio
Separate accountingvideo_refresh_* plus audio_sample_batch

The *_with_audio helpers return the number of stereo audio frames accepted by the frontend. They are convenient for the common path; split video and audio calls when the core needs to handle software-frame validation and audio acceptance independently.

Audio batches are slices of [i16; 2], where each element is one signed 16-bit stereo frame. Keep generated samples, silence buffers, decoded queues, and resampling state on your core. Use Runtime only to submit the samples.

For more detail, see Audio.

Environment From Runtime

Some environment commands are needed after setup. Use runtime.environment() to get a typed Environment view:

let mut env = runtime.environment();
let _ = env.set_message("Paused", 120);

For simple frontend messages, Runtime also exposes convenience helpers:

let _ = runtime.set_message("Loading failed", 180);
runtime.logger().warn("using fallback renderer");

Frontend Services

Some frontend-owned services are discovered or driven through runtime-valid environment calls: rumble, LED, sensors, camera, microphone, MIDI, VFS, performance counters, and netplay helpers. Treat them as optional frontend capabilities. Query or negotiate them through the typed API, keep any core-side state in your core struct, and continue to produce diagnosable frames when a service is unavailable.

For more detail, see Frontend Services.

After hardware rendering has been accepted, submit hardware or duplicate frames from fallback paths. Do not submit software pixels from an active hardware path.

Tutorials: