Import Oxide engine (Stages 0–10) under MIT license

Full project snapshot migrated to new Gitea remote without history:
engine, editor, physics, script, examples, tests, docs, and assets.
Relicensed from GPLv3 to MIT and updated repo URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Homer Simpson
2026-07-05 20:41:02 +02:00
parent 2afb56b329
commit f56a1eea3b
128 changed files with 40493 additions and 2 deletions
+233
View File
@@ -0,0 +1,233 @@
//! [`RenderPipeline`]: a data-driven, ordered list of composable render passes.
//!
//! Stage 4's renderer drew everything in one hardcoded pass. Stage 5 generalizes
//! that into a list of named [`RenderPass`]es that share one frame's targets and
//! run in order. A project enables only the passes it needs — this is the
//! mechanism behind *scalable fidelity*: a flat unlit/low-poly look (or a
//! stylized post effect) versus a full realistic stack, paying only for the
//! passes turned on.
//!
//! The Stage-4 forward pass is retrofitted onto this as [`ForwardPass`], so the
//! default pipeline ([`RenderPipeline::forward`]) is just `[Clear, Forward]` and
//! produces pixel-identical output. Later stages add passes (shadows,
//! post-process, overlay UI) **without touching the renderer core** — they
//! register a pass.
use crate::math::{Color, Rect, Transform, Vec2};
use super::{clear_view, Camera, ForwardRenderer, Lighting, RenderObject};
/// Everything one frame's passes operate on: the shared color target and the
/// scene view to draw.
///
/// Passes share the same `color` target (and, as the pipeline grows, depth and
/// intermediate textures), which is what makes them *composable*: a clear pass
/// fills the target, the forward pass draws into it, a future post pass reads and
/// rewrites it.
pub struct FrameContext<'a> {
/// The GPU device.
pub device: &'a wgpu::Device,
/// The GPU queue.
pub queue: &'a wgpu::Queue,
/// The color target every pass renders into.
pub color: &'a wgpu::TextureView,
/// Target size in physical pixels (the whole color target the pipeline
/// is writing into).
pub size: (u32, u32),
/// The sub-rectangle of the target that drawing is restricted to, in
/// physical pixels (`min` = upper-left, `max` = lower-right). Passes
/// configure the wgpu viewport from this and the camera uses its
/// aspect ratio for the projection.
///
/// `None` means "use the full target" — the default for headless tests
/// and for hosts that render to a whole window. The editor sets this to
/// the Viewport tab's rect from the docking shell so picking and
/// projection align with what the user sees inside the tab rather than
/// stretching across the whole window.
pub viewport_rect: Option<Rect>,
/// The background clear color (used by [`ClearPass`]).
pub clear_color: Color,
/// The camera to render from.
pub camera: &'a Camera,
/// The camera's world placement.
pub view_transform: &'a Transform,
/// Scene lighting.
pub lighting: &'a Lighting,
/// The drawables, already culled by the host (e.g. by camera
/// [`visibility`](Camera::visibility)).
pub objects: &'a [RenderObject<'a>],
}
impl FrameContext<'_> {
/// The viewport rect [`viewport_rect`](Self::viewport_rect) resolves to —
/// the explicit sub-rect when set, otherwise the full target.
pub fn resolved_viewport(&self) -> Rect {
self.viewport_rect.unwrap_or_else(|| {
Rect::from_min_size(
Vec2::ZERO,
Vec2::new(self.size.0.max(1) as f32, self.size.1.max(1) as f32),
)
})
}
}
/// One stage of the frame. Implement this to add a custom pass; register it on a
/// [`RenderPipeline`]. Passes are owned by the pipeline and run in order.
pub trait RenderPass {
/// Records this pass's GPU work for the frame.
fn run(&mut self, frame: &mut FrameContext<'_>);
}
struct PassEntry {
name: String,
enabled: bool,
pass: Box<dyn RenderPass>,
}
/// An ordered, named list of render passes.
///
/// Add passes with [`add_pass`](Self::add_pass), toggle them with
/// [`set_enabled`](Self::set_enabled), or drop them with [`remove`](Self::remove)
/// — all without touching any pass's implementation. [`render`](Self::render)
/// runs every enabled pass in order against one [`FrameContext`].
#[derive(Default)]
pub struct RenderPipeline {
passes: Vec<PassEntry>,
}
impl RenderPipeline {
/// An empty pipeline (no passes).
pub fn new() -> Self {
Self::default()
}
/// The default forward pipeline: a [`ClearPass`] followed by a
/// [`ForwardPass`]. Pixel-identical to the Stage-4 renderer's output.
pub fn forward(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
let mut pipeline = Self::new();
pipeline.add_pass("clear", ClearPass);
pipeline.add_pass("forward", ForwardPass::new(device, color_format));
pipeline
}
/// Appends a named pass (enabled). Replaces any existing pass with the same
/// name, keeping its position.
pub fn add_pass(&mut self, name: impl Into<String>, pass: impl RenderPass + 'static) {
let name = name.into();
let entry = PassEntry {
name: name.clone(),
enabled: true,
pass: Box::new(pass),
};
match self.passes.iter_mut().find(|e| e.name == name) {
Some(existing) => *existing = entry,
None => self.passes.push(entry),
}
}
/// Inserts a pass before the pass named `before` (or at the end if not
/// found). Useful for slotting a post effect into a fixed position.
pub fn insert_before(
&mut self,
before: &str,
name: impl Into<String>,
pass: impl RenderPass + 'static,
) {
let entry = PassEntry {
name: name.into(),
enabled: true,
pass: Box::new(pass),
};
match self.passes.iter().position(|e| e.name == before) {
Some(index) => self.passes.insert(index, entry),
None => self.passes.push(entry),
}
}
/// Enables or disables the named pass. Returns whether it exists.
pub fn set_enabled(&mut self, name: &str, enabled: bool) -> bool {
match self.passes.iter_mut().find(|e| e.name == name) {
Some(entry) => {
entry.enabled = enabled;
true
}
None => false,
}
}
/// Removes the named pass. Returns whether it existed.
pub fn remove(&mut self, name: &str) -> bool {
let before = self.passes.len();
self.passes.retain(|e| e.name != name);
self.passes.len() != before
}
/// Whether a pass with this name is registered.
pub fn has_pass(&self, name: &str) -> bool {
self.passes.iter().any(|e| e.name == name)
}
/// The pass names in execution order.
pub fn pass_names(&self) -> impl Iterator<Item = &str> {
self.passes.iter().map(|e| e.name.as_str())
}
/// Runs every enabled pass in order against `frame`.
pub fn render(&mut self, frame: &mut FrameContext<'_>) {
for entry in &mut self.passes {
if entry.enabled {
entry.pass.run(frame);
}
}
}
}
/// A pass that clears the color target to [`FrameContext::clear_color`].
///
/// Conventionally the first pass, so later passes load over the cleared
/// background (matching the Stage-4 clear-then-draw flow).
pub struct ClearPass;
impl RenderPass for ClearPass {
fn run(&mut self, frame: &mut FrameContext<'_>) {
clear_view(frame.device, frame.queue, frame.color, frame.clear_color);
}
}
/// A pass that draws the frame's objects with the lit forward renderer.
///
/// Wraps the Stage-4 [`ForwardRenderer`]; the color target is *loaded* (so a
/// preceding [`ClearPass`] shows through), depth is managed internally.
pub struct ForwardPass {
renderer: ForwardRenderer,
}
impl ForwardPass {
/// Builds a forward pass for the given color target format.
pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
Self {
renderer: ForwardRenderer::new(device, color_format),
}
}
/// The wrapped renderer's color format.
pub fn color_format(&self) -> wgpu::TextureFormat {
self.renderer.color_format()
}
}
impl RenderPass for ForwardPass {
fn run(&mut self, frame: &mut FrameContext<'_>) {
self.renderer.render(
frame.device,
frame.queue,
frame.color,
frame.size,
frame.resolved_viewport(),
frame.camera,
frame.view_transform,
frame.lighting,
frame.objects,
);
}
}