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>
4.4 KiB
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.
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— aGpuplus a window's surface and its configuration; owns the per-frame acquire → clear → present cycle.
Both are created for you by run(); applications normally
reach them through AppCtx::render().
Gpu
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 withget_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 onResized.set_clear_color(color)/clear_color()— the color the next frame is cleared to. The engine'sColoris linear f32 RGBA, matching what the surface expects (conversion towgpu::Colorisrender::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 underlyingGpu.
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:
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. GpuandRenderContextare separate types on purpose: later stages (and tests today) need the device/queue without any window, and composability is a core project principle.