oxiui_render_wgpu/lib.rs
1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3//! wgpu GPU render surface — CPU-side preparation engine for OxiUI.
4//!
5//! This crate provides the full CPU-side rendering preparation stack that a
6//! future GPU [`RenderBackend`] implementation will compose:
7//!
8//! - [`atlas`] — Dynamic shelf-based texture atlas with LRU eviction.
9//! - [`batch`] — Draw-call batcher: sorts [`DrawList`] commands by pipeline
10//! state, merges adjacent same-key runs, and culls off-screen commands.
11//! - [`clip`] — Nested clip-rect stack with outward-rounded integer scissor.
12//! - [`quality`] — [`RenderQuality`] presets (low / balanced / high).
13//! - [`resource`] — Generation-checked [`TextureHandle`]/[`ShaderHandle`]
14//! newtypes with a reference-counted [`ResourceRegistry`] and RAII guards.
15//! - [`error`] — [`GpuErrorKind`] → [`UiError`] mapping.
16//! - [`gpu`] — the real headless GPU backend ([`WgpuBackend`]) built on
17//! [`wgpu`]: offscreen device init, the `solid.wgsl` pipeline, and a
18//! [`RenderBackend`] implementation for solid rectangles, SDF circles, and
19//! scissor-based clipping, with CPU pixel readback.
20//!
21//! GPU drivers (Vulkan/Metal/DX12/WebGPU) are OS-provided at runtime;
22//! they are NOT linked at build time. [`gpu::WgpuBackend::headless`] acquires
23//! an adapter at runtime and gracefully reports [`UiError::Unsupported`] when
24//! none is available.
25//!
26//! [`RenderBackend`]: oxiui_core::paint::RenderBackend
27//! [`DrawList`]: oxiui_core::paint::DrawList
28//! [`UiError`]: oxiui_core::UiError
29//! [`UiError::Unsupported`]: oxiui_core::UiError::Unsupported
30
31pub mod atlas;
32pub mod batch;
33pub mod clip;
34pub mod error;
35pub mod gpu;
36pub mod quality;
37pub mod resource;
38
39// ── Convenience re-exports ────────────────────────────────────────────────────
40
41pub use atlas::{AtlasHandle, AtlasRect, TextureAtlas};
42pub use batch::{BatchKey, BlendMode, DrawBatch, PipelineKind, PreparedFrame};
43pub use clip::{ClipRect, ClipStack};
44pub use error::{map_gpu_error, GpuErrorKind};
45pub use gpu::{GpuContext, SolidPipeline, WgpuBackend};
46pub use quality::{RenderQuality, ShadowQuality, TextQuality};
47pub use resource::{
48 ResourceId, ResourceRegistry, ShaderGuard, ShaderHandle, TextureGuard, TextureHandle,
49};
50
51// ── WgpuPrep ─────────────────────────────────────────────────────────────────
52
53/// The CPU-side preparation state for the wgpu render pipeline.
54///
55/// `WgpuPrep` owns a [`TextureAtlas`], a [`ClipStack`], and a
56/// [`RenderQuality`] configuration. Call [`prepare`] once per frame to batch
57/// a [`oxiui_core::paint::DrawList`] into a [`PreparedFrame`] that a GPU consumer can execute.
58///
59/// [`prepare`]: WgpuPrep::prepare
60pub struct WgpuPrep {
61 /// The texture atlas for this render target.
62 pub atlas: atlas::TextureAtlas,
63 /// The active clip-rect stack.
64 pub clip: clip::ClipStack,
65 /// Current render-quality configuration.
66 pub quality: quality::RenderQuality,
67}
68
69impl WgpuPrep {
70 /// Construct a new [`WgpuPrep`] with the given atlas size and quality preset.
71 pub fn new(atlas_size: u32, quality: quality::RenderQuality) -> Self {
72 Self {
73 atlas: atlas::TextureAtlas::new(atlas_size, atlas_size),
74 clip: clip::ClipStack::new(),
75 quality,
76 }
77 }
78
79 /// Batch `list` into a [`PreparedFrame`] using the current clip state.
80 ///
81 /// The active clip rect (top of [`clip`]) is forwarded to the batcher as
82 /// the visibility-culling region.
83 ///
84 /// [`clip`]: WgpuPrep::clip
85 pub fn prepare(&mut self, list: &oxiui_core::paint::DrawList) -> batch::PreparedFrame {
86 let active_clip = self.clip.current().map(|c| [c.x, c.y, c.w, c.h]);
87 batch::batch(list, active_clip)
88 }
89}
90
91// ── Legacy stub (kept for binary compatibility) ───────────────────────────────
92
93/// Placeholder GPU renderer kept for backward compatibility.
94///
95/// This was the original M1 stub. New code should use [`WgpuPrep`] instead.
96/// The struct will be removed once all consumers are migrated.
97pub struct WgpuRenderer {
98 _marker: std::marker::PhantomData<()>,
99}
100
101impl WgpuRenderer {
102 /// Construct a new [`WgpuRenderer`] stub.
103 pub fn new() -> Self {
104 Self {
105 _marker: std::marker::PhantomData,
106 }
107 }
108}
109
110impl Default for WgpuRenderer {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116// ── Integration tests ─────────────────────────────────────────────────────────
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use oxiui_core::{geometry::Rect, paint::DrawList, Color};
122
123 fn red() -> Color {
124 Color(255, 0, 0, 255)
125 }
126
127 #[test]
128 fn prepare_empty_drawlist_is_noop() {
129 let mut prep = WgpuPrep::new(512, RenderQuality::low());
130 let list = DrawList::new();
131 let frame = prep.prepare(&list);
132 assert_eq!(
133 frame.batches.len(),
134 0,
135 "empty list must produce zero batches"
136 );
137 assert_eq!(
138 frame.culled_count, 0,
139 "empty list must have zero culled commands"
140 );
141 }
142
143 #[test]
144 fn prepare_integrates_atlas_and_clip() {
145 let mut prep = WgpuPrep::new(512, RenderQuality::balanced());
146 // Push a clip rect and add a rect that falls inside it.
147 prep.clip.push(ClipRect::new(0.0, 0.0, 100.0, 100.0));
148 let mut list = DrawList::new();
149 list.push_rect(Rect::new(10.0, 10.0, 20.0, 20.0), red());
150 let frame = prep.prepare(&list);
151 // One SolidColor batch, no culling.
152 assert_eq!(frame.batches.len(), 1);
153 assert_eq!(frame.culled_count, 0);
154 }
155}