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}