Skip to content

Commit 227b6db

Browse files
AndrewAltimitAI Agent BotclaudeAI Review AgentAI Pipeline Agent
authored
feat: migrate SDL2 → SDL3 (3.4.2) (#53)
* feat: migrate SDL2 → SDL3 (3.4.2) with build-from-source Replace sdl2 v0.37 with sdl3 v0.17.3 (sdl3-sys v0.6.1+SDL-3.4.2). SDL3 is now compiled from bundled C source via cmake, eliminating the need for system-installed SDL dev packages. Key API changes: - Canvas creation: remove builder pattern, use raw FFI for VSync - All drawing calls use FRect/FPoint (f32) via frect()/fpoint() helpers - PixelFormatEnum → PixelFormat (struct with associated constants) - read_pixels returns Surface, extract bytes with pitch-aware row copy - ClippingRect enum replaces Option<Rect> for clip_rect() - Audio: AudioQueue<i16> → AudioStreamOwner with put_data_i16() - Mouse/wheel coordinates: i32 → f32 (truncated back to i32) - surface.without_lock() is now unsafe Docker CI image updated with cmake, make, and X11/Wayland/audio dev headers. All documentation updated to reflect SDL3 migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add altimit skin to live demo skin selector Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address AI review feedback (iteration 1) Automated fix by Claude in response to Gemini/Codex review. Iteration: 1/5 Co-Authored-By: AI Review Agent <noreply@anthropic.com> * fix: resolve CI pipeline failures Automated fix by Claude in response to pipeline failures. Failures addressed: - format - lint - test-suite Actions taken: - Ran autoformat (ruff format, cargo fmt) - Fixed remaining lint issues Iteration: 1/5 Co-Authored-By: AI Pipeline Agent <noreply@anthropic.com> --------- Co-authored-by: AI Agent Bot <ai-agent@localhost> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: AI Review Agent <ai-review-agent@localhost> Co-authored-by: AI Pipeline Agent <ai-pipeline-agent@localhost>
1 parent 2e4dba4 commit 227b6db

21 files changed

+494
-394
lines changed

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ All code changes are authored by AI agents under human direction. No external co
1212

1313
## Build and Test Commands
1414

15-
All CI commands run inside Docker containers. Local development works with cargo directly if SDL2 dev libs are installed.
15+
All CI commands run inside Docker containers. Local development works with cargo directly (SDL3 is compiled from source; requires cmake, g++, and X11/audio dev headers).
1616

1717
```bash
1818
# Build
@@ -80,7 +80,7 @@ oasis-types (foundation: Color, Button, InputEvent, backend traits, error ty
8080
├── oasis-js (JavaScript engine: QuickJS-NG runtime, console API)
8181
├── oasis-video (MP4/H.264+AAC decode; features: h264, no-std-demux, video-decode)
8282
└── oasis-core (coordination: 16 apps, dashboard, agent, plugin, script)
83-
├── oasis-backend-sdl (SDL2 desktop/Pi rendering + input + audio)
83+
├── oasis-backend-sdl (SDL3 desktop/Pi rendering + input + audio)
8484
│ └── oasis-app (binary entry points: oasis-app, oasis-screenshot; oasis-video[video-decode])
8585
├── oasis-backend-wasm (Canvas 2D + DOM input + Web Audio, iframe overlay)
8686
├── oasis-backend-ue5 (software RGBA framebuffer for Unreal Engine 5)
@@ -208,7 +208,7 @@ To re-enable at your own risk: set `CODEX_ENABLED=true` in your environment.
208208
## Docker Services
209209

210210
`docker-compose.yml` profiles:
211-
- **`ci`** -- rust-ci container (rust:1.93-slim + SDL2 dev libs + nightly + cargo-deny)
211+
- **`ci`** -- rust-ci container (rust:1.93-slim + cmake + X11/audio dev libs + nightly + cargo-deny)
212212
- **`psp`** -- PPSSPP emulator (multi-stage build, NVIDIA GPU passthrough, X11 forwarding)
213213
- **`services`** -- All MCP server containers
214214
- **`memory`** -- AgentCore memory service (also included in `services`)

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Default virtual resolution is 480x272 (PSP native). Skins may override this (e.g
1010

1111
## Build Commands
1212

13-
All CI commands run inside Docker containers. For local development you can run cargo directly if SDL2 dev libs are installed, or use the Docker wrapper.
13+
All CI commands run inside Docker containers. For local development you can run cargo directly (SDL3 is compiled from source automatically via the `build-from-source` feature), or use the Docker wrapper.
1414

1515
```bash
1616
# Build (desktop)
@@ -90,7 +90,7 @@ oasis-types (foundation: Color, Button, InputEvent, backend traits, error ty
9090
├── oasis-video (MP4/H.264+AAC decode; features: h264, no-std-demux, video-decode)
9191
├── oasis-vector (vector graphics: scene graph, path ops, icons, frame-driven animations)
9292
└── oasis-core (coordination: 16 apps, dashboard, agent, plugin, script)
93-
├── oasis-backend-sdl (SDL2 desktop/Pi rendering + input + audio)
93+
├── oasis-backend-sdl (SDL3 desktop/Pi rendering + input + audio)
9494
│ └── oasis-app (binary entry points: oasis-app, oasis-screenshot; oasis-video[video-decode])
9595
├── oasis-backend-wasm (Canvas 2D + DOM input + Web Audio, iframe overlay)
9696
├── oasis-backend-ue5 (software RGBA framebuffer for Unreal Engine 5)
@@ -178,7 +178,7 @@ Exports C-ABI functions: `oasis_create`, `oasis_destroy`, `oasis_tick`, `oasis_s
178178
## Docker Services
179179

180180
`docker-compose.yml` profiles:
181-
- `ci` -- rust-ci container (rust:1.93-slim + SDL2 dev libs + nightly + cargo-deny)
181+
- `ci` -- rust-ci container (rust:1.93-slim + cmake + X11/audio dev libs + nightly + cargo-deny)
182182
- `psp` -- PPSSPP emulator (multi-stage build, NVIDIA GPU passthrough)
183183
- `services` -- MCP server containers (code-quality, content-creation, gemini, etc.)
184184

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Feature requests, guidance, consulting, and support are not provided. This polic
1313
## What You Can Do
1414

1515
- **Fork it.** Clone the repo and adapt it however you want. This is the recommended path if you need features that don't exist here.
16-
- **Study it.** The codebase demonstrates an embeddable OS framework in edition 2024 Rust with pluggable rendering backends (SDL2, WASM, UE5, PSP hardware), a window manager, HTML/CSS/Gemini browser engine, 13-skin theming system, virtual filesystem, and command interpreter.
16+
- **Study it.** The codebase demonstrates an embeddable OS framework in edition 2024 Rust with pluggable rendering backends (SDL3, WASM, UE5, PSP hardware), a window manager, HTML/CSS/Gemini browser engine, 13-skin theming system, virtual filesystem, and command interpreter.
1717
- **Use it.** You are free to use any component under the terms of the [MIT License](LICENSE).
1818

1919
You do so entirely at your own risk and without any expectation of support, maintenance, or acknowledgment from the maintainer.

Cargo.lock

Lines changed: 72 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ thiserror = "2.0"
6060
anyhow = "1.0"
6161

6262
# Graphics (desktop / Pi)
63-
sdl2 = "0.37"
63+
sdl3 = { version = "0.17", features = ["build-from-source"] }
6464

6565
# Image encoding/decoding
6666
png = "0.17"

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ OASIS_OS originated as a Rust port of a PSP homebrew shell OS written in C circa
3636

3737
| Target | Backend | Renderer | Input | Status |
3838
|--------|---------|----------|-------|--------|
39-
| Desktop / Raspberry Pi | `oasis-backend-sdl` | SDL2 window | Keyboard, mouse, gamepad | Implemented |
39+
| Desktop / Raspberry Pi | `oasis-backend-sdl` | SDL3 window | Keyboard, mouse, gamepad | Implemented |
4040
| WebAssembly (Browser) | `oasis-backend-wasm` | Canvas 2D API | DOM events (keyboard, mouse, touch) | Implemented |
4141
| PSP / PPSSPP | `oasis-backend-psp` | sceGu hardware sprites | PSP controller | Implemented |
4242
| Unreal Engine 5 | `oasis-backend-ue5` | Software RGBA framebuffer | FFI input queue | Implemented |
@@ -115,7 +115,7 @@ oasis-os/
115115
| +-- oasis-core/ # Coordination layer: 16 apps, dashboard, agent, plugin, script, etc.
116116
| +-- oasis-video/ # Software MP4/H.264+AAC decode pipeline (symphonia + openh264)
117117
| +-- oasis-vector/ # Vector graphics: scene graph, path ops, icons, animations
118-
| +-- oasis-backend-sdl/ # SDL2 rendering and input (desktop + Pi)
118+
| +-- oasis-backend-sdl/ # SDL3 rendering and input (desktop + Pi)
119119
| +-- oasis-backend-wasm/ # Canvas 2D rendering, DOM input, Web Audio (browser)
120120
| +-- oasis-backend-ue5/ # UE5 software framebuffer + FFI input queue
121121
| +-- oasis-backend-psp/ # [excluded from workspace] sceGu hardware rendering, PSP controller, UMD browsing
@@ -157,26 +157,26 @@ oasis-os/
157157
| `oasis-js` | JavaScript engine wrapping QuickJS-NG via rquickjs: `console` API, inline `<script>` execution, DOM manipulation from JS |
158158
| `oasis-core` | Coordination layer: app runner (16 apps), dashboard, agent/MCP, plugin, scripting, status/bottom bars |
159159
| `oasis-video` | Software MP4/H.264+AAC decode pipeline: streaming `VideoSource` API, symphonia for demux + AAC, optional openh264 for H.264. Consumed by `oasis-app` (desktop) and `oasis-ffi` (UE5) via `video-decode` feature |
160-
| `oasis-backend-sdl` | SDL2 rendering and input backend for desktop and Raspberry Pi |
160+
| `oasis-backend-sdl` | SDL3 rendering and input backend for desktop and Raspberry Pi (built from source via cmake) |
161161
| `oasis-backend-wasm` | WebAssembly backend -- Canvas 2D rendering, DOM event input, Web Audio, iframe overlay for real web pages |
162162
| `oasis-backend-ue5` | UE5 render target backend -- software RGBA framebuffer and FFI input queue |
163163
| `oasis-vector` | Resolution-independent vector graphics: scene graph, path operations, Altimit-style icons, frame-driven animations |
164164
| `oasis-backend-psp` | PSP hardware backend -- sceGu sprite rendering, SDI scene-graph integration, TLS 1.3 via embedded-tls, in-memory MP4 streaming with AAC hardware decode, PSP controller input, std via [rust-psp](https://github.com/AndrewAltimit/rust-psp) SDK |
165165
| `oasis-plugin-psp` | PSP overlay plugin PRX -- kernel-mode companion module for in-game overlay UI and background MP3 playback |
166166
| `oasis-ffi` | C-ABI FFI boundary (`cdylib`) for UE5 and external integrations. Optional `video-decode` feature adds `oasis_video_play/stop/is_playing` |
167-
| `oasis-app` | Desktop entry point (SDL2) and screenshot capture tool. `video-decode` feature (default) enables software video decode for TV Guide without ffmpeg |
167+
| `oasis-app` | Desktop entry point (SDL3) and screenshot capture tool. `video-decode` feature (default) enables software video decode for TV Guide without ffmpeg |
168168

169169
The PSP crates are excluded from the workspace (require `mipsel-sony-psp` target) and depend on the standalone [rust-psp SDK](https://github.com/AndrewAltimit/rust-psp) via git dependency. The backend compiles to an EBOOT.PBP (standalone application) with TV Guide, Internet Radio, and all core apps, while the plugin compiles to a kernel-mode PRX (resident overlay module loaded by CFW via `PLUGINS.TXT`). The WASM backend compiles to a `cdylib` via `wasm-pack` and runs in any modern browser with in-canvas video rendering for TV Guide.
170170

171171
## Building
172172

173-
### Desktop (SDL2)
173+
### Desktop (SDL3)
174174

175175
```bash
176176
# Via Docker (container-first)
177177
docker compose --profile ci run --rm rust-ci cargo build --release -p oasis-app
178178

179-
# Or natively (requires libsdl2-dev, libsdl2-mixer-dev)
179+
# Or natively (requires cmake, g++, and X11/audio dev headers)
180180
cargo build --release -p oasis-app
181181
```
182182

crates/oasis-backend-sdl/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ documentation = "https://docs.rs/oasis-backend-sdl"
1212
oasis-core = { workspace = true, features = ["tls-rustls", "javascript"] }
1313
oasis-audio = { workspace = true }
1414
oasis-types = { workspace = true }
15-
sdl2 = { workspace = true }
15+
sdl3 = { workspace = true }
1616
rmp3 = { workspace = true }
1717
log = { workspace = true }
1818

crates/oasis-backend-sdl/src/blitting.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
//! Texture blitting operations for the SDL2 backend.
1+
//! Texture blitting operations for the SDL3 backend.
22
//!
33
//! Contains sub-rect, tinted, and flipped blit methods.
44
5-
use sdl2::rect::Rect;
6-
75
use oasis_core::backend::{BackendErrExt, Color, TextureId, texture_not_found};
86
use oasis_core::error::Result;
97

10-
use super::SdlBackend;
8+
use super::{SdlBackend, frect};
119

1210
impl SdlBackend {
1311
#[allow(clippy::too_many_arguments)]
@@ -28,8 +26,8 @@ impl SdlBackend {
2826
.textures
2927
.get(&tex.0)
3028
.ok_or_else(|| texture_not_found(tex.0))?;
31-
let src_rect = Rect::new(src_x as i32, src_y as i32, src_w, src_h);
32-
let dst_rect = Rect::new(tx, ty, dst_w, dst_h);
29+
let src_rect = frect(src_x as i32, src_y as i32, src_w, src_h);
30+
let dst_rect = frect(tx, ty, dst_w, dst_h);
3331
self.canvas
3432
.copy(texture, src_rect, dst_rect)
3533
.backend_err()?;
@@ -53,7 +51,7 @@ impl SdlBackend {
5351
.ok_or_else(|| texture_not_found(tex.0))?;
5452
texture.set_color_mod(tint.r, tint.g, tint.b);
5553
texture.set_alpha_mod(tint.a);
56-
let dst_rect = Rect::new(tx, ty, w, h);
54+
let dst_rect = frect(tx, ty, w, h);
5755
self.canvas.copy(texture, None, dst_rect).backend_err()?;
5856
// Reset modulation on the same texture.
5957
texture.set_color_mod(255, 255, 255);
@@ -82,8 +80,8 @@ impl SdlBackend {
8280
.ok_or_else(|| texture_not_found(tex.0))?;
8381
texture.set_color_mod(tint.r, tint.g, tint.b);
8482
texture.set_alpha_mod(tint.a);
85-
let src_rect = Rect::new(src_x as i32, src_y as i32, src_w, src_h);
86-
let dst_rect = Rect::new(tx, ty, dst_w, dst_h);
83+
let src_rect = frect(src_x as i32, src_y as i32, src_w, src_h);
84+
let dst_rect = frect(tx, ty, dst_w, dst_h);
8785
self.canvas
8886
.copy(texture, src_rect, dst_rect)
8987
.backend_err()?;
@@ -109,7 +107,7 @@ impl SdlBackend {
109107
.textures
110108
.get(&tex.0)
111109
.ok_or_else(|| texture_not_found(tex.0))?;
112-
let dst_rect = Rect::new(tx, ty, w, h);
110+
let dst_rect = frect(tx, ty, w, h);
113111
self.canvas
114112
.copy_ex(texture, None, dst_rect, 0.0, None, flip_h, flip_v)
115113
.backend_err()?;

crates/oasis-backend-sdl/src/input.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
//! Input event handling for the SDL2 backend.
1+
//! Input event handling for the SDL3 backend.
22
//!
3-
//! Maps SDL2 keyboard, mouse, and window events to OASIS_OS `InputEvent`s.
3+
//! Maps SDL3 keyboard, mouse, and window events to OASIS_OS `InputEvent`s.
44
5-
use sdl2::event::Event;
6-
use sdl2::keyboard::Keycode;
5+
use sdl3::event::Event;
6+
use sdl3::keyboard::Keycode;
77

88
use oasis_core::input::{Button, InputEvent, Trigger};
99

@@ -21,7 +21,7 @@ impl oasis_core::backend::InputBackend for SdlBackend {
2121
}
2222
}
2323

24-
/// Map an SDL2 event to an OASIS_OS input event.
24+
/// Map an SDL3 event to an OASIS_OS input event.
2525
pub(crate) fn map_sdl_event(event: Event) -> Option<InputEvent> {
2626
match event {
2727
Event::Quit { .. } => Some(InputEvent::Quit),
@@ -31,16 +31,27 @@ pub(crate) fn map_sdl_event(event: Event) -> Option<InputEvent> {
3131
Event::KeyUp {
3232
keycode: Some(key), ..
3333
} => map_key_up(key),
34-
Event::MouseMotion { x, y, .. } => Some(InputEvent::CursorMove { x, y }),
35-
Event::MouseButtonDown { x, y, .. } => Some(InputEvent::PointerClick { x, y }),
36-
Event::MouseButtonUp { x, y, .. } => Some(InputEvent::PointerRelease { x, y }),
37-
Event::MouseWheel { y, .. } => Some(InputEvent::MouseWheel { delta: -y }),
34+
// SDL3 mouse coordinates are f32; truncate to i32.
35+
Event::MouseMotion { x, y, .. } => Some(InputEvent::CursorMove {
36+
x: x as i32,
37+
y: y as i32,
38+
}),
39+
Event::MouseButtonDown { x, y, .. } => Some(InputEvent::PointerClick {
40+
x: x as i32,
41+
y: y as i32,
42+
}),
43+
Event::MouseButtonUp { x, y, .. } => Some(InputEvent::PointerRelease {
44+
x: x as i32,
45+
y: y as i32,
46+
}),
47+
// SDL3 mouse wheel y is f32; truncate to i32.
48+
Event::MouseWheel { y, .. } => Some(InputEvent::MouseWheel { delta: -(y as i32) }),
3849
Event::Window {
39-
win_event: sdl2::event::WindowEvent::FocusGained,
50+
win_event: sdl3::event::WindowEvent::FocusGained,
4051
..
4152
} => Some(InputEvent::FocusGained),
4253
Event::Window {
43-
win_event: sdl2::event::WindowEvent::FocusLost,
54+
win_event: sdl3::event::WindowEvent::FocusLost,
4455
..
4556
} => Some(InputEvent::FocusLost),
4657
Event::TextInput { text, .. } => text.chars().next().map(InputEvent::TextInput),

0 commit comments

Comments
 (0)