Integrate native Rime and stabilize input, output, and rendering

- Add optional modifier-only Super binding whose tap is cancelled by
  other input, focus changes, grabs, locks, and session teardown.
- Ship the native ferese-rime user service, Rime defaults, installer
  inventory, package dependencies, session environment, and docs.
- Vendor Smithay 8a27023 with the SHM texture-cache format fix and add
  GPU regressions for format changes and resize snapshots.
- Release reused CRTC owners before staging output modes, then restore
  changed outputs before former owners when a transaction fails.
- Wait for resize-capture GPU completion, expand filtered surface
  damage, and preserve IME popup origin under fractional scaling.
- Mark weekends like holidays in both calendar layouts.
This commit is contained in:
aria committed 2026-10-30 03:13:43 +09:00
1 parent a2be421974
commit 993bc0f88f
391 files changed
+130908 -76

No files matched your search

@@ -1,4 +1,9 @@
//! Private protocol clients and offscreen EGL; no host desktop or DRM required.
#[path = "resize_scene_gpu_tests.rs"]
mod resize_scene_gpu_tests;
#[path = "partial_shm_gpu_tests.rs"]
mod partial_shm_gpu_tests;
use std::io::{Read, Write};
use std::os::fd::{AsRawFd, RawFd};
use std::os::unix::net::UnixStream;
@@ -0,0 +1,461 @@
//! Streaming SHM updates compared with independent full imports and full repaint.
use super::*;
use std::borrow::Cow;
use std::fs::File;
use std::os::unix::fs::FileExt;
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::element::{Element, RenderElement};
use smithay::backend::renderer::{Color32F, Frame, Renderer};
use smithay::utils::{Buffer, Physical, Size};
const WIDTH: u32 = 193;
const HEIGHT: u32 = 137;
const STRIDE: u32 = (WIDTH + 7) * 4;
const OFFSET: u32 = 64;
struct ReusableShm {
file: File,
buffer: u32,
version: usize,
format: u32,
}
fn buffer_pixels(pixels: &[u8], format: u32, switching_formats: bool) -> Cow<'_, [u8]> {
if !switching_formats {
return Cow::Borrowed(pixels);
}
let mut encoded = pixels.to_vec();
for pixel in encoded.chunks_exact_mut(4) {
if format == 1 {
// XRGB's unused high byte may legally be zero.
pixel[3] = 0;
} else {
// A valid premultiplied translucent ARGB surface makes a stale
// opaque shader variant visibly differ in RGB as well as alpha.
for channel in &mut pixel[..3] {
*channel = (u16::from(*channel) * 128 / 255) as u8;
}
pixel[3] = 128;
}
}
Cow::Owned(encoded)
}
fn write_region(file: &File, pixels: &[u8], rect: Rectangle<i32, Buffer>) {
for y in rect.loc.y..rect.loc.y + rect.size.h {
let source = (y as u32 * WIDTH + rect.loc.x as u32) as usize * 4;
let target = u64::from(OFFSET + y as u32 * STRIDE + rect.loc.x as u32 * 4);
file.write_all_at(&pixels[source..source + rect.size.w as usize * 4], target)
.unwrap();
}
}
fn pixels(renderer: &mut GlesRenderer, texture: &mut GlesTexture) -> Vec<u8> {
let size = smithay::backend::renderer::Texture::size(texture);
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 a private Wayland socket and an EGL rendering device"]
fn reused_shm_partial_damage_matches_fresh_full_uploads_at_fractional_scale() {
const TEST: &str = "state::capture_privacy::tests::partial_shm_gpu_tests::reused_shm_partial_damage_matches_fresh_full_uploads_at_fractional_scale";
if !crate::startup_tests::private_runtime(TEST) {
return;
}
run_shm_scenarios(&[(1, None), (3, None)]);
}
#[test]
#[ignore = "requires a private Wayland socket and an EGL rendering device"]
fn same_sized_argb_xrgb_switches_match_fresh_full_uploads() {
const TEST: &str = "state::capture_privacy::tests::partial_shm_gpu_tests::same_sized_argb_xrgb_switches_match_fresh_full_uploads";
if !crate::startup_tests::private_runtime(TEST) {
return;
}
run_shm_scenarios(&[(2, Some(0)), (2, Some(1))]);
}
fn run_shm_scenarios(scenarios: &[(usize, Option<u32>)]) {
let software = std::env::var_os("FERESE_TEST_EGL_SOFTWARE").is_some();
let device = EGLDevice::enumerate()
.unwrap()
.find(|device| device.is_software() == software)
.expect("requested EGL device");
eprintln!(
"partial SHM EGL software={software}, render node={:?}",
device.render_device_path()
);
// SAFETY: The display owns the enumerated device; each renderer below owns
// a fresh, unshared context used only by this test thread.
let display = unsafe { EGLDisplay::new(device).unwrap() };
let mut renderer = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
let scale = 1.5;
let physical: Size<i32, Physical> = (480, 360).into();
let mut mismatching_frames = 0;
for &(buffer_count, first_format) in scenarios {
let switching_formats = first_format.is_some();
let mut events = EventLoop::try_new().unwrap();
let config = crate::config::Config::parse_source(
"animations { enabled #false; }\nlayout { inner-gap 4; outer-gap 4; }\nwindow-rule transient=#false floating=#true\n",
)
.unwrap()
.runtime_config()
.unwrap();
let mut state = Ferese::new(&mut events, Display::new().unwrap(), config).unwrap();
state.animation_test_time = Some(Duration::ZERO);
state.focus_effect.active_opacity = 1.0;
state.focus_effect.inactive_opacity = 1.0;
state.focus_effect.inactive_dim = 0.0;
state.theme_settings.window_radius = 12.0;
let output = Output::new(
"partial-shm".into(),
PhysicalProperties {
serial_number: String::new(),
size: (0, 0).into(),
subpixel: Subpixel::Unknown,
make: "test".into(),
model: "test".into(),
},
);
output.change_current_state(
Some(Mode {
size: physical,
refresh: 60_000,
}),
Some(Transform::Normal),
Some(Scale::Fractional(scale)),
None,
);
state.space.map_output(&output, (0, 0));
state.register_output(&output, "partial-shm".into());
let (window, mut wire) = window(&mut state, &mut events, 0xff203c5c);
let id = state.windows.ids()[&window];
state.focused_window = Some(id);
state.set_floating_window_geometry(
&window,
(56, 64).into(),
(WIDTH as i32, HEIGHT as i32).into(),
);
state.display_handle.flush_clients().unwrap();
ack_configure(&mut wire, 3);
let client = window.toplevel().unwrap().wl_surface().client().unwrap();
let shm = client
.create_resource::<WlShm, smithay::wayland::GlobalData, Ferese>(
&state.display_handle,
1,
smithay::wayland::GlobalData,
)
.unwrap();
let compositor = client
.create_resource::<WlCompositor, smithay::wayland::GlobalData, Ferese>(
&state.display_handle,
6,
smithay::wayland::GlobalData,
)
.unwrap();
let mut raster = 0xff203c5cu32
.to_ne_bytes()
.repeat((WIDTH * HEIGHT) as usize);
let full: Rectangle<i32, Buffer> =
Rectangle::from_size((WIDTH as i32, HEIGHT as i32).into());
let mut buffers = Vec::new();
for slot in 0..buffer_count {
let format = first_format.map_or(0, |first| first ^ (slot as u32 % 2));
let file = tempfile::tempfile().unwrap();
let len = OFFSET + STRIDE * HEIGHT;
file.set_len(u64::from(len)).unwrap();
// Nontrivial offset/stride/padding exercise pixel-store state too.
file.write_all_at(&vec![0xa5; len as usize], 0).unwrap();
write_region(
&file,
&buffer_pixels(&raster, format, switching_formats),
full,
);
let pool = 7 + slot as u32 * 2;
let buffer = pool + 1;
request(
&mut wire,
shm.id().protocol_id(),
0,
&[pool, len],
Some(file.as_raw_fd()),
);
request(
&mut wire,
pool,
0,
&[buffer, OFFSET, WIDTH, HEIGHT, STRIDE, format],
None,
);
buffers.push(ReusableShm {
file,
buffer,
version: 0,
format,
});
}
let region = 7 + buffer_count as u32 * 2;
request(&mut wire, compositor.id().protocol_id(), 1, &[region], None);
if switching_formats {
// ARGB is genuinely translucent in this scenario; XRGB receives
// automatic opacity from its declared SHM format.
request(&mut wire, 2, 4, &[0], None);
} else {
request(&mut wire, region, 1, &[0, 0, WIDTH, HEIGHT], None);
request(&mut wire, 2, 4, &[region], None);
}
request(&mut wire, region, 0, &[], None);
request(&mut wire, 3, 3, &[0, 0, WIDTH, HEIGHT], None);
request(&mut wire, 2, 1, &[buffers[0].buffer, 0, 0], None);
request(&mut wire, 2, 9, &[0, 0, WIDTH, HEIGHT], None);
request(&mut wire, 2, 6, &[], None);
dispatch(&mut events, &mut state);
state.windows.update(id, |record| record.opening = None);
state.animations_enabled = true;
state.advance_animations_at(Duration::from_secs(1), Duration::from_secs(1));
let fixed_geometry = state.windows.geometry(&id).unwrap().visual.current;
assert_eq!(window.geometry().size, (WIDTH as i32, HEIGHT as i32).into());
let mut tracker = OutputDamageTracker::from_output(&output);
let mut targets: Vec<GlesTexture> = (0..3)
.map(|_| {
renderer
.create_buffer(Fourcc::Abgr8888, (physical.w, physical.h).into())
.unwrap()
})
.collect();
let mut history = Vec::new();
let mut total_changed_bytes = 0;
let mut partial_frames = 0;
for frame_index in 0..24 {
let mut changed = None;
if frame_index > 0 {
let rect = if switching_formats {
full
} else {
Rectangle::<i32, Buffer>::new(
(
(frame_index * 37 % (WIDTH as usize - 29)) as i32,
(frame_index * 23 % (HEIGHT as usize - 13)) as i32,
)
.into(),
(29, 13).into(),
)
};
let color = 0xff000000u32
| (((frame_index * 41) % 220 + 20) as u32) << 16
| (((frame_index * 67) % 220 + 20) as u32) << 8
| (((frame_index * 97) % 220 + 20) as u32);
for y in rect.loc.y..rect.loc.y + rect.size.h {
for x in rect.loc.x..rect.loc.x + rect.size.w {
let at = (y as u32 * WIDTH + x as u32) as usize * 4;
raster[at..at + 4].copy_from_slice(&color.to_ne_bytes());
}
}
history.push(rect);
let buffer = &mut buffers[frame_index % buffer_count];
let encoded = buffer_pixels(&raster, buffer.format, switching_formats);
// A recycled client buffer catches up only the rectangles changed
// since its last use. Surface damage describes this frame's delta
// from the preceding surface commit, independent of buffer age.
for pending in &history[buffer.version..] {
write_region(&buffer.file, &encoded, *pending);
total_changed_bytes += pending.size.w * pending.size.h * 4;
}
buffer.version = history.len();
request(&mut wire, 2, 1, &[buffer.buffer, 0, 0], None);
request(
&mut wire,
2,
9,
&[
rect.loc.x as u32,
rect.loc.y as u32,
rect.size.w as u32,
rect.size.h as u32,
],
None,
);
request(&mut wire, 2, 6, &[], None);
dispatch(&mut events, &mut state);
changed = Some((rect, color));
partial_frames += 1;
}
state.advance_animations_at(
Duration::from_millis(16),
Duration::from_millis(1016 + frame_index as u64 * 16),
);
let scene = state.sample_frame(&output, Duration::ZERO);
assert_eq!(
scene.windows[&id].presentation.bounds.current, fixed_geometry,
"streaming moved the window"
);
assert_eq!(scene.windows[&id].presentation.alpha(), 1.0);
let elements = crate::render::sampled_output_elements(
&mut state,
&mut renderer,
&output,
false,
&scene,
);
assert!(
elements
.iter()
.all(|element| !element.is_framebuffer_effect())
);
let slot = frame_index % 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::new(0.035, 0.04, 0.055, 1.0),
)
.unwrap()
.sync
.wait()
.unwrap();
}
let actual = pixels(&mut renderer, &mut targets[slot]);
let cached_format = smithay::backend::renderer::utils::with_renderer_surface_state(
window.toplevel().unwrap().wl_surface(),
|surface| {
surface
.texture::<GlesTexture>(renderer.context_id())
.and_then(smithay::backend::renderer::Texture::format)
},
)
.flatten();
// A fresh, unshared context cannot reuse the display path's imported
// SHM texture. Smithay creates a new texture with a complete TexImage2D
// upload regardless of partial damage, then this oracle repaints every
// layer without damage history or opaque-region culling.
let mut oracle =
unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
let oracle_context = oracle.context_id().erased();
let mut reference: GlesTexture = oracle
.create_buffer(Fourcc::Abgr8888, (physical.w, physical.h).into())
.unwrap();
let full_elements = crate::render::sampled_output_elements(
&mut state,
&mut oracle,
&output,
false,
&scene,
);
let fresh_format = smithay::backend::renderer::utils::with_renderer_surface_state(
window.toplevel().unwrap().wl_surface(),
|surface| {
surface
.texture::<GlesTexture>(oracle.context_id())
.and_then(smithay::backend::renderer::Texture::format)
},
)
.flatten();
{
let mut target = oracle.bind(&mut reference).unwrap();
let mut frame = oracle
.render(&mut target, physical, Transform::Normal)
.unwrap();
frame
.clear(
Color32F::new(0.035, 0.04, 0.055, 1.0),
&[Rectangle::from_size(physical)],
)
.unwrap();
for element in full_elements.iter().rev() {
let rect = element.geometry(scale.into());
element
.draw(
&mut frame,
element.src(),
rect,
&[Rectangle::from_size(rect.size)],
&[],
None,
)
.unwrap();
}
frame.finish().unwrap().wait().unwrap();
}
let expected = pixels(&mut oracle, &mut reference);
let differing = actual
.iter()
.zip(&expected)
.filter(|(a, b)| a.abs_diff(**b) > 1)
.count();
if differing > 0 {
mismatching_frames += 1;
let differences = actual
.chunks_exact(4)
.zip(expected.chunks_exact(4))
.enumerate()
.filter(|(_, (a, b))| a.iter().zip(*b).any(|(x, y)| x.abs_diff(*y) > 1))
.map(|(at, (a, b))| {
(
(at % physical.w as usize, at / physical.w as usize),
a.to_vec(),
b.to_vec(),
)
})
.collect::<Vec<_>>();
let bounds = differences.iter().fold(
(usize::MAX, usize::MAX, 0, 0),
|(l, t, r, b), ((x, y), _, _)| (l.min(*x), t.min(*y), r.max(*x), b.max(*y)),
);
eprintln!(
"reused client buffers={buffer_count}, partial frame={frame_index}, different channels={differing}, pixel bounds={bounds:?}, samples={:?}",
&differences[..differences.len().min(8)]
);
if switching_formats {
let rgb_differing = actual
.chunks_exact(4)
.zip(expected.chunks_exact(4))
.filter(|(a, b)| {
a[..3].iter().zip(&b[..3]).any(|(x, y)| x.abs_diff(*y) > 1)
})
.count();
eprintln!(
"same-size SHM first_format={first_format:?} attached_format={} cached_texture_format={cached_format:?} fresh_texture_format={fresh_format:?} RGB-mismatching-pixels={rgb_differing}",
buffers[frame_index % buffer_count].format
);
}
}
if !switching_formats && let Some((rect, color)) = changed {
let x = ((fixed_geometry.x + f64::from(rect.loc.x) + f64::from(rect.size.w) / 2.)
* scale) as usize;
let y = ((fixed_geometry.y + f64::from(rect.loc.y) + f64::from(rect.size.h) / 2.)
* scale) as usize;
let at = (y * physical.w as usize + x) * 4;
let cpu_rgba = [(color >> 16) as u8, (color >> 8) as u8, color as u8, 255];
assert_eq!(
&actual[at..at + 4],
&cpu_rgba,
"partial upload missed CPU raster at frame={frame_index}"
);
}
state.render.forget_context(&oracle_context);
}
assert_eq!(partial_frames, 23);
if !switching_formats {
assert!(total_changed_bytes < (WIDTH * HEIGHT * 4 * partial_frames as u32) as i32);
}
eprintln!(
"stable scale={scale}, reused wl_buffers={buffer_count}: 24 age-3 frames, {partial_frames} partial commits, {total_changed_bytes} locally written SHM bytes, 24 fresh-context full-upload oracle frames"
);
}
assert_eq!(
mismatching_frames, 0,
"partial SHM frames differ from complete fresh uploads"
);
}
@@ -0,0 +1,341 @@
//! Exercise the display scene's damage history, independently of capture repainting.
use super::*;
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::element::{Element, RenderElement};
use smithay::backend::renderer::{Color32F, Frame, Renderer, Texture};
use smithay::utils::{Physical, Size};
fn commit_raster(
state: &mut Ferese,
events: &mut EventLoop<'static, Ferese>,
window: &Window,
wire: &mut UnixStream,
object: &mut u32,
size: (u32, u32),
phase: u32,
) {
let client = window.toplevel().unwrap().wl_surface().client().unwrap();
let shm = client
.create_resource::<WlShm, smithay::wayland::GlobalData, Ferese>(
&state.display_handle,
1,
smithay::wayland::GlobalData,
)
.unwrap();
let compositor = client
.create_resource::<WlCompositor, smithay::wayland::GlobalData, Ferese>(
&state.display_handle,
6,
smithay::wayland::GlobalData,
)
.unwrap();
let (width, height) = size;
// A high-contrast grid catches stale/cropped pixels, unlike a solid window.
let mut raster = Vec::with_capacity((width * height * 4) as usize);
for y in 0..height {
for x in 0..width {
let color = if (x / 13 + y / 11 + phase) % 2 == 0 {
0xff26b976_u32
} else {
0xffd44381_u32
};
raster.extend_from_slice(&color.to_ne_bytes());
}
}
let mut file = tempfile::tempfile().unwrap();
file.write_all(&raster).unwrap();
request(
wire,
shm.id().protocol_id(),
0,
&[*object, width * height * 4],
Some(file.as_raw_fd()),
);
request(
wire,
*object,
0,
&[*object + 1, 0, width, height, width * 4, 0],
None,
);
request(wire, 3, 3, &[0, 0, width, height], None);
request(wire, 2, 1, &[*object + 1, 0, 0], None);
// Real opaque desktop clients declare an opaque region. Without it the
// damage tracker cannot exercise the rounded element's occlusion path.
request(wire, compositor.id().protocol_id(), 1, &[*object + 2], None);
request(wire, *object + 2, 1, &[0, 0, width, height], None);
request(wire, 2, 4, &[*object + 2], None);
request(wire, *object + 2, 0, &[], None);
request(wire, 2, 2, &[0, 0, width, height], None);
request(wire, 2, 6, &[], None);
*object += 3;
dispatch(events, state);
}
fn read_pixels(renderer: &mut GlesRenderer, texture: &mut GlesTexture) -> Vec<u8> {
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 a private Wayland socket and an EGL rendering device"]
fn floating_maximize_handoff_matches_full_repaint_at_buffer_age_three() {
const TEST: &str = "state::capture_privacy::tests::resize_scene_gpu_tests::floating_maximize_handoff_matches_full_repaint_at_buffer_age_three";
if !crate::startup_tests::private_runtime(TEST) {
return;
}
let software = std::env::var_os("FERESE_TEST_EGL_SOFTWARE").is_some();
let device = EGLDevice::enumerate()
.unwrap()
.find(|device| device.is_software() == software)
.expect("requested EGL rendering device");
eprintln!(
"resize scene EGL software={software}, render node={:?}",
device.render_device_path()
);
// SAFETY: This test owns the enumerated device and the fresh EGL context.
let display = unsafe { EGLDisplay::new(device).unwrap() };
// SAFETY: GL work remains on this renderer's owning test thread.
let mut renderer = unsafe { GlesRenderer::new(EGLContext::new(&display).unwrap()).unwrap() };
for scale in [1.0, 1.5] {
let mut events = EventLoop::try_new().unwrap();
let config = crate::config::Config::parse_source(
"animations { enabled #false; }\nlayout { inner-gap 4; outer-gap 4; }\nwindow-rule transient=#false floating=#true\n",
)
.unwrap()
.runtime_config()
.unwrap();
let mut state = Ferese::new(&mut events, Display::new().unwrap(), config).unwrap();
state.animation_test_time = Some(Duration::ZERO);
state.focus_effect.active_opacity = 1.0;
state.focus_effect.inactive_opacity = 1.0;
state.focus_effect.inactive_dim = 0.0;
state.theme_settings.window_radius = 12.0;
let output = Output::new(
"resize-age-three".into(),
PhysicalProperties {
serial_number: String::new(),
size: (0, 0).into(),
subpixel: Subpixel::Unknown,
make: "test".into(),
model: "test".into(),
},
);
let physical: Size<i32, Physical> = ((320.0 * scale) as i32, (240.0 * scale) as i32).into();
output.change_current_state(
Some(Mode {
size: physical,
refresh: 60_000,
}),
Some(Transform::Normal),
Some(Scale::Fractional(scale)),
None,
);
state.space.map_output(&output, (0, 0));
state.register_output(&output, "resize-age-three".into());
let (window, mut wire) = window(&mut state, &mut events, 0xff26b976);
let id = state.windows.ids()[&window];
state.focused_window = Some(id);
state.set_floating_window_geometry(&window, (106, 92).into(), (128, 80).into());
state.display_handle.flush_clients().unwrap();
ack_configure(&mut wire, 3);
let mut object = 7;
commit_raster(
&mut state,
&mut events,
&window,
&mut wire,
&mut object,
(128, 80),
0,
);
state.windows.update(id, |record| record.opening = None);
state.animations_enabled = true;
let mut tracker = OutputDamageTracker::from_output(&output);
let mut targets: Vec<GlesTexture> = (0..3)
.map(|_| {
renderer
.create_buffer(Fourcc::Abgr8888, (physical.w, physical.h).into())
.unwrap()
})
.collect();
let mut reference: GlesTexture = renderer
.create_buffer(Fourcc::Abgr8888, (physical.w, physical.h).into())
.unwrap();
let mut frame_index = 0;
let mut now = Duration::ZERO;
let mut snapshot_frames = 0;
let mut fill_frames = 0;
let mut moving_frames = 0;
let mut repaint_frames = 0;
let mut native_draw_frames = 0;
let mut opaque_frames = 0;
// Delayed commits exercise both old-frame cropping on shrink and neutral
// fill on grow. Repeating in both directions also reuses every target.
for (cycle, delay) in [3, 1, 5, 0].into_iter().enumerate() {
state.set_window_maximized(id, cycle % 2 == 0);
state.display_handle.flush_clients().unwrap();
let (_, configured) = ack_configure_with_size(&mut wire, 3, Some(4));
let requested = configured.expect("maximize/restore configure");
for tick in 0..28 {
if tick == delay {
// The offscreen fixture has no DirectBackend. Invoke the
// same capture-before-release operation used by that backend.
let snapshot = crate::render::capture_resize_snapshot(
&mut renderer,
&window,
window.geometry(),
scale,
32 * 1024 * 1024,
)
.unwrap()
.expect("old client pixels");
state.render.set_snapshot(id, snapshot);
commit_raster(
&mut state,
&mut events,
&window,
&mut wire,
&mut object,
requested,
cycle as u32 + 1,
);
repaint_frames += 1;
} else if tick > delay && tick % 2 == 0 {
// IDE repainting alternates with unchanged client buffers,
// so both client damage and compositor-only motion matter.
commit_raster(
&mut state,
&mut events,
&window,
&mut wire,
&mut object,
requested,
cycle as u32 + tick as u32,
);
repaint_frames += 1;
}
now += Duration::from_millis(16);
state.advance_animations_at(Duration::from_millis(16), now);
let scene = state.sample_frame(&output, Duration::from_millis(5));
let sample = &scene.windows[&id];
assert_eq!(sample.presentation.alpha(), 1.0, "ordinary opaque window");
if sample.geometry.visual.current != sample.geometry.visual.target {
moving_frames += 1;
}
snapshot_frames += usize::from(state.render.snapshot(&id).is_some());
let source = window.geometry().size;
let visual = sample.presentation.bounds.current;
fill_frames += usize::from(
f64::from(source.w) < visual.width || f64::from(source.h) < visual.height,
);
let elements = crate::render::sampled_output_elements(
&mut state,
&mut renderer,
&output,
false,
&scene,
);
native_draw_frames +=
usize::from(state.render.snapshot(&id).is_some_and(|snapshot| {
elements.iter().any(|element| element.id() == &snapshot.id)
}));
opaque_frames += usize::from(
elements
.iter()
.any(|element| !element.opaque_regions(scale.into()).is_empty()),
);
assert!(
elements
.iter()
.all(|element| !element.is_framebuffer_effect()),
"this regression must cover opaque resize without backdrop blur"
);
let slot = frame_index % 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::new(0.035, 0.04, 0.055, 1.0),
)
.unwrap()
.sync
.wait()
.unwrap();
}
// Independent oracle: no damage history, no opaque-region
// culling, and every layer painted back-to-front into a clean FBO.
{
let mut target = renderer.bind(&mut reference).unwrap();
let mut frame = renderer
.render(&mut target, physical, Transform::Normal)
.unwrap();
frame
.clear(
Color32F::new(0.035, 0.04, 0.055, 1.0),
&[Rectangle::from_size(physical)],
)
.unwrap();
for element in elements.iter().rev() {
let rect = element.geometry(scale.into());
element
.draw(
&mut frame,
element.src(),
rect,
&[Rectangle::from_size(rect.size)],
&[],
None,
)
.unwrap();
}
frame.finish().unwrap().wait().unwrap();
}
let actual = read_pixels(&mut renderer, &mut targets[slot]);
let expected = read_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} cycle={cycle} tick={tick} frame={frame_index} source={source:?} visual={visual:?}"
);
frame_index += 1;
}
}
assert!(snapshot_frames > 20, "resize handoff was not exercised");
assert!(
fill_frames > 0,
"growing native content never needed resize fill"
);
eprintln!(
"opaque resize scale={scale}: {frame_index} age-3 frames, {snapshot_frames} snapshot frames, {native_draw_frames} snapshot draw frames, {fill_frames} fill frames, {moving_frames} moving frames, {repaint_frames} client repaint frames, {opaque_frames} opaque frames"
);
assert!(moving_frames > 20, "maximize/restore animation did not run");
assert!(
repaint_frames > 40,
"client repainting did not overlap motion"
);
assert!(
native_draw_frames > 20,
"retained snapshots were not in the display scene"
);
assert!(
opaque_frames > 20,
"opaque-region culling was not exercised"
);
}
}