Skip to main content

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}