# 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` 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.