Commit 0b89f53f59e5
tarball5 files changed+213-52
@@ -2,30 +2,38 @@
22
//! cursor surface wins, otherwise the xcursor theme's default arrow.33
44
use crate::scene::{Draw, to_physical};5
-use monolith_render_vk::VulkanRenderer;5
+use monolith_render_vk::{VulkanFrame, VulkanRenderer, VulkanTexture};66
use smithay::backend::allocator::Fourcc;7
-use smithay::backend::renderer::element::memory::{8
- MemoryRenderBuffer, MemoryRenderBufferRenderElement,9
-};7
+use smithay::backend::renderer::element::memory::MemoryBuffer;108
use smithay::backend::renderer::element::solid::SolidColorRenderElement;119
use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;12
-use smithay::backend::renderer::element::{Id, Kind};10
+use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};1311
use smithay::backend::renderer::utils::CommitCounter;12
+use smithay::backend::renderer::{Frame, ImportMem};1413
use smithay::input::pointer::CursorIcon;1514
use smithay::input::pointer::{CursorImageAttributes, CursorImageStatus};1615
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;17
-use smithay::utils::{Logical, Physical, Point, Rectangle, Transform};16
+use smithay::utils::user_data::UserDataMap;17
+use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Transform};1818
use smithay::wayland::compositor::with_states;1919
use std::collections::HashMap;2020
use std::sync::Mutex;2121
22
-type Image = Option<(MemoryRenderBuffer, Point<i32, Logical>)>;22
+/// A theme shape sized for one output scale.23
+struct Prepared {24
+ id: Id,25
+ pixels: MemoryBuffer,26
+ texture: VulkanTexture,27
+ hotspot: Point<i32, Physical>,28
+}2329
2430
pub struct Cursor {2531
theme: xcursor::CursorTheme,2632
size: u32,27
- /// Shapes loaded so far, including ones the theme lacks.28
- images: HashMap<CursorIcon, Image>,33
+ /// Every image of the shapes read so far, including ones the theme lacks.34
+ files: HashMap<CursorIcon, Option<Vec<xcursor::parser::Image>>>,35
+ /// Shapes sized for an output scale, in steps of 1/120.36
+ sized: HashMap<(CursorIcon, u32), Option<Prepared>>,2937
}3038
3139
impl Cursor {@@ -38,9 +46,10 @@ impl Cursor {
3846
let mut cursor = Self {3947
theme: xcursor::CursorTheme::load(&theme),4048
size,41
- images: HashMap::new(),49
+ files: HashMap::new(),50
+ sized: HashMap::new(),4251
};43
- if cursor.image(CursorIcon::Default).is_none() {52
+ if cursor.file(CursorIcon::Default).is_none() {4453
tracing::warn!(theme, "no xcursor theme found, drawing a plain cursor");4554
}4655
// Reading a shape from the theme takes milliseconds, which in a frame@@ -64,21 +73,47 @@ impl Cursor {
6473
CursorIcon::SwResize,6574
CursorIcon::SeResize,6675
] {67
- cursor.image(icon);76
+ cursor.file(icon);6877
}6978
cursor7079
}7180
72
- /// The theme's image for `icon`, falling back to the default arrow.73
- fn image(&mut self, icon: CursorIcon) -> Option<&(MemoryRenderBuffer, Point<i32, Logical>)> {74
- if !self.images.contains_key(&icon) {81
+ /// The theme's images for `icon`, falling back to the default arrow.82
+ fn file(&mut self, icon: CursorIcon) -> Option<&[xcursor::parser::Image]> {83
+ if !self.files.contains_key(&icon) {7584
let names = std::iter::once(icon.name()).chain(icon.alt_names().iter().copied());76
- let image = names85
+ let images = names7786
.chain(["default", "left_ptr"])78
- .find_map(|n| load_image(&self.theme, n, self.size));79
- self.images.insert(icon, image);87
+ .find_map(|n| load_file(&self.theme, n));88
+ self.files.insert(icon, images);89
+ }90
+ self.files.get(&icon)?.as_deref()91
+ }92
+93
+ /// The theme's image nearest the cursor size times `scale`, stretched to94
+ /// exactly that size so it covers whole pixels at any scale.95
+ fn sized(96
+ &mut self,97
+ renderer: &mut VulkanRenderer,98
+ icon: CursorIcon,99
+ scale: f64,100
+ ) -> Option<&Prepared> {101
+ let key = (icon, (scale * 120.0).round() as u32);102
+ if !self.sized.contains_key(&key) {103
+ let wanted = ((f64::from(self.size) * scale).round() as u32).max(1);104
+ let prepared = self105
+ .file(icon)106
+ .and_then(|images| images.iter().min_by_key(|i| i.size.abs_diff(wanted)))107
+ .and_then(|image| {108
+ prepare(109
+ renderer,110
+ image,111
+ f64::from(wanted) / f64::from(image.size.max(1)),112
+ )113
+ });114
+ self.sized.insert(key, prepared);80115
}81
- self.images.get(&icon).and_then(Option::as_ref)116
+ self.sized.get(&key)?.as_ref()82117
}83118
84119
/// Elements for the pointer at `pos`, in output-local coordinates.@@ -114,44 +149,158 @@ impl Cursor {
114149
.map(Draw::Surface)115150
.collect()116151
}117
- CursorImageStatus::Named(icon) => match self.image(*icon) {118
- Some((buffer, hotspot)) => MemoryRenderBufferRenderElement::from_buffer(119
- renderer,120
- at(*hotspot).to_f64(),121
- buffer,122
- None,123
- None,124
- None,125
- Kind::Cursor,126
- )127
- .map(|e| vec![Draw::Memory(e)])128
- .unwrap_or_default(),129
- None => vec![Draw::Border(SolidColorRenderElement::new(130
- Id::new(),131
- Rectangle::new(at(Point::default()), (8, 8).into()),132
- CommitCounter::default(),133
- [1.0, 1.0, 1.0, 1.0],134
- Kind::Cursor,135
- ))],136
- },152
+ CursorImageStatus::Named(icon) => {153
+ let tip = to_physical((pos.x, pos.y), scale);154
+ match self.sized(renderer, *icon, scale) {155
+ Some(image) => vec![Draw::Cursor(ImageElement::new(image, tip))],156
+ None => {157
+ let side = (8.0 * scale).round() as i32;158
+ vec![Draw::Border(SolidColorRenderElement::new(159
+ Id::new(),160
+ Rectangle::new(tip, (side, side).into()),161
+ CommitCounter::default(),162
+ [1.0, 1.0, 1.0, 1.0],163
+ Kind::Cursor,164
+ ))]165
+ }166
+ }167
+ }137168
}138169
}139170
}140171
141
-fn load_image(theme: &xcursor::CursorTheme, name: &str, size: u32) -> Image {172
+fn load_file(theme: &xcursor::CursorTheme, name: &str) -> Option<Vec<xcursor::parser::Image>> {142173
let path = theme.load_icon(name)?;143174
let bytes = std::fs::read(path).ok()?;144
- let images = xcursor::parser::parse_xcursor(&bytes)?;145
- let image = images.iter().min_by_key(|i| i.size.abs_diff(size))?;146
- let buffer = MemoryRenderBuffer::from_slice(147
- &image.pixels_rgba,148
- Fourcc::Argb8888,149
- (image.width as i32, image.height as i32),150
- 1,151
- Transform::Normal,152
- None,175
+ xcursor::parser::parse_xcursor(&bytes)176
+}177
+178
+/// Uploads `image` stretched by `ratio`. The theme stores premultiplied BGRA179
+/// bytes, which is what Argb8888 means on little endian.180
+fn prepare(181
+ renderer: &mut VulkanRenderer,182
+ image: &xcursor::parser::Image,183
+ ratio: f64,184
+) -> Option<Prepared> {185
+ let (w, h) = (image.width as i32, image.height as i32);186
+ let size = (187
+ ((f64::from(w) * ratio).round() as i32).max(1),188
+ ((f64::from(h) * ratio).round() as i32).max(1),153189
);154
- Some((buffer, (image.xhot as i32, image.yhot as i32).into()))190
+ let pixels = if (w, h) == size {191
+ image.pixels_rgba.clone()192
+ } else {193
+ stretch(&image.pixels_rgba, (w, h), size)194
+ };195
+ let texture = renderer196
+ .import_memory(&pixels, Fourcc::Argb8888, size.into(), false)197
+ .ok()?;198
+ Some(Prepared {199
+ id: Id::new(),200
+ pixels: MemoryBuffer::from_slice(&pixels, Fourcc::Argb8888, size),201
+ texture,202
+ hotspot: (203
+ (f64::from(image.xhot) * ratio).round() as i32,204
+ (f64::from(image.yhot) * ratio).round() as i32,205
+ )206
+ .into(),207
+ })208
+}209
+210
+/// Bilinear resampling of 4 byte pixels, which averages evenly when halving.211
+fn stretch(src: &[u8], (sw, sh): (i32, i32), (dw, dh): (i32, i32)) -> Vec<u8> {212
+ let (fx, fy) = (f64::from(sw) / f64::from(dw), f64::from(sh) / f64::from(dh));213
+ let at = |x: i32, y: i32, c: usize| {214
+ let (x, y) = (x.clamp(0, sw - 1), y.clamp(0, sh - 1));215
+ f64::from(src[(y * sw + x) as usize * 4 + c])216
+ };217
+ let mut out = Vec::with_capacity((dw * dh * 4) as usize);218
+ for y in 0..dh {219
+ let sy = (f64::from(y) + 0.5) * fy - 0.5;220
+ let y0 = sy.floor();221
+ let ty = sy - y0;222
+ for x in 0..dw {223
+ let sx = (f64::from(x) + 0.5) * fx - 0.5;224
+ let x0 = sx.floor();225
+ let tx = sx - x0;226
+ for c in 0..4 {227
+ let (x0, y0) = (x0 as i32, y0 as i32);228
+ let top = at(x0, y0, c) * (1.0 - tx) + at(x0 + 1, y0, c) * tx;229
+ let bottom = at(x0, y0 + 1, c) * (1.0 - tx) + at(x0 + 1, y0 + 1, c) * tx;230
+ out.push((top * (1.0 - ty) + bottom * ty).round() as u8);231
+ }232
+ }233
+ }234
+ out235
+}236
+237
+/// A theme image at whole pixels, drawn unscaled so the cursor plane can238
+/// take it.239
+pub struct ImageElement {240
+ id: Id,241
+ pixels: MemoryBuffer,242
+ texture: VulkanTexture,243
+ geometry: Rectangle<i32, Physical>,244
+}245
+246
+impl ImageElement {247
+ fn new(image: &Prepared, tip: Point<i32, Physical>) -> Self {248
+ let size = image.pixels.size();249
+ Self {250
+ id: image.id.clone(),251
+ pixels: image.pixels.clone(),252
+ texture: image.texture.clone(),253
+ geometry: Rectangle::new(tip - image.hotspot, (size.w, size.h).into()),254
+ }255
+ }256
+}257
+258
+impl Element for ImageElement {259
+ fn id(&self) -> &Id {260
+ &self.id261
+ }262
+263
+ fn current_commit(&self) -> CommitCounter {264
+ CommitCounter::default()265
+ }266
+267
+ fn src(&self) -> Rectangle<f64, Buffer> {268
+ Rectangle::from_size(self.pixels.size().to_f64())269
+ }270
+271
+ fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {272
+ self.geometry273
+ }274
+275
+ fn kind(&self) -> Kind {276
+ Kind::Cursor277
+ }278
+}279
+280
+impl RenderElement<VulkanRenderer> for ImageElement {281
+ fn draw(282
+ &self,283
+ frame: &mut VulkanFrame<'_, '_>,284
+ src: Rectangle<f64, Buffer>,285
+ dst: Rectangle<i32, Physical>,286
+ damage: &[Rectangle<i32, Physical>],287
+ opaque_regions: &[Rectangle<i32, Physical>],288
+ _cache: Option<&UserDataMap>,289
+ ) -> Result<(), monolith_render_vk::Error> {290
+ frame.render_texture_from_to(291
+ &self.texture,292
+ src,293
+ dst,294
+ damage,295
+ opaque_regions,296
+ Transform::Normal,297
+ 1.0,298
+ )299
+ }300
+301
+ fn underlying_storage(&self, _renderer: &mut VulkanRenderer) -> Option<UnderlyingStorage<'_>> {302
+ Some(UnderlyingStorage::Memory(&self.pixels))303
+ }155304
}156305
157306
/// The surface a drag carries under the pointer. `offset` accumulates the@@ -16,6 +16,7 @@ use smithay::xwayland::xwm::ResizeEdge;
1616
impl State {1717
pub fn pointer_to(&mut self, pos: Point<f64, Logical>, time: InputTime) {1818
self.input.pointer_pos = pos;19
+ self.follow_pointer_scale();1920
self.activity();2021
if drag_to(self, pos) {2122
return;@@ -102,6 +102,15 @@ impl State {
102102
}103103
}104104
105
+ /// Retells the cursor and drag icon when the pointer crosses to an output.106
+ pub fn follow_pointer_scale(&mut self) {107
+ let name = self.pointer_output().map(|o| o.name());108
+ if self.input.pointer_output != name {109
+ self.input.pointer_output = name;110
+ self.send_pointer_scales();111
+ }112
+ }113
+105114
pub fn pointer_output(&self) -> Option<Output> {106115
self.wm107116
.space@@ -5,7 +5,6 @@ use crate::wm::{LastFrame, Wm};
55
use monolith_config::Decoration;66
use monolith_layout::{OutputId, Rect, WindowId};77
use monolith_render_vk::{Clip, Effect, VulkanFrame, VulkanRenderer, VulkanTexture};8
-use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;98
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};109
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;1110
use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;@@ -27,7 +26,7 @@ smithay::backend::renderer::element::render_elements! {
2726
Cropped=CropRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>,2827
Texture=PlacedTexture,2928
Border=SolidColorRenderElement,30
- Memory=MemoryRenderBufferRenderElement<VulkanRenderer>,29
+ Cursor=crate::cursor::ImageElement,3130
Rescaled=RescaleRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>,3231
Clipped=ClipElement,3332
}@@ -63,6 +63,8 @@ pub struct Input {
6363
pub dnd_icon: Option<crate::cursor::DndIcon>,6464
/// The edges of the window border under the pointer, for the resize cursor.6565
pub hover: Option<crate::input::Edges>,66
+ /// The output the pointer was last on, whose scale cursor and drag icon surfaces follow.67
+ pub pointer_output: Option<String>,6668
}6769
6870
/// What needs doing after this batch of events.@@ -195,6 +197,7 @@ impl State {
195197
cursor_status: CursorImageStatus::default_named(),196198
dnd_icon: None,197199
hover: None,200
+ pointer_output: None,198201
},199202
dirty: Dirty {200203
redraw: true,