//! Compare damaged swapchain frames with an independently repainted scene. use super::*; use smithay::backend::renderer::damage::OutputDamageTracker; use smithay::backend::renderer::element::solid::SolidColorBuffer; use smithay::utils::user_data::UserDataMap; fn blur( renderer: &mut GlesRenderer, program: &BlurProgram, scale: f64, size: Size, transform: Transform, ) -> BlurRenderElement { let sample = Rectangle::::new((0, 10).into(), (145, 80).into()); let visible = Rectangle::::new((10, 20).into(), (120, 55).into()); let sample_physical = sample.to_physical_precise_round(scale); let framebuffer = framebuffer_clip_rect(sample_physical, size, transform.invert()); let capture = framebuffer_capture_rect(framebuffer); let texture = renderer .create_buffer(Fourcc::Abgr8888, (capture[2], capture[3]).into()) .unwrap(); BlurRenderElement::new( texture, program.0.clone(), &MaterialParameters { panel: None, corner_radii: [0.; 4], presentation_alpha: 1., background_opacity: 0., blur: (8. * scale) as f32, sample_geometry: sample, sample_physical, sample_framebuffer: framebuffer, shadow_rect: [0.; 4], shadow_values: [0.; 2], shadow_bounds: visible, geometry: visible, tint: [0.; 4], generation: 0, opaque: false, visible_framebuffer: framebuffer_clip_rect( visible.to_physical_precise_round(scale), size, transform.invert(), ), }, Arc::new(AtomicBool::new(true)), ) } fn pixels(renderer: &mut GlesRenderer, texture: &mut GlesTexture) -> Vec { let size = texture.size(); let target = renderer.bind(texture).unwrap(); let mapping = renderer .copy_framebuffer(&target, Rectangle::from_size(size), Fourcc::Abgr8888) .unwrap(); renderer.map_texture(&mapping).unwrap().to_vec() } #[test] #[ignore = "requires an EGL rendering device"] fn blur_behind_moving_opaque_window_matches_full_frames_at_buffer_age_three() { let mut renderer = { use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay}; let software = std::env::var_os("FERESE_TEST_EGL_SOFTWARE").is_some(); let device = EGLDevice::enumerate() .unwrap() .find(|device| device.is_software() == software) .unwrap(); // SAFETY: The enumerated device remains owned by the EGL display/context. let display = unsafe { EGLDisplay::new(device).unwrap() }; // SAFETY: This test owns the renderer's fresh context exclusively. unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() } }; let mut resources = RenderResources::default(); let program = blur_program(&mut resources, &mut renderer).unwrap(); for scale in [1., 1.5, 2.] { for transform in [Transform::Normal, Transform::Flipped180] { let size: Size = ((160. * scale) as i32, (100. * scale) as i32).into(); let mut tracker = OutputDamageTracker::new(size, scale, transform); let mut targets = (0..3) .map(|_| { renderer .create_buffer(Fourcc::Abgr8888, (size.w, size.h).into()) .unwrap() }) .collect::>(); let mut reference: GlesTexture = renderer .create_buffer(Fourcc::Abgr8888, (size.w, size.h).into()) .unwrap(); let effect = blur(&mut renderer, &program, scale, size, transform); let mut background = SolidColorBuffer::new((160, 100), [0.1, 0.7, 0.2, 1.]); let foreground = SolidColorBuffer::new((60, 90), [0.2, 0.1, 0.8, 1.]); for frame_index in 0..9 { if frame_index % 3 == 0 { background.set_color([0.1, 0.3 + frame_index as f32 * 0.05, 0.2, 1.]); } let solid = || { SolidColorRenderElement::from_buffer( &background, (0, 0), scale, 1., RenderElementKind::Unspecified, ) }; let front = SolidColorRenderElement::from_buffer( &foreground, (((100 - frame_index * 3) as f64 * scale) as i32, 0), scale, 1., RenderElementKind::Unspecified, ); let elements: Vec = vec![front.clone().into(), effect.clone().into(), solid().into()]; backdrop::update(&elements, scale); let slot = frame_index as usize % 3; { let mut target = renderer.bind(&mut targets[slot]).unwrap(); tracker .render_output( &mut renderer, &mut target, if frame_index < 3 { 0 } else { 3 }, &elements, Color32F::TRANSPARENT, ) .unwrap() .sync .wait() .unwrap(); } // Paint every layer without opaque culling or damage reuse. // A separate capture texture prevents the oracle from repairing // the incremental renderer's cached copy as a side effect. let reference_blur = blur(&mut renderer, &program, scale, size, transform); let full_elements: Vec = vec![front.into(), reference_blur.into(), solid().into()]; { let mut target = renderer.bind(&mut reference).unwrap(); let mut frame = renderer .render(&mut target, size, transform.invert()) .unwrap(); let full = Rectangle::from_size(size); frame.clear(Color32F::TRANSPARENT, &[full]).unwrap(); for element in full_elements.iter().rev() { let cache = UserDataMap::default(); let rect = element.geometry(scale.into()); if element.is_framebuffer_effect() { element .capture_framebuffer(&mut frame, element.src(), rect, &cache) .unwrap(); } element .draw( &mut frame, element.src(), rect, &[Rectangle::from_size(rect.size)], &[], element.is_framebuffer_effect().then_some(&cache), ) .unwrap(); } frame.finish().unwrap().wait().unwrap(); } let actual = pixels(&mut renderer, &mut targets[slot]); let expected = pixels(&mut renderer, &mut reference); let differing = actual .iter() .zip(&expected) .filter(|(a, b)| a.abs_diff(**b) > 1) .count(); assert_eq!( differing, 0, "scale={scale} transform={transform:?} frame={frame_index}" ); } } } }