use { crate::{ cmm::{ cmm_description::{ColorDescription, LinearColorDescription}, cmm_render_intent::RenderIntent, }, gfx_api::{ AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SampleRect, }, rect::Rect, scale::Scale, theme::{Color, CornerRadius}, tree::Transform, }, std::rc::Rc, }; pub struct RendererBase<'a> { pub ops: &'a mut Vec, pub scaled: bool, pub scale: Scale, pub scalef: f64, pub transform: Transform, pub fb_width: f32, pub fb_height: f32, } impl RendererBase<'_> { pub fn scale(&self) -> Scale { self.scale } pub fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) { if self.scaled { [x, y] = self.scale.pixel_size([x, y]); } (x, y) } pub fn scale_point_f(&self, mut x: f32, mut y: f32) -> (f32, f32) { if self.scaled { x = (x as f64 * self.scalef) as _; y = (y as f64 * self.scalef) as _; } (x, y) } pub fn scale_rect(&self, mut rect: Rect) -> Rect { if self.scaled { let [x1, y1, x2, y2] = self.scale .pixel_size([rect.x1(), rect.y1(), rect.x2(), rect.y2()]); rect = Rect::new_saturating(x1, y1, x2, y2); } rect } pub fn scale_rect_f(&self, mut rect: (f32, f32, f32, f32)) -> (f32, f32, f32, f32) { if self.scaled { let x1 = (rect.0 as f64 * self.scalef).round() as _; let y1 = (rect.1 as f64 * self.scalef).round() as _; let x2 = (rect.2 as f64 * self.scalef).round() as _; let y2 = (rect.3 as f64 * self.scalef).round() as _; rect = (x1, y1, x2, y2) } rect } pub fn fill_scaled_boxes( &mut self, boxes: &[Rect], color: &Color, alpha: Option, cd: &Rc, render_intent: RenderIntent, ) { self.fill_boxes3(boxes, color, alpha, cd, render_intent, 0, 0, true); } pub fn fill_boxes( &mut self, boxes: &[Rect], color: &Color, cd: &Rc, render_intent: RenderIntent, ) { self.fill_boxes3(boxes, color, None, cd, render_intent, 0, 0, false); } pub fn fill_boxes2( &mut self, boxes: &[Rect], color: &Color, cd: &Rc, render_intent: RenderIntent, dx: i32, dy: i32, ) { self.fill_boxes3(boxes, color, None, cd, render_intent, dx, dy, false); } fn fill_boxes3( &mut self, boxes: &[Rect], color: &Color, alpha: Option, cd: &Rc, render_intent: RenderIntent, dx: i32, dy: i32, scaled: bool, ) { if boxes.is_empty() || *color == Color::TRANSPARENT { return; } for bx in boxes { let bx = bx.move_(dx, dy); let bx = match scaled { false => self.scale_rect(bx), true => bx, }; self.ops.push(GfxApiOpt::FillRect(FillRect { rect: FramebufferRect::new( bx.x1() as f32, bx.y1() as f32, bx.x2() as f32, bx.y2() as f32, self.transform, self.fb_width, self.fb_height, ), color: *color, alpha, render_intent, cd: cd.clone(), })); } } pub fn fill_boxes_f( &mut self, boxes: &[(f32, f32, f32, f32)], color: &Color, cd: &Rc, render_intent: RenderIntent, ) { self.fill_boxes2_f(boxes, color, cd, render_intent, 0.0, 0.0); } pub fn fill_boxes2_f( &mut self, boxes: &[(f32, f32, f32, f32)], color: &Color, cd: &Rc, render_intent: RenderIntent, dx: f32, dy: f32, ) { if boxes.is_empty() || *color == Color::TRANSPARENT { return; } let (dx, dy) = self.scale_point_f(dx, dy); for bx in boxes { let (x1, y1, x2, y2) = self.scale_rect_f(*bx); self.ops.push(GfxApiOpt::FillRect(FillRect { rect: FramebufferRect::new( x1 + dx, y1 + dy, x2 + dx, y2 + dy, self.transform, self.fb_width, self.fb_height, ), color: *color, alpha: None, render_intent, cd: cd.clone(), })); } } pub fn render_texture( &mut self, texture: &Rc, alpha: Option, x: i32, y: i32, tpoints: Option, tsize: Option<(i32, i32)>, tscale: Scale, bounds: Option<&Rect>, buffer_resv: Option>, acquire_sync: AcquireSync, release_sync: ReleaseSync, opaque: bool, cd: &Rc, render_intent: RenderIntent, alpha_mode: AlphaMode, ) { // log::info!("rendering texture {:?}", std::ptr::from_ref(&**texture) as *const u8); // log::info!("{:?}", backtrace::Backtrace::new()); let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); let (twidth, theight) = if let Some(size) = tsize { size } else { let (mut w, mut h) = texcoord.buffer_transform.maybe_swap(texture.size()); if tscale != self.scale { let tscale = tscale.to_f64(); w = (w as f64 * self.scalef / tscale).round() as _; h = (h as f64 * self.scalef / tscale).round() as _; } (w, h) }; let mut target_x = [x, x + twidth]; let mut target_y = [y, y + theight]; if let Some(bounds) = bounds && bound_target(&mut target_x, &mut target_y, &mut texcoord, bounds) { return; } let target = FramebufferRect::new( target_x[0] as f32, target_y[0] as f32, target_x[1] as f32, target_y[1] as f32, self.transform, self.fb_width, self.fb_height, ); self.ops.push(GfxApiOpt::CopyTexture(CopyTexture { tex: texture.clone(), source: texcoord, target, alpha, buffer_resv, acquire_sync, release_sync, opaque, render_intent, cd: cd.clone(), alpha_mode, })); } pub fn fill_rounded_rect( &mut self, rect: Rect, color: &Color, alpha: Option, cd: &Rc, render_intent: RenderIntent, corner_radius: CornerRadius, border_width: f32, ) { self.fill_rounded_rect_z( rect, color, alpha, cd, render_intent, corner_radius, border_width, 0, ) } pub fn fill_rounded_rect_z( &mut self, rect: Rect, color: &Color, alpha: Option, cd: &Rc, render_intent: RenderIntent, corner_radius: CornerRadius, border_width: f32, z_order: u32, ) { if *color == Color::TRANSPARENT { return; } let rect = self.scale_rect(rect); let width = (rect.x2() - rect.x1()) as f32; let height = (rect.y2() - rect.y1()) as f32; let scale = self.scalef as f32; let fitted = corner_radius.fit_to(width, height); let cr: [f32; 4] = fitted.into(); self.ops.push(GfxApiOpt::RoundedFillRect(RoundedFillRect { rect: FramebufferRect::new( rect.x1() as f32, rect.y1() as f32, rect.x2() as f32, rect.y2() as f32, self.transform, self.fb_width, self.fb_height, ), color: *color, alpha, render_intent, cd: cd.clone(), size: [width, height], corner_radius: cr, border_width, scale, z_order, })); } pub fn render_rounded_texture( &mut self, texture: &Rc, alpha: Option, x: i32, y: i32, tpoints: Option, tsize: Option<(i32, i32)>, tscale: Scale, bounds: Option<&Rect>, buffer_resv: Option>, acquire_sync: AcquireSync, release_sync: ReleaseSync, cd: &Rc, render_intent: RenderIntent, alpha_mode: AlphaMode, corner_radius: CornerRadius, ) { let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); let (twidth, theight) = if let Some(size) = tsize { size } else { let (mut w, mut h) = texcoord.buffer_transform.maybe_swap(texture.size()); if tscale != self.scale { let tscale = tscale.to_f64(); w = (w as f64 * self.scalef / tscale).round() as _; h = (h as f64 * self.scalef / tscale).round() as _; } (w, h) }; let mut target_x = [x, x + twidth]; let mut target_y = [y, y + theight]; if let Some(bounds) = bounds && bound_target(&mut target_x, &mut target_y, &mut texcoord, bounds) { return; } let target = FramebufferRect::new( target_x[0] as f32, target_y[0] as f32, target_x[1] as f32, target_y[1] as f32, self.transform, self.fb_width, self.fb_height, ); let width = (target_x[1] - target_x[0]) as f32; let height = (target_y[1] - target_y[0]) as f32; let scale = self.scalef as f32; let fitted = corner_radius.fit_to(width, height); let cr: [f32; 4] = fitted.into(); self.ops .push(GfxApiOpt::RoundedCopyTexture(RoundedCopyTexture { tex: texture.clone(), source: texcoord, target, alpha, buffer_resv, acquire_sync, release_sync, opaque: false, render_intent, cd: cd.clone(), alpha_mode, size: [width, height], corner_radius: cr, scale, })); } pub fn sync(&mut self) { self.ops.push(GfxApiOpt::Sync); } } #[inline] fn bound_target( target_x: &mut [i32; 2], target_y: &mut [i32; 2], texcoord: &mut SampleRect, bounds: &Rect, ) -> bool { let bounds_x = [bounds.x1(), bounds.x2()]; let bounds_y = [bounds.y1(), bounds.y2()]; if target_x[0] >= bounds_x[0] && target_x[1] <= bounds_x[1] && target_y[0] >= bounds_y[0] && target_y[1] <= bounds_y[1] { return false; } #[cold] fn cold() {} cold(); let SampleRect { x1: t_x1, x2: t_x2, y1: t_y1, y2: t_y2, .. } = texcoord; macro_rules! clamp { ($desired:ident, $bounds:ident, $test_idx:expr, $test_cmp:ident, $test_cmp_eq:ident, $modify:ident, $keep:ident) => {{ let desired_test = $desired[$test_idx]; let desired_other = $desired[1 - $test_idx]; let bound = $bounds[$test_idx]; if desired_test.$test_cmp(&bound) { cold(); if desired_other.$test_cmp_eq(&bound) { return true; } let max = (desired_other - bound) as f32; let desired = ($desired[1] - $desired[0]) as f32; let factor = max.abs() / desired; *$modify = *$keep + (*$modify - *$keep) * factor; $desired[$test_idx] = bound; } }}; } clamp!(target_x, bounds_x, 0, lt, le, t_x1, t_x2); clamp!(target_x, bounds_x, 1, gt, ge, t_x2, t_x1); clamp!(target_y, bounds_y, 0, lt, le, t_y1, t_y2); clamp!(target_y, bounds_y, 1, gt, ge, t_y2, t_y1); false }