Commit 48af562d968b
tarball6 files changed+219-59
@@ -678,11 +678,22 @@ impl Layout {
678678
self.place(w);679679
}680680
681
+ /// Switches a window between tiled and floating. A scratchpad window682
+ /// leaves its scratchpad and tiles on the tag being viewed, since its own683
+ /// tag may well be hidden.681684
pub fn toggle_floating(&mut self, w: WindowId) {682685
let Some(win) = self.windows.get(w) else {683686
return;684687
};685
- if win.scratchpad.is_some() {688
+ if let Some(s) = win.scratchpad {689
+ let o = win.output.or(self.active);690
+ let tag = o.map_or(self.homeless, |o| self.launch_tag(o));691
+ self.leave_scratchpad(s, w);692
+ let win = &mut self.windows[w];693
+ win.output = o;694
+ win.tag = tag;695
+ win.floating = false;696
+ self.place(w);686697
return;687698
}688699
self.unplace(w);@@ -327,7 +327,19 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
327327
}328328
surface.queued = false;329329
if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {330
- surface.last_vblank_ns = Some(t.as_nanos() as u64);330
+ let t = t.as_nanos() as u64;331
+ if let Some(last) = surface.last_vblank_ns332
+ && surface.redraw333
+ && t - last > surface.refresh_ns * 3 / 2334
+ {335
+ let missed = (t - last) / surface.refresh_ns - 1;336
+ tracing::debug!(337
+ output = surface.output.name(),338
+ missed,339
+ "missed vblanks while animating"340
+ );341
+ }342
+ surface.last_vblank_ns = Some(t);331343
}332344
render_surface(state, crtc);333345
}@@ -381,6 +393,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle) {
381393
if surface.queued {382394
return;383395
}396
+ let began = std::time::Instant::now();384397
let now = surface.predict(now_ns());385398
let Some(area) = layout.output(surface.id).map(|o| o.area) else {386399
return;@@ -413,6 +426,10 @@ fn render_surface(state: &mut State, crtc: crtc::Handle) {
413426
Ok(_) => {}414427
Err(e) => tracing::warn!("rendering failed: {e}"),415428
}429
+ let took = began.elapsed();430
+ if took.as_nanos() as u64 > surface.refresh_ns / 2 {431
+ tracing::debug!(output = surface.output.name(), ?took, "slow frame");432
+ }416433
surface.redraw = animating;417434
if animating && !surface.queued {418435
let wait = Duration::from_nanos(surface.refresh_ns);@@ -70,6 +70,7 @@ impl CompositorHandler for State {
7070
t.send_configure();7171
}7272
}73
+ self.on_buffer(surface);7374
self.popups.commit(surface);7475
if let Some(PopupKind::Xdg(popup)) = self.popups.find_popup(surface)7576
&& !popup.is_initial_configure_sent()@@ -9,7 +9,7 @@ use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement
99
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};1010
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;1111
use smithay::backend::renderer::element::texture::TextureRenderElement;12
-use smithay::backend::renderer::element::utils::CropRenderElement;12
+use smithay::backend::renderer::element::utils::{CropRenderElement, RescaleRenderElement};1313
use smithay::backend::renderer::element::{AsRenderElements, Element, Id, Kind, RenderElement};1414
use smithay::backend::renderer::utils::CommitCounter;1515
use smithay::backend::renderer::utils::with_renderer_surface_state;@@ -27,6 +27,7 @@ smithay::backend::renderer::element::render_elements! {
2727
Effect=EffectElement,2828
Border=SolidColorRenderElement,2929
Memory=MemoryRenderBufferRenderElement<VulkanRenderer>,30
+ Rescaled=RescaleRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>,3031
}3132
3233
/// Border strips per window, kept between frames so unchanged borders cause@@ -67,28 +68,30 @@ pub fn build(
6768
continue;6869
}6970
match anim.gliding(p.window, now) {70
- Some(r) => above.push((p.window, local(r), true)),71
+ Some(r) => above.push((p.window, local(r), true, true)),7172
None => {7273
let r = anim.tiled(o, p.window, now).unwrap_or(p.rect);73
- tiled.push((p.window, local(r), true));74
+ tiled.push((p.window, local(r), true, false));7475
}7576
}7677
}7778
for &(w, r) in &arr.floating {78
- above.push((w, local(anim.gliding(w, now).unwrap_or(r)), true));79
+ let glide = anim.gliding(w, now);80
+ above.push((w, local(glide.unwrap_or(r)), true, glide.is_some()));7981
}8082
if let Some(w) = arr.fullscreen {81
- above.push((w, local(anim.gliding(w, now).unwrap_or(area)), false));83
+ let glide = anim.gliding(w, now);84
+ above.push((w, local(glide.unwrap_or(area)), false, glide.is_some()));8285
}8386
borders.0.retain(|id, _| windows.contains_key(id));8487
let focused = layout.focused();8588
let mut out = Vec::new();86
- for (id, r, framed) in tiled.into_iter().chain(above).rev() {89
+ for (id, r, framed, gliding) in tiled.into_iter().chain(above).rev() {8790
let Some(window) = windows.get(&id) else {8891
continue;8992
};9093
let effect = anim.effect(id, now);91
- window_elements(&mut out, renderer, window, r, effect);94
+ window_elements(&mut out, renderer, window, r, effect, gliding);9295
if framed && deco.border > 0 {9396
let color = if focused == Some(id) {9497
deco.focused.0@@ -120,15 +123,17 @@ pub fn build(
120123
out121124
}122125
123
-/// Draws `window` filling `r`. A buffer bigger than `r` is cropped, and one124
-/// smaller has its last row and column stretched over the rest, so a resize125
-/// in flight never shows a scaled or missing client.126
+/// Draws `window` filling `r`. In a planned move a buffer bigger than `r` is127
+/// cropped and one smaller has its last row and column stretched over the128
+/// rest, keeping content aligned with its neighbours. A gliding window is129
+/// scaled instead, so the client resizing mid-glide does not jump.126130
fn window_elements(127131
out: &mut Vec<Draw>,128132
renderer: &mut VulkanRenderer,129133
window: &Window,130134
r: Rect,131135
effect: Option<Effect>,136
+ scaled: bool,132137
) {133138
let Some(((x, y), (w, h))) = pixels(r) else {134139
return;@@ -142,6 +147,19 @@ fn window_elements(
142147
effect_elements(out, renderer, window, elems, ((x, y), (w, h)), effect);143148
return;144149
}150
+ if scaled && geo.size.w > 0 && geo.size.h > 0 {151
+ let scale = Scale::from((152
+ f64::from(w) / f64::from(geo.size.w),153
+ f64::from(h) / f64::from(geo.size.h),154
+ ));155
+ let origin = Point::<i32, Physical>::from((x, y));156
+ out.extend(157
+ elems158
+ .into_iter()159
+ .map(|e| Draw::Rescaled(RescaleRenderElement::from_element(e, origin, scale))),160
+ );161
+ return;162
+ }145163
if (w, h) == (geo.size.w, geo.size.h) {146164
out.extend(elems.into_iter().map(Draw::Surface));147165
return;@@ -13,7 +13,9 @@ use smithay::desktop::{PopupManager, Space, Window, WindowSurfaceType};
1313
use smithay::input::keyboard::{Keycode, XkbConfig};1414
use smithay::input::pointer::CursorImageStatus;1515
use smithay::input::{Seat, SeatState};16
+use smithay::reexports::calloop::LoopHandle;1617
use smithay::reexports::calloop::generic::Generic;18
+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};1719
use smithay::reexports::calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction};1820
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState;1921
use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};@@ -28,7 +30,7 @@ use smithay::wayland::shell::xdg::decoration::XdgDecorationState;
2830
use smithay::wayland::shell::xdg::{XdgShellState, XdgToplevelSurfaceData};2931
use smithay::wayland::shm::ShmState;3032
use smithay::wayland::socket::ListeningSocketSource;31
-use std::collections::HashMap;33
+use std::collections::{HashMap, HashSet};3234
use std::ffi::OsString;3335
use std::path::PathBuf;3436
use std::sync::Arc;@@ -70,6 +72,8 @@ pub struct State {
7072
/// How each shown window was placed by the last layout pass, to spot7173
/// switches between tiled, floating and fullscreen.7274
pub last_kind: HashMap<WindowId, (Kind, Rect, OutputId)>,75
+ pub awaiting: HashSet<WindowId>,76
+ pub handle: LoopHandle<'static, State>,7377
7478
pub compositor_state: CompositorState,7579
pub xdg_shell_state: XdgShellState,@@ -85,7 +89,7 @@ pub struct State {
8589
8690
impl State {8791
pub fn new(88
- event_loop: &mut EventLoop<State>,92
+ event_loop: &mut EventLoop<'static, State>,8993
display: Display<State>,9094
config_path: Option<PathBuf>,9195
) -> Result<Self, Box<dyn std::error::Error>> {@@ -157,6 +161,8 @@ impl State {
157161
anim: Animator::new(animation),158162
borders: Default::default(),159163
last_kind: HashMap::new(),164
+ awaiting: HashSet::new(),165
+ handle: event_loop.handle(),160166
compositor_state: CompositorState::new::<State>(&dh),161167
xdg_shell_state: XdgShellState::new::<State>(&dh),162168
_xdg_decoration_state: XdgDecorationState::new::<State>(&dh),@@ -225,13 +231,44 @@ impl State {
225231
self.layout.scratchpad_add(id, name);226232
}227233
self.windows.insert(id, window);228
- self.anim.spawned(id, now_ns());234
+ self.await_first_buffer(id);229235
if self.layout.visible(id) {230236
self.layout.focus(id);231237
}232238
self.apply_layout();233239
}234240
241
+ /// Holds a new window on its seed until its first buffer lands, so the242
+ /// spawn animation never plays over an empty slot. Clients that are slow243
+ /// to draw get it anyway after a while.244
+ fn await_first_buffer(&mut self, id: WindowId) {245
+ self.awaiting.insert(id);246
+ let timeout = Timer::from_duration(Duration::from_millis(300));247
+ let _ = self.handle.insert_source(timeout, move |_, _, state| {248
+ state.first_buffer(id);249
+ TimeoutAction::Drop250
+ });251
+ }252
+253
+ /// Called on commits, starts the spawn of a window that now has a buffer.254
+ pub fn on_buffer(&mut self, surface: &WlSurface) {255
+ if self.awaiting.is_empty() {256
+ return;257
+ }258
+ let has_buffer =259
+ with_renderer_surface_state(surface, |s| s.buffer_size().is_some()).unwrap_or(false);260
+ if has_buffer && let Some(id) = self.window_id(surface) {261
+ self.first_buffer(id);262
+ }263
+ }264
+265
+ fn first_buffer(&mut self, id: WindowId) {266
+ if self.awaiting.remove(&id) {267
+ self.anim.spawned(id, now_ns());268
+ self.apply_layout();269
+ }270
+ }271
+235272
pub fn unmap_window(&mut self, surface: &WlSurface) {236273
self.unmapped237274
.retain(|w| w.toplevel().is_some_and(|t| t.wl_surface() != surface));@@ -321,8 +358,19 @@ impl State {
321358
if let Some(w) = arr.fullscreen {322359
shown.push((w, area, Kind::Fullscreen, o));323360
}361
+ let held: Vec<WindowId> = arr362
+ .tiled363
+ .iter()364
+ .map(|p| p.window)365
+ .filter(|w| self.awaiting.contains(w))366
+ .collect();367
+ let planned = if held.is_empty() {368
+ arr.tiled.clone()369
+ } else {370
+ self.layout.arrange_hiding(o, &held).tiled371
+ };324372
let targets: Vec<(WindowId, Rect)> =325
- arr.tiled.iter().map(|p| (p.window, p.rect)).collect();373
+ planned.iter().map(|p| (p.window, p.rect)).collect();326374
tracks.push((o, targets));327375
}328376
@@ -26,12 +26,13 @@ use smithay::wayland::shm::{shm_format_to_fourcc, with_buffer_contents};
2626
use std::collections::HashMap;2727
use std::fmt;2828
use std::marker::PhantomData;29
-use std::sync::Arc;29
+use std::sync::{Arc, Mutex};3030
3131
struct Pending {3232
fence: Arc<FenceInner>,3333
cmd: vk::CommandBuffer,3434
_keep: Vec<Arc<ImageInner>>,35
+ _staging: Option<Staging>,3536
}3637
3738
pub struct VulkanRenderer {@@ -365,10 +366,13 @@ impl VulkanRenderer {
365366
fence: fence.clone(),366367
cmd,367368
_keep: sampled,369
+ _staging: None,368370
});369371
Ok(SyncPoint::from(VkFence(fence)))370372
}371373
374
+ /// Copies `region` of a full-image `data` into `image`, rows top-down or375
+ /// flipped.372376
fn upload(373377
&mut self,374378
image: &Arc<ImageInner>,@@ -376,35 +380,53 @@ impl VulkanRenderer {
376380
region: Rectangle<i32, Buffer>,377381
flipped: bool,378382
) -> Result<(), Error> {379
- if image.external {380
- return Err(Error::ForeignTexture);381
- }382383
let width = image.width as usize;383
- let needed = width * image.height as usize * BYTES_PER_PIXEL;384
+ let rows = image.height as usize;385
+ let needed = width * rows * BYTES_PER_PIXEL;384386
if data.len() < needed {385387
return Err(Error::BufferTooSmall {386388
needed,387389
got: data.len(),388390
});389391
}392
+ let stride = width * BYTES_PER_PIXEL;393
+ self.upload_with(image, region, |y| {394
+ let from = if flipped { rows - 1 - y } else { y };395
+ let start = from * stride + region.loc.x as usize * BYTES_PER_PIXEL;396
+ &data[start..start + region.size.w as usize * BYTES_PER_PIXEL]397
+ })398
+ }399
+400
+ /// Uploads `region` of `image`, taking each image row `y` in the region401
+ /// from `row(y)`. The CPU does not wait: the copy is ordered before later402
+ /// frames by its barrier, and its staging memory lives until its fence.403
+ fn upload_with<'a>(404
+ &mut self,405
+ image: &Arc<ImageInner>,406
+ region: Rectangle<i32, Buffer>,407
+ row: impl Fn(usize) -> &'a [u8],408
+ ) -> Result<(), Error> {409
+ if image.external {410
+ return Err(Error::ForeignTexture);411
+ }412
+ self.reap();413
+ let (w, h) = (region.size.w as usize, region.size.h as usize);414
+ if w == 0 || h == 0 {415
+ return Ok(());416
+ }417
+ let line = w * BYTES_PER_PIXEL;390418
let mut staging = Staging::new(391419
&self.shared,392
- needed,420
+ line * h,393421
vk::BufferUsageFlags::TRANSFER_SRC,394422
MemoryLocation::CpuToGpu,395423
)?;396
- let row = width * BYTES_PER_PIXEL;397
- let rows = image.height as usize;398424
let dst = staging.bytes_mut();399
- for y in 0..rows {400
- let from = if flipped { rows - 1 - y } else { y };401
- dst[y * row..(y + 1) * row].copy_from_slice(&data[from * row..(from + 1) * row]);425
+ for i in 0..h {426
+ dst[i * line..(i + 1) * line].copy_from_slice(row(region.loc.y as usize + i));402427
}403428
let copy = vk::BufferImageCopy::default()404
- .buffer_offset(405
- ((region.loc.y as usize * width + region.loc.x as usize) * BYTES_PER_PIXEL) as u64,406
- )407
- .buffer_row_length(image.width)429
+ .buffer_row_length(w as u32)408430
.image_subresource(409431
vk::ImageSubresourceLayers::default()410432
.aspect_mask(vk::ImageAspectFlags::COLOR)@@ -416,24 +438,19 @@ impl VulkanRenderer {
416438
z: 0,417439
})418440
.image_extent(vk::Extent3D {419
- width: region.size.w as u32,420
- height: region.size.h as u32,441
+ width: w as u32,442
+ height: h as u32,421443
depth: 1,422444
});423
- let buffer = staging.buffer;424
- let target = image.clone();425
- self.one_shot(|d, cmd, family| unsafe {426
- acquire(427
- d,428
- cmd,429
- family,430
- &target,431
- vk::ImageLayout::TRANSFER_DST_OPTIMAL,432
- );445
+ let d = &self.shared.device.device;446
+ let family = self.shared.device.queue_family;447
+ let cmd = self.begin()?;448
+ unsafe {449
+ acquire(d, cmd, family, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL);433450
d.cmd_copy_buffer_to_image(434451
cmd,435
- buffer,436
- target.image,452
+ staging.buffer,453
+ image.image,437454
vk::ImageLayout::TRANSFER_DST_OPTIMAL,438455
&[copy],439456
);@@ -441,10 +458,18 @@ impl VulkanRenderer {
441458
d,442459
cmd,443460
family,444
- &target,461
+ image,445462
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,446463
);447
- })464
+ }465
+ let fence = self.end_and_submit(cmd)?;466
+ self.pending.push(Pending {467
+ fence,468
+ cmd,469
+ _keep: vec![image.clone()],470
+ _staging: Some(staging),471
+ });472
+ Ok(())448473
}449474
450475
fn read_back(@@ -879,13 +904,20 @@ impl ExportMem for VulkanRenderer {
879904
}880905
881906
impl ImportMemWl for VulkanRenderer {907
+ /// Keeps one texture per surface and uploads only the damaged rows into908
+ /// it, straight from the client's shm pool.882909
fn import_shm_buffer(883910
&mut self,884911
buffer: &WlBuffer,885
- _surface: Option<&SurfaceData>,886
- _damage: &[Rectangle<i32, Buffer>],912
+ surface: Option<&SurfaceData>,913
+ damage: &[Rectangle<i32, Buffer>],887914
) -> Result<VulkanTexture, Error> {888
- let (pixels, fourcc, size) = with_buffer_contents(buffer, |ptr, len, data| {915
+ let cache = surface.map(|s| {916
+ s.data_map917
+ .insert_if_missing_threadsafe(|| ShmCache(Mutex::new(None)));918
+ s.data_map.get::<ShmCache>().expect("just inserted")919
+ });920
+ with_buffer_contents(buffer, |ptr, len, data| {889921
let fourcc = shm_format_to_fourcc(data.format).ok_or(Error::ShmAccess)?;890922
let (w, h, stride, offset) = (891923
data.width as usize,@@ -893,18 +925,51 @@ impl ImportMemWl for VulkanRenderer {
893925
data.stride as usize,894926
data.offset as usize,895927
);896
- let row = w * BYTES_PER_PIXEL;897
- if w == 0 || h == 0 || stride < row || offset + stride * (h - 1) + row > len {928
+ let line = w * BYTES_PER_PIXEL;929
+ if w == 0 || h == 0 || stride < line || offset + stride * (h - 1) + line > len {898930
return Err(Error::ShmAccess);899931
}900
- let mut pixels = Vec::with_capacity(row * h);901
- for y in 0..h {902
- let line = unsafe { std::slice::from_raw_parts(ptr.add(offset + y * stride), row) };903
- pixels.extend_from_slice(line);932
+ let size = Size::from((data.width, data.height));933
+ let full = Rectangle::from_size(size);934
+ let reused = cache935
+ .and_then(|c| c.0.lock().unwrap_or_else(|e| e.into_inner()).clone())936
+ .filter(|t| {937
+ t.0.fourcc == fourcc938
+ && t.size() == size939
+ && Arc::ptr_eq(&t.0.shared, &self.shared)940
+ });941
+ let (texture, region) = match reused {942
+ Some(t) => {943
+ let dirty = damage944
+ .iter()945
+ .filter_map(|d| d.intersection(full))946
+ .reduce(|a, b| a.merge(b));947
+ (t, if damage.is_empty() { Some(full) } else { dirty })948
+ }949
+ None => {950
+ let usage = vk::ImageUsageFlags::SAMPLED951
+ | vk::ImageUsageFlags::TRANSFER_DST952
+ | vk::ImageUsageFlags::TRANSFER_SRC;953
+ let image =954
+ ImageInner::create(&self.shared, fourcc, w as u32, h as u32, usage)?;955
+ (VulkanTexture(image), Some(full))956
+ }957
+ };958
+ if let Some(region) = region {959
+ let x = region.loc.x as usize * BYTES_PER_PIXEL;960
+ let rw = region.size.w as usize * BYTES_PER_PIXEL;961
+ self.upload_with(&texture.0, region, |y| unsafe {962
+ std::slice::from_raw_parts(ptr.add(offset + y * stride + x), rw)963
+ })?;904964
}905
- Ok((pixels, fourcc, Size::from((data.width, data.height))))965
+ if let Some(c) = cache {966
+ *c.0.lock().unwrap_or_else(|e| e.into_inner()) = Some(texture.clone());967
+ }968
+ Ok(texture)906969
})907
- .map_err(|_| Error::ShmAccess)??;908
- self.import_memory(&pixels, fourcc, size, false)970
+ .map_err(|_| Error::ShmAccess)?909971
}910972
}973
+974
+/// The texture last imported for a surface's shm buffers.975
+struct ShmCache(Mutex<Option<VulkanTexture>>);