//! 把 DisplayList 写入 CPU 像素缓冲。裁剪栈限定矩形、文字和 3D 表面的实际写入区域。 //! RenderScope 约束递归深度;累计像素预算限制嵌套 3D 和世界 UI 的中间表面分配。错误向上传递,不把半幅结果当成成功帧呈现。 //! use crate::{ display_list::{Color, DisplayList, DrawCommand}, geometry::{Point, Rect}, text::{self, TextStyle}, }; /// 显示列表的 CPU 执行入口,错误在原生 present 前向上传递。 pub struct SoftwareRenderer; thread_local! { static RENDER_DEPTH:std::cell::Cell=const { std::cell::Cell::new(0) }; static PIXEL_UNITS:std::cell::Cell=const { std::cell::Cell::new(0) }; } /// 递归渲染的 RAII 深度保护,最外层还重置累计像素工作预算。 pub(crate) struct RenderScope; impl RenderScope { /// 进入一次嵌套渲染并检查深度上限;最外层调用重新开始累计像素预算。 pub fn enter() -> Result { RENDER_DEPTH.with(|depth| { let current = depth.get(); if current >= 18 { return Err("render nesting budget exceeded"); } if current == 0 { PIXEL_UNITS.with(|pixels| pixels.set(0)); } depth.set(current + 1); Ok(Self) }) } } impl Drop for RenderScope { /// 在 Rust 所有者离开作用域时执行本类型的配套清理;不能用它代替需要报告错误的显式关闭。 fn drop(&mut self) { RENDER_DEPTH.with(|depth| depth.set(depth.get() - 1)); } } /// 累加本帧中间表面工作量,超过预算立即返回错误。 pub(crate) fn reserve_pixels(units: usize) -> Result<(), &'static str> { PIXEL_UNITS.with(|pixels| { let next = pixels.get().saturating_add(units); if next > 16_777_216 { return Err("nested render pixel budget exceeded"); } pixels.set(next); Ok(()) }) } impl SoftwareRenderer { /// 顺序执行显示列表并维护裁剪栈;3D 和面板可递归进入同一渲染预算域。 pub fn render( list: &DisplayList, target: &mut [u32], width: u32, height: u32, ) -> Result<(), &'static str> { let _scope = RenderScope::enter()?; let mut canvas = Canvas { pixels: target, width, height, clip: Rect::new(0.0, 0.0, width as f32, height as f32), }; let mut clips = Vec::new(); for command in list.commands() { match command { DrawCommand::Scene3D { rect, view } => { let left = rect.x.max(canvas.clip.x).max(0.).floor() as u32; let top = rect.y.max(canvas.clip.y).max(0.).floor() as u32; let right = (rect.x + rect.width) .min(canvas.clip.x + canvas.clip.width) .min(width as f32) .ceil() .max(0.) as u32; let bottom = (rect.y + rect.height) .min(canvas.clip.y + canvas.clip.height) .min(height as f32) .ceil() .max(0.) as u32; if left >= right || top >= bottom || rect.width <= 0. || rect.height <= 0. { continue; } let lod = (2048. / rect.width.max(rect.height)).min(1.); let w = (rect.width * lod).ceil().max(1.) as u32; let h = (rect.height * lod).ceil().max(1.) as u32; let surface = crate::scene3d::surface(view, w, h, rect.width / rect.height)?; for y in top..bottom { for x in left..right { let u = (((x as f32 + 0.5 - rect.x) / rect.width) * w as f32) .clamp(0., (w - 1) as f32) as u32; let v = (((y as f32 + 0.5 - rect.y) / rect.height) * h as f32) .clamp(0., (h - 1) as f32) as u32; canvas.pixels[y as usize * width as usize + x as usize] = surface[v as usize * w as usize + u as usize]; } } } DrawCommand::PushClip(rect) => { clips.push(canvas.clip); let x = canvas.clip.x.max(rect.x); let y = canvas.clip.y.max(rect.y); canvas.clip = Rect::new( x, y, (canvas.clip.x + canvas.clip.width) .min(rect.x + rect.width) .max(x) - x, (canvas.clip.y + canvas.clip.height) .min(rect.y + rect.height) .max(y) - y, ); } DrawCommand::PopClip => { if let Some(clip) = clips.pop() { canvas.clip = clip; } } DrawCommand::Clear(color) => canvas.clear(*color), DrawCommand::FillRoundedRect { rect, radius, color, } => canvas.fill_rounded_rect(*rect, *radius, *color), DrawCommand::StrokeRoundedRect { rect, radius, width, color, } => canvas.stroke_rounded_rect(*rect, *radius, *width, *color), DrawCommand::WrappedText { rect, value, style, color, } => canvas.draw_wrapped_text(*rect, value, *style, *color), DrawCommand::Text { origin, value, style, color, } => canvas.draw_text(*origin, value, *style, *color), } } Ok(()) } } /// 借用目标像素切片并维护当前裁剪,所有像素写入都受该上下文限制。 struct Canvas<'a> { pixels: &'a mut [u32], width: u32, height: u32, clip: Rect, } impl Canvas<'_> { /// 清空本对象维护的当前内容或缓存,后续状态由调用方重新建立。 fn clear(&mut self, color: Color) { self.pixels.fill(pack(color)); } /// 只遍历裁剪后的矩形像素,按圆角覆盖决定填充区域。 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: Color) { let bounds = self.clamped_bounds(rect); let radius = radius.max(0.0).min(rect.width / 2.0).min(rect.height / 2.0); for y in bounds.1..bounds.3 { for x in bounds.0..bounds.2 { if rounded_rect_contains(rect, radius, x as f32 + 0.5, y as f32 + 0.5) { self.blend_pixel(x, y, color); } } } } /// 用外轮廓与内轮廓的差绘制边框,避免把边框当成整个矩形填充。 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, width: f32, color: Color) { let bounds = self.clamped_bounds(rect); let width = width.max(1.0).min(rect.width / 2.0).min(rect.height / 2.0); let outer_radius = radius.max(0.0).min(rect.width / 2.0).min(rect.height / 2.0); let inner = Rect::new( rect.x + width, rect.y + width, (rect.width - width * 2.0).max(0.0), (rect.height - width * 2.0).max(0.0), ); let inner_radius = (outer_radius - width).max(0.0); for y in bounds.1..bounds.3 { for x in bounds.0..bounds.2 { let px = x as f32 + 0.5; let py = y as f32 + 0.5; if rounded_rect_contains(rect, outer_radius, px, py) && !rounded_rect_contains(inner, inner_radius, px, py) { self.blend_pixel(x, y, color); } } } } /// 把文字原点转换为局部裁剪区域,交给有界字形绘制,再混合到目标缓冲。 fn draw_text(&mut self, origin: Point, value: &str, style: TextStyle, color: Color) { let x = origin.x.round() as i32; let y = origin.y.round() as i32; let clip = Rect::new( self.clip.x - x as f32, self.clip.y - y as f32, self.clip.width, self.clip.height, ); text::draw_clipped(value, style, color, None, clip, |dx, dy, pixel| { self.blend_pixel(x + dx, y + dy, pixel) }); } /// 把段落矩形与当前裁剪相交,按受限宽度绘制,再恢复外层裁剪。 fn draw_wrapped_text(&mut self, rect: Rect, value: &str, style: TextStyle, color: Color) { if rect.width <= 0.0 || rect.height <= 0.0 { return; } let old = self.clip; let x = old.x.max(rect.x); let y = old.y.max(rect.y); self.clip = Rect::new( x, y, ((old.x + old.width).min(rect.x + rect.width) - x).max(0.0), ((old.y + old.height).min(rect.y + rect.height) - y).max(0.0), ); let left = rect.x.round() as i32; let top = rect.y.round() as i32; let clip = Rect::new( self.clip.x - left as f32, self.clip.y - top as f32, self.clip.width, self.clip.height, ); text::draw_clipped( value, style, color, Some(rect.width), clip, |dx, dy, pixel| self.blend_pixel(left + dx, top + dy, pixel), ); self.clip = old; } /// 把连续矩形转换成目标缓冲内的整数像素范围,避免在不可见区域进行光栅循环。 fn clamped_bounds(&self, rect: Rect) -> (i32, i32, i32, i32) { ( rect.x.max(self.clip.x).max(0.0).floor() as i32, rect.y.max(self.clip.y).max(0.0).floor() as i32, (rect.x + rect.width) .min(self.clip.x + self.clip.width) .min(self.width as f32) .ceil() as i32, (rect.y + rect.height) .min(self.clip.y + self.clip.height) .min(self.height as f32) .ceil() as i32, ) } /// 在画布与当前裁剪范围内进行透明度混合,防止文字/图形写出目标缓冲。 fn blend_pixel(&mut self, x: i32, y: i32, source: Color) { if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 || source.alpha == 0 || (x as f32) < self.clip.x || (y as f32) < self.clip.y || (x as f32) >= self.clip.x + self.clip.width || (y as f32) >= self.clip.y + self.clip.height { return; } let index = y as usize * self.width as usize + x as usize; if source.alpha == 255 { self.pixels[index] = pack(source); return; } let destination = self.pixels[index]; let dst_red = (destination >> 16) & 0xff; let dst_green = (destination >> 8) & 0xff; let dst_blue = destination & 0xff; let alpha = source.alpha as u32; let inverse = 255 - alpha; let red = (source.red as u32 * alpha + dst_red * inverse) / 255; let green = (source.green as u32 * alpha + dst_green * inverse) / 255; let blue = (source.blue as u32 * alpha + dst_blue * inverse) / 255; self.pixels[index] = (red << 16) | (green << 8) | blue; } } /// 测试一个像素采样点是否位于圆角矩形内,角区域使用圆弧距离。 fn rounded_rect_contains(rect: Rect, radius: f32, x: f32, y: f32) -> bool { if rect.width <= 0.0 || rect.height <= 0.0 { return false; } if radius <= 0.0 { return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height; } // Clamp in local coordinates: adding a large origin to a shrinking tab's // half-width can otherwise round the lower bound above the upper bound. let radius = radius.min(rect.width / 2.0).min(rect.height / 2.0); let local_x = x - rect.x; let local_y = y - rect.y; let dx = local_x - local_x.clamp(radius, rect.width - radius); let dy = local_y - local_y.clamp(radius, rect.height - radius); dx * dx + dy * dy <= radius * radius } /// 将颜色通道打包成目标缓冲的整数像素表示。 fn pack(color: Color) -> u32 { (color.red as u32) << 16 | (color.green as u32) << 8 | color.blue as u32 } #[cfg(test)] mod tests { use super::*; #[test] fn wrapped_text_uses_multiple_lines_respects_nested_clips_and_restores_the_clip() { let mut list = DisplayList::default(); list.push(DrawCommand::Clear(Color::rgb(255, 255, 255))); list.push(DrawCommand::PushClip(Rect::new(12.0, 5.0, 20.0, 40.0))); list.push(DrawCommand::WrappedText { rect: Rect::new(10.0, 5.0, 32.0, 40.0), value: "MMMMMMMMMMMMMMMM".into(), style: TextStyle::ui(1), color: Color::rgb(0, 0, 0), }); list.push(DrawCommand::PopClip); list.push(DrawCommand::WrappedText { rect: Rect::new(60.0, 0.0, 0.0, 40.0), value: "invisible".into(), style: TextStyle::ui(1), color: Color::rgb(0, 0, 0), }); list.push(DrawCommand::FillRoundedRect { rect: Rect::new(70.0, 50.0, 2.0, 2.0), radius: 0.0, color: Color::rgb(0, 0, 255), }); let mut pixels = vec![0; 80 * 60]; SoftwareRenderer::render(&list, &mut pixels, 80, 60).unwrap(); let mut second_line = false; for y in 0..60 { for x in 0..80 { let color = pixels[y * 80 + x]; if color != 0xffffff && color != 0x0000ff { assert!((12..32).contains(&x) && (5..45).contains(&y)); second_line |= y >= 25; } } } assert!(second_line); assert_eq!(pixels[50 * 80 + 70], 0x0000ff); } #[test] fn renderer_changes_pixels_inside_a_rectangle() { let mut list = DisplayList::default(); list.push(DrawCommand::Clear(Color::rgb(0, 0, 0))); list.push(DrawCommand::FillRoundedRect { rect: Rect::new(1.0, 1.0, 2.0, 2.0), radius: 0.0, color: Color::rgb(255, 0, 0), }); let mut pixels = vec![0; 16]; SoftwareRenderer::render(&list, &mut pixels, 4, 4).unwrap(); assert_eq!(pixels[5], 0x00ff0000); assert_eq!(pixels[0], 0); } #[test] fn text_edges_are_antialiased_and_respect_opacity() { let mut list = DisplayList::default(); list.push(DrawCommand::Clear(Color::rgb(0, 0, 0))); list.push(DrawCommand::Text { origin: Point::new(2.0, 2.0), value: "Hello 中文".into(), style: TextStyle::ui(1).scaled(1.25), color: Color::rgba(255, 255, 255, 128), }); let mut pixels = vec![0; 256 * 48]; SoftwareRenderer::render(&list, &mut pixels, 256, 48).unwrap(); assert!(pixels.iter().any(|pixel| *pixel > 0)); assert!(pixels.iter().all(|pixel| (pixel & 255) <= 128)); assert!(pixels.iter().any(|pixel| (1..128).contains(&(pixel & 255)))); let mut invisible = DisplayList::default(); invisible.push(DrawCommand::Text { origin: Point::new(2.0, 2.0), value: "Hello 中文".into(), style: TextStyle::ui(1), color: Color::rgba(255, 255, 255, 0), }); let mut pixels = vec![0; 256 * 48]; SoftwareRenderer::render(&invisible, &mut pixels, 256, 48).unwrap(); assert!(pixels.iter().all(|pixel| *pixel == 0)); } }