WebAssembly
PixelRAM is designed to work both as a native library and in a browser build. The web backend adds a few browser-friendly behaviors while keeping the framebuffer API small.
One-shot programs
Before a web program has called present() explicitly, framebuffer changes are automatically displayed on the next browser animation frame. That makes a tiny example possible without introducing a main loop immediately:
#include "pixelram.h"
int main(void)
{
if (!screen_open(320, 180, pixel_indexed8, "PixelRAM"))
return 1;
set_pixel(screen_width() / 2, screen_height() / 2, 10);
return 0;
}
This automatic presentation is a web convenience. Code that should behave identically in native and browser builds should present explicitly.
Animation and Asyncify
The first explicit call to present() switches the web backend to the normal explicit frame model. An animation therefore uses the familiar loop:
while (!should_close())
{
/* update state */
/* write pixels */
present();
}
present() synchronizes with browser animation frames. PixelRAM targets 60 FPS by default; set_target_fps() can select a lower or different maximum. On high-refresh-rate displays PixelRAM skips browser animation frames as needed to honor the requested cap.
set_target_fps(30);
set_target_fps(0) disables PixelRAM’s additional cap, but browser presentation still follows requestAnimationFrame() and therefore the browser/display refresh cycle.
Both animation-frame waiting and sleep_ms() yield to the browser rather than blocking it. These operations use Emscripten Asyncify, so the final web link step must enable Asyncify.
A typical toolchain therefore defines PLATFORM_WEB and links with an option equivalent to:
-sASYNCIFY
The exact raylib library path and the rest of the Emscripten link flags depend on the surrounding toolchain. PixelRAM deliberately does not bake those project-specific paths into its two distributable files.
Browser display controls
The repository’s shared shell.html adds a small YouTube-style control bar at the bottom of the PixelRAM display. Move the pointer over the display to reveal it; it hides again after mouse inactivity.
The controls provide the console, CRT filter, and fullscreen without requiring students to remember keyboard shortcuts. Tooltips show the available shortcuts where applicable.
Save picture (or F9) downloads the current frame as a PNG at the program’s own resolution: one image pixel per framebuffer pixel, without the scaling of the display and without the CRT filter. The file is named after the window title and the time. Programs that draw without PixelRAM’s framebuffer (raylib) are captured from the canvas, cropped to their logical size.
The console also contains the optional ARGS field used by some larger ports. While that text field has focus, its keystrokes are kept out of the running PixelRAM program.
Resolution: fixed or native
The program decides how the shared shell shows it.
A program with a fixed resolution (every PixelRAM framebuffer, and a raylib program that does not resize its window) is scaled to fill the display. Enlarged, its pixels stay sharp, which is what pixel art and ports such as Prince of Persia need. A program that prefers smooth scaling sets this before its first frame:
EM_ASM(window.PIXELRAM_SCALING = 'smooth';);
A raylib program that can draw at any size runs at native resolution instead:
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
InitWindow(960, 540, "Mein Programm");
The display then fills the free space, and the canvas follows it whenever the browser window, the console or fullscreen changes that space: one canvas pixel per CSS pixel, nothing enlarged. raylib reads the new size from window.innerWidth and window.innerHeight on a resize event, so the shell reports the free area there and announces a change with such an event. The program reads its current size with GetScreenWidth() and GetScreenHeight() every frame; the 960×540 above is only the size it starts with. The shell tells the two kinds apart by whether the canvas follows the announced size.
Mouse capture
set_mouse_relative(true) enables relative mouse input. Browsers require a user gesture for pointer lock, so PixelRAM captures and hides the pointer on the next click on the canvas. Escape releases browser pointer lock; a later click can capture it again while relative mode remains requested.
mouse_delta() reports the movement for the current backend frame. Multiple readers in the same frame see the same delta, which is important for larger engines that have more than one input layer.
Ordinary programs do not capture the pointer unless they explicitly enable relative mode.
Fullscreen
set_fullscreen(true) requests fullscreen on the next canvas click because browsers require fullscreen changes to originate from a user gesture. The shared shell also exposes fullscreen directly through its bottom-right control.
Browsers reserve Escape for leaving fullscreen, so games that also use Escape for menus cannot reliably prevent that browser behavior.
Pixel aspect ratio
set_pixel_aspect() exposes the desired pixel width/height ratio through the CSS custom property --pixel-aspect-ratio and triggers a resize event. The shared web shell uses that information when sizing the canvas. Native builds currently use square display pixels.