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:
@@ -0,0 +1,105 @@
|
||||
# Render Context & GPU Setup
|
||||
|
||||
Stage 2 reference for `oxide_engine::render` — how the engine acquires the
|
||||
GPU and drives a window surface. For the event loop that calls into this each
|
||||
frame, see [windowing.md](windowing.md).
|
||||
|
||||
## Overview
|
||||
|
||||
Stage 2 rendering is deliberately minimal: acquire the GPU, configure the
|
||||
window surface, and clear it to a configurable color every frame. Meshes,
|
||||
materials, and passes arrive in Stage 4+. The module still establishes the
|
||||
two long-lived types every later stage builds on:
|
||||
|
||||
- **`Gpu`** — instance, adapter, and the device/queue pair. Everything that
|
||||
touches the GPU goes through these four objects.
|
||||
- **`RenderContext`** — a `Gpu` plus a window's surface and its
|
||||
configuration; owns the per-frame acquire → clear → present cycle.
|
||||
|
||||
Both are created for you by [`run()`](windowing.md); applications normally
|
||||
reach them through `AppCtx::render()`.
|
||||
|
||||
## `Gpu`
|
||||
|
||||
```rust
|
||||
use oxide_engine::prelude::*;
|
||||
|
||||
let gpu = Gpu::headless()?; // offscreen / tests
|
||||
let device: &wgpu::Device = gpu.device();
|
||||
let queue: &wgpu::Queue = gpu.queue();
|
||||
# Ok::<(), oxide_engine::render::RenderError>(())
|
||||
```
|
||||
|
||||
Acquisition asks for a high-performance adapter (compatible with the window
|
||||
surface in the windowed path) and a default-limits device. The chosen adapter
|
||||
and backend are logged at `info` level on startup.
|
||||
|
||||
`Gpu::headless()` skips the surface entirely — used by offscreen rendering
|
||||
and the automated Stage 2 integration test. Backend selection and debug flags
|
||||
remain overridable through wgpu's standard `WGPU_*` environment variables
|
||||
(e.g. `WGPU_BACKEND=vulkan`).
|
||||
|
||||
## `RenderContext`
|
||||
|
||||
Owns the surface lifecycle:
|
||||
|
||||
- **Creation** — builds the wgpu instance (the window doubles as the display
|
||||
handle), creates the surface, acquires the `Gpu`, and configures the
|
||||
surface with `get_default_config` (the platform's preferred format and
|
||||
present mode).
|
||||
- **`resize(width, height)`** — reconfigures the surface. Zero dimensions
|
||||
(minimized windows) are clamped to 1 so the surface stays valid. Called
|
||||
automatically by the event loop on `Resized`.
|
||||
- **`set_clear_color(color)` / `clear_color()`** — the color the next frame
|
||||
is cleared to. The engine's `Color` is linear f32 RGBA, matching what the
|
||||
surface expects (conversion to `wgpu::Color` is `render::to_wgpu_color`).
|
||||
- **`render_frame()`** — one frame: acquire the next surface texture, record
|
||||
a clear pass, submit, present.
|
||||
- **`size()`, `gpu()`** — current surface size (physical pixels) and the
|
||||
underlying `Gpu`.
|
||||
|
||||
### Frame acquisition and transient failures
|
||||
|
||||
`get_current_texture` can fail for reasons that are *normal* during resizes
|
||||
and window-manager activity. `render_frame()` maps them as follows:
|
||||
|
||||
| Surface state | Behavior |
|
||||
|---------------|----------|
|
||||
| `Success` / `Suboptimal` | Clear and present (a suboptimal frame is still presentable; the next resize reconfigures anyway) |
|
||||
| `Lost` / `Outdated` | Reconfigure the surface, skip the frame |
|
||||
| `Timeout` / `Occluded` | Skip the frame |
|
||||
| `Validation` | Returned as `RenderError::SurfaceValidation` — a real bug, not transient |
|
||||
|
||||
Skipped frames are invisible in practice: the next `RedrawRequested` arrives
|
||||
within milliseconds.
|
||||
|
||||
## `clear_view`
|
||||
|
||||
The single render operation Stage 2 owns:
|
||||
|
||||
```rust
|
||||
oxide_engine::render::clear_view(device, queue, &texture_view, Color::RED);
|
||||
```
|
||||
|
||||
Records and submits a render pass whose only work is a load-op clear. Both
|
||||
the windowed path (`render_frame`) and offscreen targets go through it, which
|
||||
is what makes the GPU path automatically testable: the integration test
|
||||
`stage2::headless_clear_fills_texture_with_clear_color` clears an offscreen
|
||||
texture headless, reads the pixels back, and asserts the exact clear color —
|
||||
no window or human needed. (It self-skips on machines with no GPU adapter.)
|
||||
|
||||
## Errors
|
||||
|
||||
`RenderError` (a `thiserror` enum) distinguishes the failure modes callers
|
||||
might handle: `NoAdapter`, `Device`, `CreateSurface`, `UnsupportedSurface`,
|
||||
and `SurfaceValidation`. Binaries typically just propagate it via `anyhow`
|
||||
out of `run()`.
|
||||
|
||||
## Design notes
|
||||
|
||||
- The window is held as `Arc<winit::window::Window>` so the surface, which
|
||||
borrows the window, can be `'static` — winit hands windows out from inside
|
||||
its event loop, and wgpu surfaces must outlive every frame.
|
||||
- `Gpu` and `RenderContext` are separate types on purpose: later stages (and
|
||||
tests today) need the device/queue without any window, and composability is
|
||||
a core project principle.
|
||||
Reference in New Issue
Block a user