← Developer's Journal

One Codebase, Five Platforms: Architecture of a Unified Embedded OS

How trait-based backend abstraction lets the same Rust codebase render to SDL3, WebAssembly, PSP hardware, Unreal Engine 5, and a kernel-mode overlay — without a single #[cfg] in core logic.

The Problem: One OS, Radically Different Hosts

OASIS_OS started as a PSP homebrew shell in 2006. Twenty years later, the same codebase runs inside a web browser, on a desktop with SDL3, embedded in Unreal Engine 5 as an in-game terminal, and as a kernel-mode overlay drawn directly into game framebuffers on the original PSP hardware.

These aren't five ports with shared libraries. It's one set of core crates — UI widgets, browser engine, terminal, skin system, window manager — compiled identically for each target. The only thing that changes is the backend: a thin implementation of rendering, input, audio, and networking traits.

The Backend Trait Boundary

The entire platform abstraction lives in a single file: oasis-types/src/backend.rs. Every platform interaction in the system passes through one of five traits:

Backend Trait Hierarchy

SdiCore (13 required methods — every backend must implement)
├── init, shutdown
├── clear, swap_buffers
├── fill_rect, blit, load_texture, destroy_texture
├── draw_text, measure_text
├── set_clip_rect, reset_clip_rect
└── read_pixels

SdiBackend (39 optional accelerated primitives)
├── SdiShapes      rounded_rect, circle, line, triangle, polygon
├── SdiGradients    linear_gradient, radial_gradient
├── SdiAlpha        set_global_alpha, fill_rect_alpha
├── SdiText         draw_text_styled, measure_text_styled
├── SdiTextures     blit_rotated, blit_tinted, blit_region
├── SdiClipTransform push_clip, pop_clip, set_transform
├── SdiVector       begin_path, move_to, line_to, arc, bezier, fill, stroke
└── SdiBatch        begin_batch, end_batch

InputBackend     poll() → Vec<InputEvent>
NetworkBackend   TCP connect, read, write
AudioBackend     play, pause, set_volume, load_audio
The five backend traits that define the entire platform abstraction boundary

SdiCore is the minimum viable backend: 13 methods that let you clear a screen, draw rectangles, blit textures, render text, and read pixels back. Every widget, every app, the browser engine, the terminal — they all target this interface exclusively.

SdiBackend extends SdiCore with 39 optional accelerated primitives split across 8 focused extension traits. Each has a default no-op or software fallback implementation. If a backend supports hardware-accelerated gradients, it implements SdiGradients. If not, the core rendering path handles it. This means a new backend can ship with just 13 methods and progressively add hardware acceleration.

The Five Backends

Deployment Topology

                        ┌──────────────────────────────────┐
                        │          oasis-core              │
                        │  16 apps · browser · terminal    │
                        │  skin engine · window manager     │
                        │  agent · plugins · scripting      │
                        └──────────┬───────────────────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              │                    │                     │
     ┌────────▼────────┐  ┌───────▼────────┐  ┌────────▼────────┐
     │ backend-sdl     │  │ backend-wasm   │  │ backend-ue5     │
     │ SDL3 + OpenGL   │  │ Canvas 2D      │  │ Software RGBA   │
     │ Linux/Mac/Win/Pi│  │ Web Audio       │  │ Unreal Engine 5 │
     └─────────────────┘  │ DOM Input       │  └─────────────────┘
                          └────────────────┘
              ┌──────────────────┐    ┌───────────────────────┐
              │ backend-psp      │    │ plugin-psp            │
              │ sceGu + VRAM     │    │ Kernel PRX overlay    │
              │ Full OS shell    │    │ Hooks sceDisplaySet   │
              │ TLS 1.3 + Stream │    │ Draws into game FB    │
              └──────────────────┘    └───────────────────────┘
Five backends sharing one core — from game engines to bare metal MIPS

SDL3 Desktop (oasis-backend-sdl)

The primary development backend. Uses SDL3's GPU-accelerated 2D renderer for all SdiCore operations, with full SdiBackend extension coverage. Compiles SDL3 from source via the build-from-source feature — no system SDL installation required. Handles keyboard, mouse, and gamepad input; audio playback via SDL_mixer.

WebAssembly (oasis-backend-wasm)

Targets wasm32-unknown-unknown via wasm-bindgen. Renders to an HTML Canvas 2D context, captures DOM keyboard/mouse events as InputEvents, and uses Web Audio for sound. The live demo on GitHub Pages is this backend — the full OS running in your browser.

PSP EBOOT (oasis-backend-psp)

Native PSP rendering via sceGu (the PSP's OpenGL-like 3D API) with textures in VRAM. The most constrained backend: 32MB RAM, 333MHz single-core MIPS, 480×272 resolution. Yet it runs the full shell with 18 skins, the browser engine, 90+ terminal commands, TLS 1.3 networking, and streaming media playback.

Unreal Engine 5 (oasis-backend-ue5 + oasis-ffi)

A software RGBA framebuffer backend exposed through a C-ABI FFI layer. UE5 (or any C-compatible host) calls oasis_tick() each frame, reads back the pixel buffer via oasis_get_buffer(), and uploads it as a dynamic texture. The OS runs as an in-game computer terminal, complete with working browser and shell.

PSP Kernel PRX (oasis-plugin-psp)

The most exotic backend: a <64KB kernel-mode module that runs alongside games. Loaded via PLUGINS.TXT by custom firmware, it hooks sceDisplaySetFrameBuf to draw overlay UI directly into the game's framebuffer on every VSync. Claims a PSP audio channel for background MP3 playback. No dependency on oasis-core — direct framebuffer rendering with its own minimal widget set.

The Crate Dependency Graph

The workspace is organized as 20 crates with a strict dependency hierarchy. Foundation types flow downward; no crate reaches up to its consumers.

oasis-types (Color, Button, InputEvent, backend traits, error types)
├── oasis-vfs        virtual file system: MemoryVfs, RealVfs, GameAssetVfs
├── oasis-platform   platform service traits: Power, Time, USB, Network, OSK
├── oasis-sdi        scene display interface: named objects, z-order
├── oasis-net        TCP networking, PSK auth, remote terminal, FTP
├── oasis-audio      audio manager, playlist, MP3 ID3 parsing
├── oasis-ui         32 widgets: Button, Card, TabBar, ListView, flex layout...
├── oasis-wm         window manager: drag/resize, hit testing, decorations
├── oasis-skin       TOML skin engine, 18 skins, theme derivation
├── oasis-terminal   90+ commands, shell features, variable expansion
├── oasis-browser    HTML/CSS/Gemini: DOM, CSS cascade, layout, JS bindings
├── oasis-js         QuickJS-NG runtime, console API, DOM manipulation
├── oasis-video      MP4/H.264+AAC decode, streaming pipelines
├── oasis-vector     scene graph, path ops, icons, frame animations
└── oasis-core       16 apps, dashboard, agent, plugin, scripting
    ├── oasis-backend-sdloasis-app (binary)
    ├── oasis-backend-wasm
    ├── oasis-backend-ue5oasis-ffi (cdylib)
    ├── oasis-backend-psp (excluded from workspace, nightly)
    └── oasis-plugin-psp  (excluded from workspace, kernel mode)
34 workspace crates — core logic never imports a backend

Why This Works: The Key Design Decisions

No #[cfg] in Core

Platform-specific code is confined entirely to backend crates. The core crates — oasis-ui, oasis-browser, oasis-terminal, etc. — compile identically for every target. They accept a &mut dyn SdiCore and draw to it. This eliminates the conditional compilation spaghetti that plagues most cross-platform Rust projects.

Progressive Capability

The split between SdiCore (required) and the 8 extension traits (optional) means backends can ship minimal implementations and progressively add hardware acceleration. The PSP backend implements SdiShapes using sceGu hardware primitives but falls back to software for gradients. The SDL3 backend implements everything. The WASM backend sits in between. Core code never cares.

Virtual File System

File operations go through oasis-vfs traits, not std::fs. On desktop, RealVfs wraps the real filesystem. In UE5, GameAssetVfs maps to Unreal's asset system with an overlay write layer. On WASM, MemoryVfs provides a fully in-RAM filesystem. On PSP, it wraps sceIo* syscalls. Same API, radically different storage backends.

Bitmap Font Independence

Every backend ships its own glyph table in font.rs. No FreeType, no system font dependencies. oasis-types provides glyph_advance() with variable per-character widths (3–8px). This means text rendering works identically everywhere — including on the PSP where the system font API (psp::font) supplements the bitmap glyphs with TrueType via a VRAM atlas.

What 20 Crates Buys You

The aggressive crate split isn't just organizational hygiene. It has concrete engineering benefits:

Key Insight

The trait boundary isn't just an abstraction — it's a compilation firewall. Backend crates are leaf nodes in the dependency graph. They depend on core; core never depends on them. This means adding a sixth backend (framebuffer, Android, iOS) requires zero changes to any existing crate.

Numbers

MetricCount
Workspace crates34
Backend implementations5
UI widgets32
Terminal commands90+
Skins (external TOML + built-in)18
Apps (12 extracted crates)16
SdiCore required methods13
SdiBackend extended methods39
Backend trait files to implement6 (backend/ directory)