Skip to main content

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}