Skip to main content

oxiui_render_wgpu/
lib.rs

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