oxiui_render_soft/lib.rs
1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3//! softbuffer CPU framebuffer backend — headless / ffi-audit / embedded path.
4//!
5//! Use `--no-default-features --features software` for the GPU-free audit build.
6//!
7//! # Headless rendering
8//!
9//! The [`headless`] module provides [`headless::render_headless_once`] and
10//! [`headless::RgbaBuffer`] for CI/ffi-audit usage without any display.
11
12use oxiui_core::{Color, UiError};
13
14/// Headless rendering helpers: [`RgbaBuffer`], [`render_headless_once`], and PNG save.
15pub mod headless;
16pub use headless::{
17 render_headless_once, render_headless_scene, PixelFormat, RgbaBuffer, HEADLESS_BG_COLOR,
18};
19
20/// CPU pixel framebuffer (`0xAARRGGBB`) with blending and RGBA export.
21pub mod framebuffer;
22pub use framebuffer::Framebuffer;
23
24/// Rectangular clip-region stack.
25pub mod clip;
26pub use clip::{ClipRect, ClipStack};
27
28/// Drawing primitives: rectangles, lines, circles, rounded rects, blits.
29pub mod draw;
30pub use draw::{Canvas, SrcImage};
31
32/// Linear and radial gradient fills with sRGB colour interpolation.
33pub mod gradient;
34pub use gradient::{lerp_color, GradientStop, LinearGradient, RadialGradient};
35
36/// Extended blend modes (multiply / screen / overlay / darken / lighten) and
37/// premultiplied-alpha helpers — additive over `Framebuffer::blend`.
38pub mod blend;
39pub use blend::{blend_mode, blend_pixel, composite_into, BlendMode, RgbaUnit};
40
41/// Active-Edge-Table scanline polygon / triangle fill with vertical-
42/// supersample coverage AA. Supports even-odd and non-zero winding.
43pub mod scanline;
44pub use scanline::{fill_polygon, fill_triangle, FillRule};
45
46/// 2D paths with Bezier flattening, fill (via [`scanline`]), and stroke.
47pub mod path;
48pub use path::{Cap, Join, Path, PathBuilder, StrokeStyle};
49
50/// Box shadow via separable 1-D Gaussian blur with a kernel cache.
51pub mod shadow;
52pub use shadow::{box_shadow, gaussian_blur_alpha, GaussianCache};
53
54/// Bayer-matrix ordered dithering for reduced-bit output paths.
55pub mod dither;
56pub use dither::{ordered_dither_rgba, BayerMatrix};
57
58/// 64×64 render-tile iterator (rayon-ready; serial driver only this run).
59pub mod tile;
60#[cfg(feature = "parallel")]
61pub use tile::render_parallel;
62pub use tile::{
63 collect_tiles, render_tiles, tiles_for, DirtyRegion, Tile, TileIter, DEFAULT_TILE_SIZE,
64};
65
66/// CPU framebuffer backend implementing [`oxiui_core::paint::RenderBackend`].
67pub mod backend;
68pub use backend::{blit_glyph_bitmap, SoftBackend};
69
70/// SIMD-accelerated bulk pixel operations (Pure-Rust `wide` crate).
71///
72/// All functions have scalar fallbacks — callers never need to feature-gate
73/// individual call sites. The `simd` feature enables 8-wide SIMD paths.
74pub mod simd_fill;
75pub use simd_fill::{alpha_blend_row, fill_solid, gradient_row_horizontal};
76
77/// FFT-accelerated Gaussian blur for large kernels (OxiFFT, COOLJAPAN ecosystem).
78///
79/// Enable the `fft-blur` feature for the OxiFFT-backed path; without it the
80/// module exposes only `should_use_fft_blur` and the stub `gaussian_blur_alpha_fft`.
81pub mod fft_blur;
82pub use fft_blur::{gaussian_blur_alpha_fft, should_use_fft_blur, FFT_BLUR_MIN_RADIUS};
83
84/// Runtime CPU/GPU backend switching via shared [`oxiui_core::paint::DrawList`].
85///
86/// The `wgpu-compat` feature must be enabled to use the GPU variant.
87pub mod backend_switch;
88pub use backend_switch::{BackendKind, DynBackend};
89
90/// Canvas 2D pixel upload path for wasm32 targets.
91///
92/// Enabled by the `canvas-2d` feature. On native targets the upload
93/// functions are no-ops that always return `Ok(())`.
94pub mod canvas_upload;
95pub use canvas_upload::{framebuffer_to_rgba8, upload_framebuffer, upload_rgba};
96
97/// Re-export [`oxiui_theme::ShadowSpec`] when the `theme` feature is active so
98/// integration tests can address it as `oxiui_render_soft::ShadowSpec`.
99#[cfg(feature = "theme")]
100pub use oxiui_theme::ShadowSpec;
101
102/// Errors specific to the soft-render backend.
103#[derive(Debug)]
104pub enum SoftRenderError {
105 /// An I/O error (e.g. cannot create or write the output file).
106 Io(String),
107 /// A PNG encoding / decoding error.
108 Png(String),
109}
110
111impl std::fmt::Display for SoftRenderError {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 SoftRenderError::Io(s) => write!(f, "soft-render I/O error: {s}"),
115 SoftRenderError::Png(s) => write!(f, "soft-render PNG error: {s}"),
116 }
117 }
118}
119
120impl std::error::Error for SoftRenderError {}
121
122/// CPU-based software renderer using a raw pixel framebuffer.
123///
124/// In the full implementation this would wrap a `softbuffer::Surface`.
125/// For M1 it is a pure-Rust pixel-buffer helper that does not require
126/// a display at build time.
127pub struct SoftRenderer {
128 _marker: std::marker::PhantomData<()>,
129}
130
131impl SoftRenderer {
132 /// Construct a new [`SoftRenderer`].
133 pub fn new() -> Self {
134 Self {
135 _marker: std::marker::PhantomData,
136 }
137 }
138
139 /// Fill a framebuffer with a solid colour.
140 ///
141 /// Returns a `Vec<u32>` of length `width * height` in `0xAARRGGBB` format,
142 /// every pixel set to `color`.
143 ///
144 /// # Errors
145 /// Currently infallible; returns `Err` only if a future implementation
146 /// encounters a resource constraint.
147 pub fn clear_frame(&self, width: u32, height: u32, color: Color) -> Result<Vec<u32>, UiError> {
148 let fb = Framebuffer::with_fill(width, height, color);
149 Ok(fb.pixels().to_vec())
150 }
151
152 /// Render a scene into a fresh [`Framebuffer`] of the given size.
153 ///
154 /// The framebuffer is pre-filled with `background`; `draw_fn` receives a
155 /// clipped [`Canvas`] to paint the scene. Returns the finished framebuffer.
156 pub fn render<F>(&self, width: u32, height: u32, background: Color, draw_fn: F) -> Framebuffer
157 where
158 F: FnOnce(&mut Canvas<'_>),
159 {
160 let mut fb = Framebuffer::with_fill(width, height, background);
161 {
162 let mut canvas = Canvas::new(&mut fb);
163 draw_fn(&mut canvas);
164 }
165 fb
166 }
167}
168
169impl Default for SoftRenderer {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175// ── Quality configuration ────────────────────────────────────────────────────
176
177/// Anti-aliasing mode for the software renderer.
178///
179/// Controls the AA strategy used by polygon / path fill operations.
180/// `Msaa4x` is listed for forward-compatibility but maps to `Supersampling`
181/// in the current scanline implementation.
182#[derive(Clone, Copy, Debug, PartialEq)]
183pub enum AaMode {
184 /// No anti-aliasing — all edges are aliased (fastest).
185 None,
186 /// 4× multi-sample anti-aliasing (currently maps to the supersample path).
187 Msaa4x,
188 /// Vertical-supersample coverage AA via the Active Edge Table (default quality).
189 Supersampling,
190}
191
192/// Shadow render quality.
193#[derive(Clone, Copy, Debug, PartialEq)]
194pub enum ShadowQuality {
195 /// Shadow rendering disabled — `BoxShadow` commands produce a hard-edge fill.
196 Off,
197 /// Low-quality shadow: small Gaussian kernel (faster).
198 Low,
199 /// High-quality shadow: full Gaussian kernel with kernel caching.
200 High,
201}
202
203/// Combined quality preset for [`SoftBackend::with_quality`].
204///
205/// Use the [`low`](SoftRenderQuality::low), [`balanced`](SoftRenderQuality::balanced),
206/// and [`high`](SoftRenderQuality::high) constructors to get sensible presets,
207/// or build a custom config with struct syntax.
208#[derive(Clone, Debug, PartialEq)]
209pub struct SoftRenderQuality {
210 /// Anti-aliasing strategy.
211 pub aa_mode: AaMode,
212 /// Shadow rendering quality.
213 pub shadow_quality: ShadowQuality,
214}
215
216impl SoftRenderQuality {
217 /// Fastest preset: no AA, no shadows.
218 pub fn low() -> Self {
219 Self {
220 aa_mode: AaMode::None,
221 shadow_quality: ShadowQuality::Off,
222 }
223 }
224
225 /// Balanced preset: supersampling AA, low-quality shadows.
226 pub fn balanced() -> Self {
227 Self {
228 aa_mode: AaMode::Supersampling,
229 shadow_quality: ShadowQuality::Low,
230 }
231 }
232
233 /// Highest-quality preset: supersampling AA, high-quality shadows.
234 pub fn high() -> Self {
235 Self {
236 aa_mode: AaMode::Supersampling,
237 shadow_quality: ShadowQuality::High,
238 }
239 }
240}
241
242// ── SoftRenderer additional constructors ────────────────────────────────────
243
244impl SoftRenderer {
245 /// Pre-allocate the framebuffer for the given pixel dimensions.
246 ///
247 /// Equivalent to `SoftRenderer::new()` — the renderer itself is stateless;
248 /// the framebuffer is created on demand inside [`SoftRenderer::render`].
249 /// Provided for API parity with [`SoftBackend::with_quality`].
250 pub fn with_size(_width: u32, _height: u32) -> Self {
251 Self {
252 _marker: std::marker::PhantomData,
253 }
254 }
255}
256
257// ── SoftBackend additional constructors ─────────────────────────────────────
258
259impl SoftBackend {
260 /// Create a backend pre-configured with a [`SoftRenderQuality`] preset.
261 ///
262 /// The `quality` is stored and used to drive AA mode selection during
263 /// subsequent [`execute`](oxiui_core::paint::RenderBackend::execute) calls.
264 ///
265 /// [`AaMode::Supersampling`] / [`AaMode::Msaa4x`] enables the vertical-
266 /// supersample coverage path in the scanline polygon rasterizer.
267 /// [`AaMode::None`] disables AA (faster, aliased edges).
268 ///
269 /// [`ShadowQuality::Off`] skips Gaussian convolution for box-shadow commands.
270 /// [`ShadowQuality::Low`] and [`ShadowQuality::High`] use the existing
271 /// [`GaussianCache`] with a small or full kernel radius respectively.
272 pub fn with_quality(width: u32, height: u32, quality: SoftRenderQuality) -> Self {
273 let mut backend = Self::new(width, height);
274 backend.set_quality(quality);
275 backend
276 }
277}
278
279// ── Tests ────────────────────────────────────────────────────────────────────
280
281#[cfg(test)]
282mod lib_tests {
283 use super::*;
284
285 /// Test 4: SoftRenderQuality::low().aa_mode == AaMode::None
286 #[test]
287 fn quality_low_aa_mode_is_none() {
288 let q = SoftRenderQuality::low();
289 assert_eq!(q.aa_mode, AaMode::None);
290 assert_eq!(q.shadow_quality, ShadowQuality::Off);
291 }
292
293 /// Test 5: SoftRenderQuality::high().aa_mode == AaMode::Supersampling
294 #[test]
295 fn quality_high_aa_mode_is_supersampling() {
296 let q = SoftRenderQuality::high();
297 assert_eq!(q.aa_mode, AaMode::Supersampling);
298 assert_eq!(q.shadow_quality, ShadowQuality::High);
299 }
300
301 /// balanced preset.
302 #[test]
303 fn quality_balanced_preset() {
304 let q = SoftRenderQuality::balanced();
305 assert_eq!(q.aa_mode, AaMode::Supersampling);
306 assert_eq!(q.shadow_quality, ShadowQuality::Low);
307 }
308
309 /// Test 6: SoftRenderer::with_size(100,100) constructs without panic.
310 #[test]
311 fn soft_renderer_with_size_constructs() {
312 let _r = SoftRenderer::with_size(100, 100);
313 }
314
315 /// Test 7: SoftBackend::with_quality(50,50,low()) constructs without panic.
316 #[test]
317 fn soft_backend_with_quality_low_constructs() {
318 let _b = SoftBackend::with_quality(50, 50, SoftRenderQuality::low());
319 }
320
321 /// SoftBackend::with_quality(high) constructs without panic.
322 #[test]
323 fn soft_backend_with_quality_high_constructs() {
324 let _b = SoftBackend::with_quality(50, 50, SoftRenderQuality::high());
325 }
326
327 /// Quality struct supports Clone and Debug.
328 #[test]
329 fn quality_clone_debug() {
330 let q = SoftRenderQuality::balanced();
331 let q2 = q.clone();
332 assert_eq!(q, q2);
333 let _ = format!("{:?}", q);
334 }
335}