Merge branch 'Smithay:master' into master

This commit is contained in:
Victor Timofei 2022-01-16 13:42:21 +02:00 committed by GitHub
commit 2fb1f93160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 3911 additions and 399 deletions

View File

@ -105,7 +105,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: update
args: -p libdbus-sys -p calloop:0.6.1
args: -p libdbus-sys
- name: Check
uses: actions-rs/cargo@v1
with:
@ -131,6 +131,7 @@ jobs:
- backend_session_logind
- backend_session_libseat
- backend_x11
- desktop
- renderer_gl
- wayland_frontend
- xwayland

View File

@ -4,6 +4,7 @@ on:
push:
branches:
- master
pull_request:
jobs:
doc:
@ -33,12 +34,13 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps --features "test_all_features" -p smithay -p calloop:0.9.1 -p dbus -p drm -p gbm -p input -p nix:0.22.0 -p udev -p slog -p wayland-server -p wayland-commons:0.29.1 -p wayland-protocols:0.29.1 -p winit -p x11rb
args: --no-deps --features "test_all_features" -p smithay -p calloop -p dbus -p drm -p gbm -p input -p nix -p udev -p slog -p wayland-server -p wayland-commons -p wayland-protocols -p winit -p x11rb
- name: Setup index
run: cp ./doc_index.html ./target/doc/index.html
- name: Deploy
if: ${{ github.event_name == 'push' }}
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -31,6 +31,9 @@
- `X11Surface::buffer` now additionally returns the age of the buffer
- `X11Surface` now has an explicit `submit` function
- `X11Surface` is now multi-window capable.
- `Renderer::clear` now expects a second argument to optionally only clear parts of the buffer/surface
- `Transform::transform_size` now takes a `Size` instead of two `u32`
- `Gles2Renderer` now automatically flips the `render` result to account for OpenGLs coordinate system
### Additions
@ -45,6 +48,7 @@
- Add support for the zxdg-foreign-v2 protocol.
- Support for `xdg_wm_base` protocol version 3
- Added the option to initialize the dmabuf global with a client filter
- `wayland::output::Output` now has user data attached to it and more functions to query its properties
#### Backends

View File

@ -29,6 +29,7 @@ drm = { version = "0.5.0", optional = true }
drm-ffi = { version = "0.2.0", optional = true }
gbm = { version = "0.7.0", optional = true, default-features = false, features = ["drm-support"] }
input = { version = "0.7", default-features = false, features=["libinput_1_14"], optional = true }
indexmap = { version = "1.7", optional = true }
lazy_static = "1"
libc = "0.2.103"
libseat= { version = "0.1.1", optional = true }
@ -39,14 +40,14 @@ rand = "0.8.4"
slog = "2"
slog-stdlog = { version = "4", optional = true }
tempfile = { version = "3.0", optional = true }
thiserror = "1.0.7"
thiserror = "1.0.25"
udev = { version = "0.6", optional = true }
wayland-commons = { version = "0.29.0", optional = true }
wayland-egl = { version = "0.29.0", optional = true }
wayland-protocols = { version = "0.29.0", features = ["unstable_protocols", "staging_protocols", "server"], optional = true }
wayland-server = { version = "0.29.0", optional = true }
wayland-sys = { version = "0.29.0", optional = true }
winit = { version = "0.25.0", optional = true }
winit = { version = "0.26", optional = true }
x11rb = { version = "0.9.0", optional = true }
xkbcommon = "0.4.0"
scan_fmt = { version = "0.2.3", default-features = false }
@ -59,7 +60,7 @@ gl_generator = { version = "0.14", optional = true }
pkg-config = { version = "0.3.17", optional = true }
[features]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "desktop", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"]
backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"]
backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"]
backend_drm = ["drm", "drm-ffi"]
@ -71,6 +72,7 @@ backend_udev = ["udev", "input/udev"]
backend_session_logind = ["dbus", "backend_session", "pkg-config"]
backend_session_elogind = ["backend_session_logind"]
backend_session_libseat = ["backend_session", "libseat"]
desktop = ["indexmap", "wayland_frontend"]
renderer_gl = ["gl_generator", "backend_egl"]
use_system_lib = ["wayland_frontend", "wayland-sys", "wayland-server/use_system_lib"]
wayland_frontend = ["wayland-server", "wayland-commons", "wayland-protocols", "tempfile"]

View File

@ -178,6 +178,7 @@ where
buffer_scale,
output_scale as f64,
Transform::Normal, /* TODO */
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
1.0,
) {
result = Err(err.into());
@ -355,6 +356,7 @@ where
_ => unreachable!(),
},
Rectangle::from_loc_and_size((offset_x, 0.0), (22.0 * output_scale, 35.0 * output_scale)),
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
Transform::Normal,
1.0,
)

View File

@ -24,7 +24,10 @@ pub fn render_layers_and_windows(
output_scale: f32,
logger: &Logger,
) -> Result<(), SwapBuffersError> {
frame.clear([0.8, 0.8, 0.9, 1.0])?;
frame.clear(
[0.8, 0.8, 0.9, 1.0],
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
)?;
for layer in [Layer::Background, Layer::Bottom] {
draw_layers(

View File

@ -50,7 +50,7 @@ use smithay::{
},
utils::{
signaling::{Linkable, SignalToken, Signaler},
Logical, Point,
Logical, Point, Rectangle,
},
wayland::{
output::{Mode, PhysicalProperties},
@ -743,53 +743,19 @@ fn render_surface(
// and draw to our buffer
match renderer
.render(
mode.size,
Transform::Flipped180, // Scanout is rotated
|renderer, frame| {
render_layers_and_windows(
renderer,
frame,
window_map,
output_geometry,
output_scale,
logger,
)?;
.render(mode.size, Transform::Normal, |renderer, frame| {
render_layers_and_windows(renderer, frame, window_map, output_geometry, output_scale, logger)?;
// set cursor
if output_geometry.to_f64().contains(pointer_location) {
let (ptr_x, ptr_y) = pointer_location.into();
let relative_ptr_location =
Point::<i32, Logical>::from((ptr_x as i32, ptr_y as i32)) - output_geometry.loc;
// draw the dnd icon if applicable
{
if let Some(ref wl_surface) = dnd_icon.as_ref() {
if wl_surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
wl_surface,
relative_ptr_location,
output_scale,
logger,
)?;
}
}
}
// draw the cursor as relevant
{
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *cursor_status {
reset = !surface.as_ref().is_alive();
}
if reset {
*cursor_status = CursorImageStatus::Default;
}
if let CursorImageStatus::Image(ref wl_surface) = *cursor_status {
draw_cursor(
// set cursor
if output_geometry.to_f64().contains(pointer_location) {
let (ptr_x, ptr_y) = pointer_location.into();
let relative_ptr_location =
Point::<i32, Logical>::from((ptr_x as i32, ptr_y as i32)) - output_geometry.loc;
// draw the dnd icon if applicable
{
if let Some(ref wl_surface) = dnd_icon.as_ref() {
if wl_surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
wl_surface,
@ -797,38 +763,62 @@ fn render_surface(
output_scale,
logger,
)?;
} else {
frame.render_texture_at(
pointer_image,
relative_ptr_location
.to_f64()
.to_physical(output_scale as f64)
.to_i32_round(),
1,
output_scale as f64,
Transform::Normal,
1.0,
)?;
}
}
#[cfg(feature = "debug")]
{
draw_fps(
renderer,
frame,
fps_texture,
output_scale as f64,
surface.fps.avg().round() as u32,
)?;
surface.fps.tick();
}
}
Ok(())
},
)
// draw the cursor as relevant
{
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *cursor_status {
reset = !surface.as_ref().is_alive();
}
if reset {
*cursor_status = CursorImageStatus::Default;
}
if let CursorImageStatus::Image(ref wl_surface) = *cursor_status {
draw_cursor(
renderer,
frame,
wl_surface,
relative_ptr_location,
output_scale,
logger,
)?;
} else {
frame.render_texture_at(
pointer_image,
relative_ptr_location
.to_f64()
.to_physical(output_scale as f64)
.to_i32_round(),
1,
output_scale as f64,
Transform::Normal,
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
1.0,
)?;
}
}
#[cfg(feature = "debug")]
{
draw_fps(
renderer,
frame,
fps_texture,
output_scale as f64,
surface.fps.avg().round() as u32,
)?;
surface.fps.tick();
}
}
Ok(())
})
.map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x)
.map_err(Into::<SwapBuffersError>::into)
@ -873,7 +863,10 @@ fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) ->
renderer
.render((1, 1).into(), Transform::Normal, |_, frame| {
frame
.clear([0.8, 0.8, 0.9, 1.0])
.clear(
[0.8, 0.8, 0.9, 1.0],
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
)
.map_err(Into::<SwapBuffersError>::into)
})
.map_err(Into::<SwapBuffersError>::into)

View File

@ -9,6 +9,7 @@ use smithay::{
};
use smithay::{
backend::{
renderer::{Renderer, Transform},
winit::{self, WinitEvent},
SwapBuffersError,
},
@ -46,39 +47,39 @@ pub fn run_winit(log: Logger) {
let mut event_loop = EventLoop::try_new().unwrap();
let display = Rc::new(RefCell::new(Display::new()));
let (renderer, mut winit) = match winit::init(log.clone()) {
let (backend, mut winit) = match winit::init(log.clone()) {
Ok(ret) => ret,
Err(err) => {
slog::crit!(log, "Failed to initialize Winit backend: {}", err);
return;
}
};
let renderer = Rc::new(RefCell::new(renderer));
let backend = Rc::new(RefCell::new(backend));
#[cfg(feature = "egl")]
if renderer
if backend
.borrow_mut()
.renderer()
.bind_wl_display(&display.borrow())
.is_ok()
{
info!(log, "EGL hardware-acceleration enabled");
let dmabuf_formats = renderer
let dmabuf_formats = backend
.borrow_mut()
.renderer()
.dmabuf_formats()
.cloned()
.collect::<Vec<_>>();
let renderer = renderer.clone();
let backend = backend.clone();
init_dmabuf_global(
&mut *display.borrow_mut(),
dmabuf_formats,
move |buffer, _| renderer.borrow_mut().renderer().import_dmabuf(buffer).is_ok(),
move |buffer, _| backend.borrow_mut().renderer().import_dmabuf(buffer).is_ok(),
log.clone(),
);
};
let size = renderer.borrow().window_size().physical_size;
let size = backend.borrow().window_size().physical_size;
/*
* Initialize the globals
@ -87,7 +88,7 @@ pub fn run_winit(log: Logger) {
let data = WinitData {
#[cfg(feature = "debug")]
fps_texture: import_bitmap(
renderer.borrow_mut().renderer(),
backend.borrow_mut().renderer(),
&image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
.decode()
.unwrap()
@ -153,7 +154,7 @@ pub fn run_winit(log: Logger) {
// drawing logic
{
let mut renderer = renderer.borrow_mut();
let mut backend = backend.borrow_mut();
// This is safe to do as with winit we are guaranteed to have exactly one output
let (output_geometry, output_scale) = state
.output_map
@ -162,82 +163,96 @@ pub fn run_winit(log: Logger) {
.map(|output| (output.geometry(), output.scale()))
.unwrap();
let result = renderer
.render(|renderer, frame| {
render_layers_and_windows(
renderer,
frame,
&*state.window_map.borrow(),
output_geometry,
output_scale,
&log,
)?;
let (x, y) = state.pointer_location.into();
// draw the dnd icon if any
{
let guard = state.dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() {
draw_dnd_icon(
let result = backend
.bind()
.and_then(|_| {
backend
.renderer()
.render(
output_geometry
.size
.to_f64()
.to_physical(output_scale as f64)
.to_i32_round(),
Transform::Flipped180,
|renderer, frame| {
render_layers_and_windows(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
&*state.window_map.borrow(),
output_geometry,
output_scale,
&log,
)?;
}
}
}
// draw the cursor as relevant
{
let mut guard = state.cursor_status.lock().unwrap();
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *guard {
reset = !surface.as_ref().is_alive();
}
if reset {
*guard = CursorImageStatus::Default;
}
// draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard {
cursor_visible = false;
draw_cursor(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
output_scale,
&log,
)?;
} else {
cursor_visible = true;
}
}
let (x, y) = state.pointer_location.into();
#[cfg(feature = "debug")]
{
let fps = state.backend_data.fps.avg().round() as u32;
// draw the dnd icon if any
{
let guard = state.dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
output_scale,
&log,
)?;
}
}
}
// draw the cursor as relevant
{
let mut guard = state.cursor_status.lock().unwrap();
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *guard {
reset = !surface.as_ref().is_alive();
}
if reset {
*guard = CursorImageStatus::Default;
}
draw_fps(
renderer,
frame,
&state.backend_data.fps_texture,
output_scale as f64,
fps,
)?;
}
// draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard {
cursor_visible = false;
draw_cursor(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
output_scale,
&log,
)?;
} else {
cursor_visible = true;
}
}
Ok(())
#[cfg(feature = "debug")]
{
let fps = state.backend_data.fps.avg().round() as u32;
draw_fps(
renderer,
frame,
&state.backend_data.fps_texture,
output_scale as f64,
fps,
)?;
}
Ok(())
},
)
.map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x)
})
.map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x);
.and_then(|_| backend.submit(None, 1.0));
renderer.window().set_cursor_visible(cursor_visible);
backend.window().set_cursor_visible(cursor_visible);
if let Err(SwapBuffersError::ContextLost(err)) = result {
error!(log, "Critical Rendering Error: {}", err);

View File

@ -239,56 +239,23 @@ pub fn run_x11(log: Logger) {
// drawing logic
match renderer
// X11 scanout for a Dmabuf is upside down
// TODO: Address this issue in renderer.
.render(
backend_data.mode.size,
Transform::Flipped180,
|renderer, frame| {
render_layers_and_windows(
renderer,
frame,
&*window_map,
output_geometry,
output_scale,
&log,
)?;
.render(backend_data.mode.size, Transform::Normal, |renderer, frame| {
render_layers_and_windows(
renderer,
frame,
&*window_map,
output_geometry,
output_scale,
&log,
)?;
// draw the dnd icon if any
{
let guard = dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
output_scale,
&log,
)?;
}
}
}
// draw the cursor as relevant
{
let mut guard = cursor_status.lock().unwrap();
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *guard {
reset = !surface.as_ref().is_alive();
}
if reset {
*guard = CursorImageStatus::Default;
}
// draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard {
cursor_visible = false;
draw_cursor(
// draw the dnd icon if any
{
let guard = dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
surface,
@ -296,21 +263,49 @@ pub fn run_x11(log: Logger) {
output_scale,
&log,
)?;
} else {
cursor_visible = true;
}
}
}
#[cfg(feature = "debug")]
{
use crate::drawing::draw_fps;
// draw the cursor as relevant
{
let mut guard = cursor_status.lock().unwrap();
// reset the cursor if the surface is no longer alive
let mut reset = false;
draw_fps(renderer, frame, fps_texture, output_scale as f64, fps)?;
if let CursorImageStatus::Image(ref surface) = *guard {
reset = !surface.as_ref().is_alive();
}
Ok(())
},
)
if reset {
*guard = CursorImageStatus::Default;
}
// draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard {
cursor_visible = false;
draw_cursor(
renderer,
frame,
surface,
(x as i32, y as i32).into(),
output_scale,
&log,
)?;
} else {
cursor_visible = true;
}
}
#[cfg(feature = "debug")]
{
use crate::drawing::draw_fps;
draw_fps(renderer, frame, fps_texture, output_scale as f64, fps)?;
}
Ok(())
})
.map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x)
.map_err(Into::<SwapBuffersError>::into)

View File

@ -35,6 +35,8 @@ fn gl_generate() {
"EGL_KHR_image_base",
"EGL_EXT_image_dma_buf_import",
"EGL_EXT_image_dma_buf_import_modifiers",
"EGL_EXT_buffer_age",
"EGL_EXT_swap_buffers_with_damage",
],
)
.write_bindings(gl_generator::GlobalGenerator, &mut file)

View File

@ -196,6 +196,15 @@ where
/// Remove all internally cached buffers to e.g. reset age values
pub fn reset_buffers(&mut self) {
self.slots = Default::default();
for slot in &mut self.slots {
if let Some(internal_slot) = Arc::get_mut(slot) {
*internal_slot = InternalSlot {
buffer: internal_slot.buffer.take(),
..Default::default()
};
} else {
*slot = Default::default();
}
}
}
}

View File

@ -238,6 +238,11 @@ where
flip.map_err(Error::DrmError)
}
/// Reset the underlying buffers
pub fn reset_buffers(&mut self) {
self.swapchain.reset_buffers()
}
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
pub fn crtc(&self) -> crtc::Handle {
self.drm.crtc()

View File

@ -1,12 +1,21 @@
//! EGL context related structs
use std::collections::HashSet;
use std::os::raw::c_int;
use std::sync::atomic::Ordering;
use std::{
collections::HashSet,
os::raw::c_int,
sync::{atomic::Ordering, Arc},
};
use super::{ffi, wrap_egl_call, Error, MakeCurrentError};
use crate::backend::allocator::Format as DrmFormat;
use crate::backend::egl::display::{EGLDisplay, PixelFormat};
use crate::backend::egl::EGLSurface;
use crate::{
backend::{
allocator::Format as DrmFormat,
egl::{
display::{EGLDisplay, PixelFormat},
EGLSurface,
},
},
utils::user_data::UserDataMap,
};
use slog::{info, o, trace};
@ -17,6 +26,7 @@ pub struct EGLContext {
pub(crate) display: EGLDisplay,
config_id: ffi::egl::types::EGLConfig,
pixel_format: Option<PixelFormat>,
user_data: Arc<UserDataMap>,
}
// EGLContexts can be moved between threads safely
unsafe impl Send for EGLContext {}
@ -162,6 +172,11 @@ impl EGLContext {
display: display.clone(),
config_id,
pixel_format,
user_data: if let Some(shared) = shared {
shared.user_data.clone()
} else {
Arc::new(UserDataMap::default())
},
})
}
@ -241,6 +256,21 @@ impl EGLContext {
pub fn dmabuf_texture_formats(&self) -> &HashSet<DrmFormat> {
&self.display.dmabuf_import_formats
}
/// Retrieve user_data associated with this context
///
/// *Note:* UserData is shared between shared context, if constructed with
/// [`new_shared`](EGLContext::new_shared) or [`new_shared_with_config`](EGLContext::new_shared_with_config).
pub fn user_data(&self) -> &UserDataMap {
&*self.user_data
}
/// Get a raw handle to the underlying context.
///
/// The pointer will become invalid, when this struct is destroyed.
pub fn get_context_handle(&self) -> ffi::egl::types::EGLContext {
self.context
}
}
impl Drop for EGLContext {

View File

@ -173,7 +173,9 @@ impl EGLDevice {
}
/// Returns the pointer to the raw [`EGLDevice`].
pub fn inner(&self) -> *const c_void {
///
/// The pointer will become invalid, when this struct is destroyed.
pub fn get_device_handle(&self) -> *const c_void {
self.inner
}
}

View File

@ -40,7 +40,7 @@ extern "system" fn egl_debug_log(
}
egl::DEBUG_MSG_WARN_KHR => slog::warn!(logger, "[EGL] {}: {}", command_utf8, message_utf8),
egl::DEBUG_MSG_INFO_KHR => slog::info!(logger, "[EGL] {}: {}", command_utf8, message_utf8),
_ => slog::debug!(logger, "[EGL] {}: {}", command_utf8, message_utf8),
_ => {}
};
});
}

View File

@ -1,6 +1,7 @@
//! Type safe native types for safe context/surface creation
use super::{display::EGLDisplayHandle, ffi, wrap_egl_call, EGLDevice, SwapBuffersError};
use crate::utils::{Physical, Rectangle};
#[cfg(feature = "backend_winit")]
use std::os::raw::c_int;
use std::os::raw::c_void;
@ -246,9 +247,19 @@ pub unsafe trait EGLNativeSurface: Send + Sync {
&self,
display: &Arc<EGLDisplayHandle>,
surface: ffi::egl::types::EGLSurface,
damage: Option<&mut [Rectangle<i32, Physical>]>,
) -> Result<(), SwapBuffersError> {
wrap_egl_call(|| unsafe {
ffi::egl::SwapBuffers(***display, surface as *const _);
if let Some(damage) = damage {
ffi::egl::SwapBuffersWithDamageEXT(
***display,
surface as *const _,
damage.as_mut_ptr() as *mut _,
damage.len() as i32,
);
} else {
ffi::egl::SwapBuffers(***display, surface as *const _);
}
})
.map_err(SwapBuffersError::EGLSwapBuffers)
}

View File

@ -12,6 +12,7 @@ use crate::backend::egl::{
native::EGLNativeSurface,
EGLError, SwapBuffersError,
};
use crate::utils::{Physical, Rectangle};
use slog::{debug, o};
@ -79,12 +80,30 @@ impl EGLSurface {
})
}
/// Returns the buffer age of the underlying back buffer
pub fn buffer_age(&self) -> i32 {
let surface = self.surface.load(Ordering::SeqCst);
let mut age = 0;
unsafe {
ffi::egl::QuerySurface(
**self.display,
surface as *const _,
ffi::egl::BUFFER_AGE_EXT as i32,
&mut age as *mut _,
);
}
age
}
/// Swaps buffers at the end of a frame.
pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
pub fn swap_buffers(
&self,
damage: Option<&mut [Rectangle<i32, Physical>]>,
) -> ::std::result::Result<(), SwapBuffersError> {
let surface = self.surface.load(Ordering::SeqCst);
let result = if !surface.is_null() {
self.native.swap_buffers(&self.display, surface)
self.native.swap_buffers(&self.display, surface, damage)
} else {
Err(SwapBuffersError::EGLSwapBuffers(EGLError::BadSurface))
};
@ -153,6 +172,16 @@ impl EGLSurface {
pub fn resize(&self, width: i32, height: i32, dx: i32, dy: i32) -> bool {
self.native.resize(width, height, dx, dy)
}
/// Get a raw handle to the underlying surface
///
/// *Note*: The surface might get dynamically recreated during swap-buffers
/// causing the pointer to become invalid.
///
/// The pointer will become invalid, when this struct is destroyed.
pub fn get_surface_handle(&self) -> ffi::egl::types::EGLSurface {
self.surface.load(Ordering::SeqCst)
}
}
impl Drop for EGLSurface {

View File

@ -5,10 +5,7 @@ use std::ffi::CStr;
use std::fmt;
use std::ptr;
use std::rc::Rc;
use std::sync::{
atomic::{AtomicUsize, Ordering},
mpsc::{channel, Receiver, Sender},
};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::{collections::HashSet, os::raw::c_char};
use cgmath::{prelude::*, Matrix3, Vector2, Vector3};
@ -44,23 +41,27 @@ pub mod ffi {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
// This static is used to assign every created Renderer a unique ID (until is overflows...).
//
// This id is used to differenciate between user_data of different renderers, because one
// cannot assume, that resources between two renderers are (and even can be) shared.
static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone)]
struct Gles2Program {
struct Gles2TexProgram {
program: ffi::types::GLuint,
uniform_tex: ffi::types::GLint,
uniform_matrix: ffi::types::GLint,
uniform_invert_y: ffi::types::GLint,
uniform_alpha: ffi::types::GLint,
attrib_vert: ffi::types::GLint,
attrib_position: ffi::types::GLint,
attrib_tex_coords: ffi::types::GLint,
}
#[derive(Debug, Clone)]
struct Gles2SolidProgram {
program: ffi::types::GLuint,
uniform_matrix: ffi::types::GLint,
uniform_color: ffi::types::GLint,
attrib_vert: ffi::types::GLint,
attrib_position: ffi::types::GLint,
}
/// A handle to a GLES2 texture
#[derive(Debug, Clone)]
pub struct Gles2Texture(Rc<Gles2TextureInternal>);
@ -158,45 +159,27 @@ struct Gles2Buffer {
_dmabuf: Dmabuf,
}
#[cfg(feature = "wayland_frontend")]
struct BufferEntry {
id: u32,
buffer: wl_buffer::WlBuffer,
}
#[cfg(feature = "wayland_frontend")]
impl std::hash::Hash for BufferEntry {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
self.id.hash(hasher);
}
}
#[cfg(feature = "wayland_frontend")]
impl PartialEq for BufferEntry {
fn eq(&self, other: &Self) -> bool {
self.buffer == other.buffer
}
}
#[cfg(feature = "wayland_frontend")]
impl Eq for BufferEntry {}
/// A renderer utilizing OpenGL ES 2
pub struct Gles2Renderer {
id: usize,
buffers: Vec<WeakGles2Buffer>,
target_buffer: Option<Gles2Buffer>,
target_surface: Option<Rc<EGLSurface>>,
extensions: Vec<String>,
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT],
solid_program: Gles2SolidProgram,
#[cfg(feature = "wayland_frontend")]
dmabuf_cache: std::collections::HashMap<WeakDmabuf, Gles2Texture>,
egl: EGLContext,
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
egl_reader: Option<EGLBufferReader>,
vbos: [ffi::types::GLuint; 2],
gl: ffi::Gles2,
destruction_callback: Receiver<CleanupResource>,
// This field is only accessed if the image or wayland_frontend features are active
#[allow(dead_code)]
destruction_callback_sender: Sender<CleanupResource>,
min_filter: TextureFilter,
max_filter: TextureFilter,
logger_ptr: Option<*mut ::slog::Logger>,
logger: ::slog::Logger,
_not_send: *mut (),
@ -205,15 +188,25 @@ pub struct Gles2Renderer {
/// Handle to the currently rendered frame during [`Gles2Renderer::render`](Renderer::render)
pub struct Gles2Frame {
current_projection: Matrix3<f32>,
transform: Transform,
gl: ffi::Gles2,
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT],
solid_program: Gles2SolidProgram,
vbos: [ffi::types::GLuint; 2],
size: Size<i32, Physical>,
min_filter: TextureFilter,
max_filter: TextureFilter,
}
impl fmt::Debug for Gles2Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Gles2Frame")
.field("current_projection", &self.current_projection)
.field("programs", &self.programs)
.field("tex_programs", &self.tex_programs)
.field("solid_program", &self.solid_program)
.field("size", &self.size)
.field("min_filter", &self.min_filter)
.field("max_filter", &self.max_filter)
.finish_non_exhaustive()
}
}
@ -221,14 +214,16 @@ impl fmt::Debug for Gles2Frame {
impl fmt::Debug for Gles2Renderer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Gles2Renderer")
.field("id", &self.id)
.field("buffers", &self.buffers)
.field("target_buffer", &self.target_buffer)
.field("target_surface", &self.target_surface)
.field("extensions", &self.extensions)
.field("programs", &self.programs)
.field("tex_programs", &self.tex_programs)
.field("solid_program", &self.solid_program)
// ffi::Gles2 does not implement Debug
.field("egl", &self.egl)
.field("min_filter", &self.min_filter)
.field("max_filter", &self.max_filter)
.field("logger", &self.logger)
.finish()
}
@ -382,9 +377,10 @@ unsafe fn link_program(
Ok(program)
}
unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result<Gles2Program, Gles2Error> {
unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result<Gles2TexProgram, Gles2Error> {
let program = link_program(gl, shaders::VERTEX_SHADER, frag)?;
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated");
let tex_coords = CStr::from_bytes_with_nul(b"tex_coords\0").expect("NULL terminated");
let tex = CStr::from_bytes_with_nul(b"tex\0").expect("NULL terminated");
@ -392,17 +388,35 @@ unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result<Gles2Pr
let invert_y = CStr::from_bytes_with_nul(b"invert_y\0").expect("NULL terminated");
let alpha = CStr::from_bytes_with_nul(b"alpha\0").expect("NULL terminated");
Ok(Gles2Program {
Ok(Gles2TexProgram {
program,
uniform_tex: gl.GetUniformLocation(program, tex.as_ptr() as *const ffi::types::GLchar),
uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
uniform_invert_y: gl.GetUniformLocation(program, invert_y.as_ptr() as *const ffi::types::GLchar),
uniform_alpha: gl.GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
attrib_position: gl.GetAttribLocation(program, position.as_ptr() as *const ffi::types::GLchar),
attrib_tex_coords: gl.GetAttribLocation(program, tex_coords.as_ptr() as *const ffi::types::GLchar),
})
}
unsafe fn solid_program(gl: &ffi::Gles2) -> Result<Gles2SolidProgram, Gles2Error> {
let program = link_program(gl, shaders::VERTEX_SHADER_SOLID, shaders::FRAGMENT_SHADER_SOLID)?;
let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
let color = CStr::from_bytes_with_nul(b"color\0").expect("NULL terminated");
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated");
Ok(Gles2SolidProgram {
program,
uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
uniform_color: gl.GetUniformLocation(program, color.as_ptr() as *const ffi::types::GLchar),
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
attrib_position: gl.GetAttribLocation(program, position.as_ptr() as *const ffi::types::GLchar),
})
}
impl Gles2Renderer {
/// Creates a new OpenGL ES 2 renderer from a given [`EGLContext`](crate::backend::egl::EGLBuffer).
///
@ -473,6 +487,16 @@ impl Gles2Renderer {
if gl_version < version::GLES_3_0 && !exts.iter().any(|ext| ext == "GL_EXT_unpack_subimage") {
return Err(Gles2Error::GLExtensionNotSupported(&["GL_EXT_unpack_subimage"]));
}
// required for instanced damage rendering
if gl_version < version::GLES_3_0
&& !(exts.iter().any(|ext| ext == "GL_EXT_instanced_arrays")
&& exts.iter().any(|ext| ext == "GL_EXT_draw_instanced"))
{
return Err(Gles2Error::GLExtensionNotSupported(&[
"GL_EXT_instanced_arrays",
"GL_EXT_draw_instanced",
]));
}
let logger = if exts.iter().any(|ext| ext == "GL_KHR_debug") {
let logger = Box::into_raw(Box::new(log.clone()));
@ -487,21 +511,33 @@ impl Gles2Renderer {
(gl, exts, logger)
};
let programs = [
let tex_programs = [
texture_program(&gl, shaders::FRAGMENT_SHADER_ABGR)?,
texture_program(&gl, shaders::FRAGMENT_SHADER_XBGR)?,
texture_program(&gl, shaders::FRAGMENT_SHADER_EXTERNAL)?,
];
let solid_program = solid_program(&gl)?;
let mut vbos = [0; 2];
gl.GenBuffers(2, vbos.as_mut_ptr());
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
gl.BufferData(
ffi::ARRAY_BUFFER,
(std::mem::size_of::<ffi::types::GLfloat>() * VERTS.len()) as isize,
VERTS.as_ptr() as *const _,
ffi::STATIC_DRAW,
);
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
let (tx, rx) = channel();
let mut renderer = Gles2Renderer {
id: RENDERER_COUNTER.fetch_add(1, Ordering::SeqCst),
let renderer = Gles2Renderer {
gl,
egl: context,
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
egl_reader: None,
extensions: exts,
programs,
tex_programs,
solid_program,
target_buffer: None,
target_surface: None,
buffers: Vec::new(),
@ -509,12 +545,13 @@ impl Gles2Renderer {
dmabuf_cache: std::collections::HashMap::new(),
destruction_callback: rx,
destruction_callback_sender: tx,
vbos,
min_filter: TextureFilter::Nearest,
max_filter: TextureFilter::Linear,
logger_ptr,
logger: log,
_not_send: std::ptr::null_mut(),
};
renderer.downscale_filter(TextureFilter::Nearest)?;
renderer.upscale_filter(TextureFilter::Linear)?;
renderer.egl.unbind()?;
Ok(renderer)
}
@ -607,7 +644,6 @@ impl ImportShm for Gles2Renderer {
unsafe {
self.gl.BindTexture(ffi::TEXTURE_2D, texture.0.texture);
self.gl
.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32);
self.gl
@ -966,9 +1002,11 @@ impl Drop for Gles2Renderer {
unsafe {
if self.egl.make_current().is_ok() {
self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);
for program in &self.programs {
for program in &self.tex_programs {
self.gl.DeleteProgram(program.program);
}
self.gl.DeleteProgram(self.solid_program.program);
self.gl.DeleteBuffers(2, self.vbos.as_ptr());
if self.extensions.iter().any(|ext| ext == "GL_KHR_debug") {
self.gl.Disable(ffi::DEBUG_OUTPUT);
@ -987,12 +1025,25 @@ impl Drop for Gles2Renderer {
}
impl Gles2Renderer {
/// Get access to the underlying [`EGLContext`].
///
/// *Note*: Modifying the context state, might result in rendering issues.
/// The context state is considerd an implementation detail
/// and no guarantee is made about what can or cannot be changed.
/// To make sure a certain modification does not interfere with
/// the renderer's behaviour, check the source.
pub fn egl_context(&self) -> &EGLContext {
&self.egl
}
/// Run custom code in the GL context owned by this renderer.
///
/// *Note*: Any changes to the GL state should be restored at the end of this function.
/// Otherwise this can lead to rendering errors while using functions of this renderer.
/// Relying on any state set by the renderer may break on any smithay update as the
/// details about how this renderer works are considered an implementation detail.
/// The OpenGL state of the renderer is considered an implementation detail
/// and no guarantee is made about what can or cannot be changed,
/// as such you should reset everything you change back to its previous value
/// or check the source code of the version of Smithay you are using to ensure
/// your changes don't interfere with the renderer's behavior.
/// Doing otherwise can lead to rendering errors while using other functions of this renderer.
pub fn with_context<F, R>(&mut self, func: F) -> Result<R, Gles2Error>
where
F: FnOnce(&mut Self, &ffi::Gles2) -> R,
@ -1009,31 +1060,11 @@ impl Renderer for Gles2Renderer {
type Frame = Gles2Frame;
fn downscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> {
self.make_current()?;
unsafe {
self.gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MIN_FILTER,
match filter {
TextureFilter::Nearest => ffi::NEAREST as i32,
TextureFilter::Linear => ffi::LINEAR as i32,
},
);
}
self.min_filter = filter;
Ok(())
}
fn upscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> {
self.make_current()?;
unsafe {
self.gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MAG_FILTER,
match filter {
TextureFilter::Nearest => ffi::NEAREST as i32,
TextureFilter::Linear => ffi::LINEAR as i32,
},
);
}
self.max_filter = filter;
Ok(())
}
@ -1077,11 +1108,20 @@ impl Renderer for Gles2Renderer {
renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0]));
renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1]));
// We account for OpenGLs coordinate system here
let flip180 = Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0);
let mut frame = Gles2Frame {
gl: self.gl.clone(),
programs: self.programs.clone(),
tex_programs: self.tex_programs.clone(),
solid_program: self.solid_program.clone(),
// output transformation passed in by the user
current_projection: transform.matrix() * renderer,
current_projection: flip180 * transform.matrix() * renderer,
transform,
vbos: self.vbos,
size,
min_filter: self.min_filter,
max_filter: self.max_filter,
};
let result = rendering(self, &mut frame);
@ -1121,10 +1161,85 @@ impl Frame for Gles2Frame {
type Error = Gles2Error;
type TextureId = Gles2Texture;
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> {
fn clear(&mut self, color: [f32; 4], at: &[Rectangle<i32, Physical>]) -> Result<(), Self::Error> {
if at.is_empty() {
return Ok(());
}
let mut mat = Matrix3::<f32>::identity();
mat = mat * Matrix3::from_translation(Vector2::new(0.0, 0.0));
mat = mat * Matrix3::from_nonuniform_scale(self.size.w as f32, self.size.h as f32);
mat = self.current_projection * mat;
let damage = at
.iter()
.map(|rect| {
[
rect.loc.x as f32 / self.size.w as f32,
rect.loc.y as f32 / self.size.h as f32,
rect.size.w as f32 / self.size.w as f32,
rect.size.h as f32 / self.size.h as f32,
]
})
.flatten()
.collect::<Vec<ffi::types::GLfloat>>();
unsafe {
self.gl.ClearColor(color[0], color[1], color[2], color[3]);
self.gl.Clear(ffi::COLOR_BUFFER_BIT);
self.gl.UseProgram(self.solid_program.program);
self.gl.Uniform4f(
self.solid_program.uniform_color,
color[0],
color[1],
color[2],
color[3],
);
self.gl
.UniformMatrix3fv(self.solid_program.uniform_matrix, 1, ffi::FALSE, mat.as_ptr());
self.gl
.EnableVertexAttribArray(self.solid_program.attrib_vert as u32);
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]);
self.gl.VertexAttribPointer(
self.solid_program.attrib_vert as u32,
2,
ffi::FLOAT,
ffi::FALSE,
0,
std::ptr::null(),
);
self.gl
.EnableVertexAttribArray(self.solid_program.attrib_position as u32);
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]);
self.gl.BufferData(
ffi::ARRAY_BUFFER,
(std::mem::size_of::<ffi::types::GLfloat>() * damage.len()) as isize,
damage.as_ptr() as *const _,
ffi::STREAM_DRAW,
);
self.gl.VertexAttribPointer(
self.solid_program.attrib_position as u32,
4,
ffi::FLOAT,
ffi::FALSE,
0,
std::ptr::null(),
);
self.gl
.VertexAttribDivisor(self.solid_program.attrib_vert as u32, 0);
self.gl
.VertexAttribDivisor(self.solid_program.attrib_position as u32, 1);
self.gl
.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, at.len() as i32);
self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
self.gl
.DisableVertexAttribArray(self.solid_program.attrib_vert as u32);
self.gl
.DisableVertexAttribArray(self.solid_program.attrib_position as u32);
}
Ok(())
@ -1135,6 +1250,7 @@ impl Frame for Gles2Frame {
texture: &Self::TextureId,
src: Rectangle<i32, Buffer>,
dest: Rectangle<f64, Physical>,
damage: &[Rectangle<i32, Physical>],
transform: Transform,
alpha: f32,
) -> Result<(), Self::Error> {
@ -1158,7 +1274,7 @@ impl Frame for Gles2Frame {
let texture_mat = Matrix3::from_nonuniform_scale(tex_size.w as f32, tex_size.h as f32)
.invert()
.unwrap();
let verts = [
let tex_verts = [
(texture_mat * Vector3::new((src.loc.x + src.size.w) as f32, src.loc.y as f32, 0.0)).truncate(), // top-right
(texture_mat * Vector3::new(src.loc.x as f32, src.loc.y as f32, 0.0)).truncate(), // top-left
(texture_mat
@ -1170,7 +1286,32 @@ impl Frame for Gles2Frame {
.truncate(), // bottom-right
(texture_mat * Vector3::new(src.loc.x as f32, (src.loc.y + src.size.h) as f32, 0.0)).truncate(), // bottom-left
];
self.render_texture(texture, mat, verts, alpha)
let damage = damage
.iter()
.map(|rect| {
let rect = rect.to_f64();
let rect_constrained_loc = rect
.loc
.constrain(Rectangle::from_extemities((0f64, 0f64), dest.size.to_point()));
let rect_clamped_size = rect.size.clamp((0f64, 0f64), dest.size);
[
(rect_constrained_loc.x / dest.size.w) as f32,
(rect_constrained_loc.y / dest.size.h) as f32,
(rect_clamped_size.w / dest.size.w) as f32,
(rect_clamped_size.h / dest.size.h) as f32,
]
})
.flatten()
.collect::<Vec<_>>();
self.render_texture(texture, mat, Some(&damage), tex_verts, alpha)
}
fn transformation(&self) -> Transform {
self.transform
}
}
@ -1181,6 +1322,7 @@ impl Gles2Frame {
&mut self,
tex: &Gles2Texture,
mut matrix: Matrix3<f32>,
instances: Option<&[ffi::types::GLfloat]>,
tex_coords: [Vector2<f32>; 4],
alpha: f32,
) -> Result<(), Gles2Error> {
@ -1197,35 +1339,43 @@ impl Gles2Frame {
unsafe {
self.gl.ActiveTexture(ffi::TEXTURE0);
self.gl.BindTexture(target, tex.0.texture);
self.gl
.TexParameteri(target, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
self.gl.UseProgram(self.programs[tex.0.texture_kind].program);
self.gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MIN_FILTER,
match self.min_filter {
TextureFilter::Nearest => ffi::NEAREST as i32,
TextureFilter::Linear => ffi::LINEAR as i32,
},
);
self.gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MAG_FILTER,
match self.max_filter {
TextureFilter::Nearest => ffi::NEAREST as i32,
TextureFilter::Linear => ffi::LINEAR as i32,
},
);
self.gl.UseProgram(self.tex_programs[tex.0.texture_kind].program);
self.gl
.Uniform1i(self.programs[tex.0.texture_kind].uniform_tex, 0);
.Uniform1i(self.tex_programs[tex.0.texture_kind].uniform_tex, 0);
self.gl.UniformMatrix3fv(
self.programs[tex.0.texture_kind].uniform_matrix,
self.tex_programs[tex.0.texture_kind].uniform_matrix,
1,
ffi::FALSE,
matrix.as_ptr(),
);
self.gl.Uniform1i(
self.programs[tex.0.texture_kind].uniform_invert_y,
self.tex_programs[tex.0.texture_kind].uniform_invert_y,
if tex.0.y_inverted { 1 } else { 0 },
);
self.gl
.Uniform1f(self.programs[tex.0.texture_kind].uniform_alpha, alpha);
.Uniform1f(self.tex_programs[tex.0.texture_kind].uniform_alpha, alpha);
self.gl
.EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32);
self.gl.VertexAttribPointer(
self.programs[tex.0.texture_kind].attrib_position as u32,
2,
ffi::FLOAT,
ffi::FALSE,
0,
VERTS.as_ptr() as *const _,
);
self.gl.VertexAttribPointer(
self.programs[tex.0.texture_kind].attrib_tex_coords as u32,
self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32,
2,
ffi::FLOAT,
ffi::FALSE,
@ -1234,20 +1384,62 @@ impl Gles2Frame {
);
self.gl
.EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32);
self.gl
.EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32);
.EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32);
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]);
self.gl.VertexAttribPointer(
self.solid_program.attrib_vert as u32,
2,
ffi::FLOAT,
ffi::FALSE,
0,
std::ptr::null(),
);
self.gl.DrawArrays(ffi::TRIANGLE_STRIP, 0, 4);
let damage = instances.unwrap_or(&[0.0, 0.0, 1.0, 1.0]);
self.gl
.EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32);
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]);
self.gl.BufferData(
ffi::ARRAY_BUFFER,
(std::mem::size_of::<ffi::types::GLfloat>() * damage.len()) as isize,
damage.as_ptr() as *const _,
ffi::STREAM_DRAW,
);
let count = (damage.len() / 4) as i32;
self.gl.VertexAttribPointer(
self.tex_programs[tex.0.texture_kind].attrib_position as u32,
4,
ffi::FLOAT,
ffi::FALSE,
0,
std::ptr::null(),
);
self.gl
.DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32);
.VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_vert as u32, 0);
self.gl
.DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32);
.VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32, 0);
self.gl
.VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_position as u32, 1);
self.gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, count);
self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
self.gl.BindTexture(target, 0);
self.gl
.DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32);
self.gl
.DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32);
self.gl
.DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32);
}
Ok(())
}
/// Projection matrix for this frame
pub fn projection(&self) -> &[f32; 9] {
self.current_projection.as_ref()
}
}

View File

@ -2,29 +2,48 @@
* OpenGL Shaders
*/
pub const VERTEX_SHADER: &str = r#"
#version 100
uniform mat3 matrix;
uniform bool invert_y;
attribute vec2 position;
attribute vec2 vert;
attribute vec2 tex_coords;
attribute vec4 position;
varying vec2 v_tex_coords;
mat2 scale(vec2 scale_vec){
return mat2(
scale_vec.x, 0.0,
0.0, scale_vec.y
);
}
void main() {
gl_Position = vec4(matrix * vec3(position, 1.0), 1.0);
if (invert_y) {
v_tex_coords = vec2(tex_coords.x, 1.0 - tex_coords.y);
} else {
v_tex_coords = tex_coords;
}
}"#;
vec2 transform_translation = position.xy;
vec2 transform_scale = position.zw;
v_tex_coords = (vec3((tex_coords * scale(transform_scale)) + transform_translation, 1.0)).xy;
gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0);
}
"#;
pub const FRAGMENT_COUNT: usize = 3;
pub const FRAGMENT_SHADER_ABGR: &str = r#"
#version 100
precision mediump float;
uniform sampler2D tex;
uniform float alpha;
varying vec2 v_tex_coords;
void main() {
gl_FragColor = texture2D(tex, v_tex_coords) * alpha;
}
@ -32,10 +51,12 @@ void main() {
pub const FRAGMENT_SHADER_XBGR: &str = r#"
#version 100
precision mediump float;
uniform sampler2D tex;
uniform float alpha;
varying vec2 v_tex_coords;
void main() {
gl_FragColor = vec4(texture2D(tex, v_tex_coords).rgb, 1.0) * alpha;
}
@ -44,11 +65,45 @@ void main() {
pub const FRAGMENT_SHADER_EXTERNAL: &str = r#"
#version 100
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES tex;
uniform float alpha;
varying vec2 v_tex_coords;
void main() {
gl_FragColor = texture2D(tex, v_tex_coords) * alpha;
}
"#;
pub const VERTEX_SHADER_SOLID: &str = r#"
#version 100
uniform mat3 matrix;
attribute vec2 vert;
attribute vec4 position;
mat2 scale(vec2 scale_vec){
return mat2(
scale_vec.x, 0.0,
0.0, scale_vec.y
);
}
void main() {
vec2 transform_translation = position.xy;
vec2 transform_scale = position.zw;
gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0);
}
"#;
pub const FRAGMENT_SHADER_SOLID: &str = r#"
#version 100
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
"#;

View File

@ -10,7 +10,7 @@
use std::collections::HashSet;
use std::error::Error;
use crate::utils::{Buffer, Physical, Point, Rectangle, Size};
use crate::utils::{Buffer, Coordinate, Physical, Point, Rectangle, Size};
#[cfg(feature = "wayland_frontend")]
use crate::wayland::compositor::SurfaceData;
@ -32,6 +32,9 @@ use crate::backend::egl::{
Error as EglError,
};
#[cfg(feature = "wayland_frontend")]
pub mod utils;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
/// Possible transformations to two-dimensional planes
pub enum Transform {
@ -94,17 +97,47 @@ impl Transform {
}
/// Transformed size after applying this transformation.
pub fn transform_size(&self, width: u32, height: u32) -> (u32, u32) {
pub fn transform_size<N: Coordinate, Kind>(&self, size: Size<N, Kind>) -> Size<N, Kind> {
if *self == Transform::_90
|| *self == Transform::_270
|| *self == Transform::Flipped90
|| *self == Transform::Flipped270
{
(height, width)
(size.h, size.w).into()
} else {
(width, height)
size
}
}
/// Transforms a rectangle inside an area of a given size by applying this transformation
pub fn transform_rect_in<N: Coordinate, Kind>(
&self,
rect: Rectangle<N, Kind>,
area: &Size<N, Kind>,
) -> Rectangle<N, Kind> {
let size = self.transform_size(rect.size);
let loc = match *self {
Transform::Normal => rect.loc,
Transform::_90 => (area.h - rect.loc.y - rect.size.h, rect.loc.x).into(),
Transform::_180 => (
area.w - rect.loc.x - rect.size.w,
area.h - rect.loc.y - rect.size.h,
)
.into(),
Transform::_270 => (rect.loc.y, area.w - rect.loc.x - rect.size.w).into(),
Transform::Flipped => (area.w - rect.loc.x - rect.size.w, rect.loc.y).into(),
Transform::Flipped90 => (rect.loc.y, rect.loc.x).into(),
Transform::Flipped180 => (rect.loc.x, area.h - rect.loc.y - rect.size.h).into(),
Transform::Flipped270 => (
area.h - rect.loc.y - rect.size.h,
area.w - rect.loc.x - rect.size.w,
)
.into(),
};
Rectangle::from_loc_and_size(loc, size)
}
}
#[cfg(feature = "wayland_frontend")]
@ -171,11 +204,12 @@ pub trait Frame {
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>;
fn clear(&mut self, color: [f32; 4], at: &[Rectangle<i32, Physical>]) -> Result<(), Self::Error>;
/// Render a texture to the current target as a flat 2d-plane at a given
/// position and applying the given transformation with the given alpha value.
/// (Meaning `src_transform` should match the orientation of surface being rendered).
#[allow(clippy::too_many_arguments)]
fn render_texture_at(
&mut self,
texture: &Self::TextureId,
@ -183,6 +217,7 @@ pub trait Frame {
texture_scale: i32,
output_scale: f64,
src_transform: Transform,
damage: &[Rectangle<i32, Physical>],
alpha: f32,
) -> Result<(), Self::Error> {
self.render_texture_from_to(
@ -196,6 +231,7 @@ pub trait Frame {
.to_f64()
.to_physical(output_scale),
),
damage,
src_transform,
alpha,
)
@ -209,9 +245,13 @@ pub trait Frame {
texture: &Self::TextureId,
src: Rectangle<i32, Buffer>,
dst: Rectangle<f64, Physical>,
damage: &[Rectangle<i32, Physical>],
src_transform: Transform,
alpha: f32,
) -> Result<(), Self::Error>;
/// Output transformation that is applied to this frame
fn transformation(&self) -> Transform;
}
/// Abstraction of commonly used rendering operations for compositors.
@ -250,7 +290,7 @@ pub trait Renderer {
pub trait ImportShm: Renderer {
/// Import a given shm-based buffer into the renderer (see [`buffer_type`]).
///
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
/// or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
@ -317,7 +357,7 @@ pub trait ImportEgl: Renderer {
/// Import a given wl_drm-based buffer into the renderer (see [`buffer_type`]).
///
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
/// or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
@ -365,7 +405,7 @@ pub trait ImportDma: Renderer {
/// Import a given raw dmabuf into the renderer.
///
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
/// or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
@ -388,7 +428,7 @@ pub trait ImportDma: Renderer {
pub trait ImportAll: Renderer {
/// Import a given buffer into the renderer.
///
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
/// or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
@ -520,3 +560,102 @@ pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<Size<i32, Physi
crate::wayland::shm::with_buffer_contents(buffer, |_, data| (data.width, data.height).into()).ok()
}
#[cfg(test)]
mod tests {
use super::Transform;
use crate::utils::{Logical, Rectangle, Size};
#[test]
fn transform_rect_ident() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 90));
let transform = Transform::Normal;
assert_eq!(rect, transform.transform_rect_in(rect, &size))
}
#[test]
fn transform_rect_90() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 90));
let transform = Transform::_90;
assert_eq!(
Rectangle::from_loc_and_size((30, 10), (40, 30)),
transform.transform_rect_in(rect, &size)
)
}
#[test]
fn transform_rect_180() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 90));
let transform = Transform::_180;
assert_eq!(
Rectangle::from_loc_and_size((30, 30), (30, 40)),
transform.transform_rect_in(rect, &size)
)
}
#[test]
fn transform_rect_270() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 90));
let transform = Transform::_270;
assert_eq!(
Rectangle::from_loc_and_size((20, 30), (40, 30)),
transform.transform_rect_in(rect, &size)
)
}
#[test]
fn transform_rect_f() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 90));
let transform = Transform::Flipped;
assert_eq!(
Rectangle::from_loc_and_size((30, 20), (30, 40)),
transform.transform_rect_in(rect, &size)
)
}
#[test]
fn transform_rect_f90() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 80));
let transform = Transform::Flipped90;
assert_eq!(
Rectangle::from_loc_and_size((20, 10), (40, 30)),
transform.transform_rect_in(rect, &size)
)
}
#[test]
fn transform_rect_f180() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 90));
let transform = Transform::Flipped180;
assert_eq!(
Rectangle::from_loc_and_size((10, 30), (30, 40)),
transform.transform_rect_in(rect, &size)
)
}
#[test]
fn transform_rect_f270() {
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
let size = Size::from((70, 90));
let transform = Transform::Flipped270;
assert_eq!(
Rectangle::from_loc_and_size((30, 30), (40, 30)),
transform.transform_rect_in(rect, &size)
)
}
}

View File

@ -0,0 +1,223 @@
//! Utility module for helpers around drawing [`WlSurface`]s with [`Renderer`]s.
use crate::{
backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture},
utils::{Logical, Physical, Point, Rectangle, Size},
wayland::compositor::{
is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState,
SurfaceAttributes, TraversalAction,
},
};
use std::cell::RefCell;
#[cfg(feature = "desktop")]
use std::collections::HashSet;
use wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface};
#[derive(Default)]
pub(crate) struct SurfaceState {
pub(crate) buffer_dimensions: Option<Size<i32, Physical>>,
pub(crate) buffer_scale: i32,
pub(crate) buffer: Option<WlBuffer>,
pub(crate) texture: Option<Box<dyn std::any::Any + 'static>>,
#[cfg(feature = "desktop")]
pub(crate) damage_seen: HashSet<crate::desktop::space::SpaceOutputHash>,
}
impl SurfaceState {
pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) {
match attrs.buffer.take() {
Some(BufferAssignment::NewBuffer { buffer, .. }) => {
// new contents
self.buffer_dimensions = buffer_dimensions(&buffer);
self.buffer_scale = attrs.buffer_scale;
if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) {
if &old_buffer != self.buffer.as_ref().unwrap() {
old_buffer.release();
}
}
self.texture = None;
#[cfg(feature = "desktop")]
self.damage_seen.clear();
}
Some(BufferAssignment::Removed) => {
// remove the contents
self.buffer_dimensions = None;
if let Some(buffer) = self.buffer.take() {
buffer.release();
};
self.texture = None;
#[cfg(feature = "desktop")]
self.damage_seen.clear();
}
None => {}
}
}
}
/// Handler to let smithay take over buffer management.
///
/// Needs to be called first on the commit-callback of
/// [`crate::wayland::compositor::compositor_init`].
///
/// Consumes the buffer of [`SurfaceAttributes`], the buffer will
/// not be accessible anymore, but [`draw_surface_tree`] and other
/// `draw_*` helpers of the [desktop module](`crate::desktop`) will
/// become usable for surfaces handled this way.
pub fn on_commit_buffer_handler(surface: &WlSurface) {
if !is_sync_subsurface(surface) {
with_surface_tree_upward(
surface,
(),
|_, _, _| TraversalAction::DoChildren(()),
|_surf, states, _| {
states
.data_map
.insert_if_missing(|| RefCell::new(SurfaceState::default()));
let mut data = states
.data_map
.get::<RefCell<SurfaceState>>()
.unwrap()
.borrow_mut();
data.update_buffer(&mut *states.cached_state.current::<SurfaceAttributes>());
},
|_, _, _| true,
);
}
}
/// Draws a surface and its subsurfaces using a given [`Renderer`] and [`Frame`].
///
/// - `scale` needs to be equivalent to the fractional scale the rendered result should have.
/// - `location` is the position the surface should be drawn at.
/// - `damage` is the set of regions of the surface that should be drawn.
///
/// Note: This element will render nothing, if you are not using
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
/// to let smithay handle buffer management.
pub fn draw_surface_tree<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
surface: &WlSurface,
scale: f64,
location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error>
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture + 'static,
{
let mut result = Ok(());
let damage = damage
.iter()
.map(|geo| geo.to_f64().to_physical(scale).to_i32_up())
.collect::<Vec<_>>();
with_surface_tree_upward(
surface,
location,
|_surface, states, location| {
let mut location = *location;
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
let mut data = data.borrow_mut();
let attributes = states.cached_state.current::<SurfaceAttributes>();
// Import a new buffer if necessary
if data.texture.is_none() {
if let Some(buffer) = data.buffer.as_ref() {
let damage = attributes
.damage
.iter()
.map(|dmg| match dmg {
Damage::Buffer(rect) => *rect,
// TODO also apply transformations
Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale),
})
.collect::<Vec<_>>();
match renderer.import_buffer(buffer, Some(states), &damage) {
Some(Ok(m)) => {
data.texture = Some(Box::new(m));
}
Some(Err(err)) => {
slog::warn!(log, "Error loading buffer: {}", err);
}
None => {
slog::error!(log, "Unknown buffer format for: {:?}", buffer);
}
}
}
}
// Now, should we be drawn ?
if data.texture.is_some() {
// if yes, also process the children
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
location += current.location;
}
TraversalAction::DoChildren(location)
} else {
// we are not displayed, so our children are neither
TraversalAction::SkipChildren
}
} else {
// we are not displayed, so our children are neither
TraversalAction::SkipChildren
}
},
|_surface, states, location| {
let mut location = *location;
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
let mut data = data.borrow_mut();
let buffer_scale = data.buffer_scale;
let buffer_dimensions = data.buffer_dimensions;
let attributes = states.cached_state.current::<SurfaceAttributes>();
if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::<T>()) {
// we need to re-extract the subsurface offset, as the previous closure
// only passes it to our children
let mut surface_offset = (0, 0).into();
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
surface_offset = current.location;
location += current.location;
}
let rect = Rectangle::<i32, Physical>::from_loc_and_size(
surface_offset.to_f64().to_physical(scale).to_i32_round(),
buffer_dimensions
.unwrap_or_default()
.to_logical(buffer_scale)
.to_f64()
.to_physical(scale)
.to_i32_round(),
);
let new_damage = damage
.iter()
.cloned()
.flat_map(|geo| geo.intersection(rect))
.map(|mut geo| {
geo.loc -= rect.loc;
geo
})
.collect::<Vec<_>>();
// TODO: Take wp_viewporter into account
if let Err(err) = frame.render_texture_at(
texture,
location.to_f64().to_physical(scale).to_i32_round(),
buffer_scale,
scale,
attributes.buffer_transform.into(),
&new_damage,
1.0,
) {
result = Err(err);
}
}
}
},
|_, _, _| true,
);
result
}

View File

@ -10,7 +10,8 @@
//! you want on the initialization of the backend. These functions will provide you
//! with two objects:
//!
//! - a [`WinitGraphicsBackend`], which can give you an implementation of a [`Renderer`]
//! - a [`WinitGraphicsBackend`], which can give you an implementation of a
//! [`Renderer`](crate::backend::renderer::Renderer)
//! (or even [`Gles2Renderer`]) through its `renderer` method in addition to further
//! functionality to access and manage the created winit-window.
//! - a [`WinitEventLoop`], which dispatches some [`WinitEvent`] from the host graphics server.
@ -27,11 +28,11 @@ use crate::{
},
input::InputEvent,
renderer::{
gles2::{Gles2Error, Gles2Frame, Gles2Renderer},
Bind, Renderer, Transform, Unbind,
gles2::{Gles2Error, Gles2Renderer},
Bind,
},
},
utils::{Logical, Physical, Size},
utils::{Logical, Physical, Rectangle, Size},
};
use std::{cell::RefCell, rc::Rc, time::Instant};
use wayland_egl as wegl;
@ -81,7 +82,7 @@ impl WindowSize {
}
}
/// Window with an active EGL Context created by `winit`. Implements the [`Renderer`] trait
/// Window with an active EGL Context created by `winit`.
#[derive(Debug)]
pub struct WinitGraphicsBackend {
renderer: Gles2Renderer,
@ -90,6 +91,7 @@ pub struct WinitGraphicsBackend {
egl: Rc<EGLSurface>,
window: Rc<WinitWindow>,
size: Rc<RefCell<WindowSize>>,
damage_tracking: bool,
resize_notification: Rc<Cell<Option<Size<i32, Physical>>>>,
}
@ -111,7 +113,8 @@ pub struct WinitEventLoop {
is_x11: bool,
}
/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait and a corresponding
/// Create a new [`WinitGraphicsBackend`], which implements the
/// [`Renderer`](crate::backend::renderer::Renderer) trait and a corresponding
/// [`WinitEventLoop`].
pub fn init<L>(logger: L) -> Result<(WinitGraphicsBackend, WinitEventLoop), Error>
where
@ -126,7 +129,8 @@ where
)
}
/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait, from a given [`WindowBuilder`]
/// Create a new [`WinitGraphicsBackend`], which implements the
/// [`Renderer`](crate::backend::renderer::Renderer) trait, from a given [`WindowBuilder`]
/// struct and a corresponding [`WinitEventLoop`].
pub fn init_from_builder<L>(
builder: WindowBuilder,
@ -147,7 +151,8 @@ where
)
}
/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait, from a given [`WindowBuilder`]
/// Create a new [`WinitGraphicsBackend`], which implements the
/// [`Renderer`](crate::backend::renderer::Renderer) trait, from a given [`WindowBuilder`]
/// struct, as well as given [`GlAttributes`] for further customization of the rendering pipeline and a
/// corresponding [`WinitEventLoop`].
pub fn init_from_builder_with_gl_attr<L>(
@ -220,6 +225,10 @@ where
let egl = Rc::new(surface);
let renderer = unsafe { Gles2Renderer::new(context, log.clone())? };
let resize_notification = Rc::new(Cell::new(None));
let damage_tracking = display.extensions.iter().any(|ext| ext == "EGL_EXT_buffer_age")
&& display.extensions.iter().any(|ext| {
ext == "EGL_KHR_swap_buffers_with_damage" || ext == "EGL_EXT_swap_buffers_with_damage"
});
Ok((
WinitGraphicsBackend {
@ -227,6 +236,7 @@ where
_display: display,
egl,
renderer,
damage_tracking,
size: size.clone(),
resize_notification: resize_notification.clone(),
},
@ -281,27 +291,59 @@ impl WinitGraphicsBackend {
&mut self.renderer
}
/// Shortcut to `Renderer::render` with the current window dimensions
/// and this window set as the rendering target.
pub fn render<F, R>(&mut self, rendering: F) -> Result<R, crate::backend::SwapBuffersError>
where
F: FnOnce(&mut Gles2Renderer, &mut Gles2Frame) -> R,
{
/// Bind the underlying window to the underlying renderer
pub fn bind(&mut self) -> Result<(), crate::backend::SwapBuffersError> {
// Were we told to resize?
if let Some(size) = self.resize_notification.take() {
self.egl.resize(size.w, size.h, 0, 0);
}
let size = {
let size = self.size.borrow();
size.physical_size
};
self.renderer.bind(self.egl.clone())?;
let result = self.renderer.render(size, Transform::Normal, rendering)?;
self.egl.swap_buffers()?;
self.renderer.unbind()?;
Ok(result)
Ok(())
}
/// Retrieve the buffer age of the current backbuffer of the window
pub fn buffer_age(&self) -> usize {
if self.damage_tracking {
self.egl.buffer_age() as usize
} else {
0
}
}
/// Submits the back buffer to the window by swapping, requires the window to be previously bound (see [`WinitGraphicsBackend::bind`]).
pub fn submit(
&mut self,
damage: Option<&[Rectangle<i32, Logical>]>,
scale: f64,
) -> Result<(), crate::backend::SwapBuffersError> {
let mut damage = match damage {
Some(damage) if self.damage_tracking && !damage.is_empty() => {
let size = self
.size
.borrow()
.physical_size
.to_f64()
.to_logical(scale)
.to_i32_round::<i32>();
let damage = damage
.iter()
.map(|rect| {
Rectangle::from_loc_and_size(
(rect.loc.x, size.h - rect.loc.y - rect.size.h),
rect.size,
)
.to_f64()
.to_physical(scale)
.to_i32_round::<i32>()
})
.collect::<Vec<_>>();
Some(damage)
}
_ => None,
};
self.egl.swap_buffers(damage.as_deref_mut())?;
Ok(())
}
}

View File

@ -72,7 +72,7 @@ impl X11Surface {
///
/// You may bind this buffer to a renderer to render.
/// This function will return the same buffer until [`submit`](Self::submit) is called
/// or [`reset_buffers`](Self::reset_buffer) is used to reset the buffers.
/// or [`reset_buffers`](Self::reset_buffers) is used to reset the buffers.
pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> {
if let Some(new_size) = self.resize.try_iter().last() {
self.resize(new_size);

553
src/desktop/layer.rs Normal file
View File

@ -0,0 +1,553 @@
use crate::{
backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture},
desktop::{utils::*, PopupManager, Space},
utils::{user_data::UserDataMap, Logical, Point, Rectangle},
wayland::{
compositor::with_states,
output::{Inner as OutputInner, Output},
shell::wlr_layer::{
Anchor, ExclusiveZone, KeyboardInteractivity, Layer as WlrLayer, LayerSurface as WlrLayerSurface,
LayerSurfaceCachedState,
},
},
};
use indexmap::IndexSet;
use wayland_server::protocol::wl_surface::WlSurface;
use std::{
cell::{RefCell, RefMut},
hash::{Hash, Hasher},
rc::Rc,
sync::{Arc, Mutex, Weak},
};
crate::utils::ids::id_gen!(next_layer_id, LAYER_ID, LAYER_IDS);
/// Map of [`LayerSurface`]s on an [`Output`]
#[derive(Debug)]
pub struct LayerMap {
layers: IndexSet<LayerSurface>,
output: Weak<(Mutex<OutputInner>, wayland_server::UserDataMap)>,
zone: Rectangle<i32, Logical>,
}
/// Retrieve a [`LayerMap`] for a given [`Output`].
///
/// If none existed before a new empty [`LayerMap`] is attached
/// to the output and returned on subsequent calls.
///
/// Note: This function internally uses a [`RefCell`] per
/// [`Output`] as exposed by its return type. Therefor
/// trying to hold on to multiple references of a [`LayerMap`]
/// of the same output using this function *will* result in a panic.
pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> {
let userdata = o.user_data();
let weak_output = Arc::downgrade(&o.inner);
userdata.insert_if_missing(|| {
RefCell::new(LayerMap {
layers: IndexSet::new(),
output: weak_output,
zone: Rectangle::from_loc_and_size(
(0, 0),
o.current_mode()
.map(|mode| mode.size.to_logical(o.current_scale()))
.unwrap_or_else(|| (0, 0).into()),
),
})
});
userdata.get::<RefCell<LayerMap>>().unwrap().borrow_mut()
}
#[derive(Debug, thiserror::Error)]
pub enum LayerError {
#[error("Layer is already mapped to a different map")]
AlreadyMapped,
}
impl LayerMap {
/// Map a [`LayerSurface`] to this [`LayerMap`].
pub fn map_layer(&mut self, layer: &LayerSurface) -> Result<(), LayerError> {
if !self.layers.contains(layer) {
if layer
.0
.userdata
.get::<LayerUserdata>()
.map(|s| s.borrow().is_some())
.unwrap_or(false)
{
return Err(LayerError::AlreadyMapped);
}
self.layers.insert(layer.clone());
self.arrange();
}
Ok(())
}
/// Remove a [`LayerSurface`] from this [`LayerMap`].
pub fn unmap_layer(&mut self, layer: &LayerSurface) {
if self.layers.shift_remove(layer) {
let _ = layer.user_data().get::<LayerUserdata>().take();
self.arrange();
}
}
/// Return the area of this output, that is not exclusive to any [`LayerSurface`]s.
pub fn non_exclusive_zone(&self) -> Rectangle<i32, Logical> {
self.zone
}
/// Returns the geometry of a given mapped [`LayerSurface`].
///
/// If the surface was not previously mapped onto this layer map,
/// this function return `None`.
pub fn layer_geometry(&self, layer: &LayerSurface) -> Option<Rectangle<i32, Logical>> {
if !self.layers.contains(layer) {
return None;
}
let mut bbox = layer.bbox_with_popups();
let state = layer_state(layer);
bbox.loc += state.location;
Some(bbox)
}
/// Returns a [`LayerSurface`] under a given point and on a given layer, if any.
pub fn layer_under<P: Into<Point<f64, Logical>>>(
&self,
layer: WlrLayer,
point: P,
) -> Option<&LayerSurface> {
let point = point.into();
self.layers_on(layer).rev().find(|l| {
let bbox = self.layer_geometry(l).unwrap();
bbox.to_f64().contains(point)
})
}
/// Iterator over all [`LayerSurface`]s currently mapped.
pub fn layers(&self) -> impl DoubleEndedIterator<Item = &LayerSurface> {
self.layers.iter()
}
/// Iterator over all [`LayerSurface`]s currently mapped on a given layer.
pub fn layers_on(&self, layer: WlrLayer) -> impl DoubleEndedIterator<Item = &LayerSurface> {
self.layers
.iter()
.filter(move |l| l.layer().map(|l| l == layer).unwrap_or(false))
}
/// Returns the [`LayerSurface`] matching a given [`WlSurface`], if any.
pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<&LayerSurface> {
if !surface.as_ref().is_alive() {
return None;
}
self.layers
.iter()
.find(|w| w.get_surface().map(|x| x == surface).unwrap_or(false))
}
/// Force re-arranging the layer surfaces, e.g. when the output size changes.
///
/// Note: Mapping or unmapping a layer surface will automatically cause a re-arrangement.
pub fn arrange(&mut self) {
if let Some(output) = self.output() {
let output_rect = Rectangle::from_loc_and_size(
(0, 0),
output
.current_mode()
.map(|mode| mode.size.to_logical(output.current_scale()))
.unwrap_or_else(|| (0, 0).into()),
);
let mut zone = output_rect;
slog::debug!(
crate::slog_or_fallback(None),
"Arranging layers into {:?}",
output_rect.size
);
for layer in self.layers.iter() {
let surface = if let Some(surface) = layer.get_surface() {
surface
} else {
continue;
};
let data = with_states(surface, |states| {
*states.cached_state.current::<LayerSurfaceCachedState>()
})
.unwrap();
let source = match data.exclusive_zone {
ExclusiveZone::Neutral | ExclusiveZone::Exclusive(_) => &zone,
ExclusiveZone::DontCare => &output_rect,
};
let mut size = data.size;
if size.w == 0 {
size.w = source.size.w / 2;
}
if size.h == 0 {
size.h = source.size.h / 2;
}
if data.anchor.anchored_horizontally() {
size.w = source.size.w;
}
if data.anchor.anchored_vertically() {
size.h = source.size.h;
}
let x = if data.anchor.contains(Anchor::LEFT) {
source.loc.x + data.margin.left
} else if data.anchor.contains(Anchor::RIGHT) {
source.loc.x + (source.size.w - size.w) - data.margin.right
} else {
source.loc.x + ((source.size.w / 2) - (size.w / 2))
};
let y = if data.anchor.contains(Anchor::TOP) {
source.loc.y + data.margin.top
} else if data.anchor.contains(Anchor::BOTTOM) {
source.loc.y + (source.size.h - size.h) - data.margin.bottom
} else {
source.loc.y + ((source.size.h / 2) - (size.h / 2))
};
let location: Point<i32, Logical> = (x, y).into();
if let ExclusiveZone::Exclusive(amount) = data.exclusive_zone {
match data.anchor {
x if x.contains(Anchor::LEFT) && !x.contains(Anchor::RIGHT) => {
zone.loc.x += amount as i32 + data.margin.left + data.margin.right
}
x if x.contains(Anchor::TOP) && !x.contains(Anchor::BOTTOM) => {
zone.loc.y += amount as i32 + data.margin.top + data.margin.bottom
}
x if x.contains(Anchor::RIGHT) && !x.contains(Anchor::LEFT) => {
zone.size.w -= amount as i32 + data.margin.left + data.margin.right
}
x if x.contains(Anchor::BOTTOM) && !x.contains(Anchor::TOP) => {
zone.size.h -= amount as i32 + data.margin.top + data.margin.top
}
_ => {}
}
}
slog::debug!(
crate::slog_or_fallback(None),
"Setting layer to pos {:?} and size {:?}",
location,
size
);
let size_changed = layer
.0
.surface
.with_pending_state(|state| {
state.size.replace(size).map(|old| old != size).unwrap_or(true)
})
.unwrap();
if size_changed {
layer.0.surface.send_configure();
}
layer_state(layer).location = location;
}
slog::debug!(crate::slog_or_fallback(None), "Remaining zone {:?}", zone);
self.zone = zone;
}
}
fn output(&self) -> Option<Output> {
self.output.upgrade().map(|inner| Output { inner })
}
/// Cleanup some internally used resources.
///
/// This function needs to be called periodically (though not necessarily frequently)
/// to be able cleanup internally used resources.
pub fn cleanup(&mut self) {
self.layers.retain(|layer| layer.alive())
}
}
#[derive(Debug, Default)]
pub struct LayerState {
pub location: Point<i32, Logical>,
}
type LayerUserdata = RefCell<Option<LayerState>>;
pub fn layer_state(layer: &LayerSurface) -> RefMut<'_, LayerState> {
let userdata = layer.user_data();
userdata.insert_if_missing(LayerUserdata::default);
RefMut::map(userdata.get::<LayerUserdata>().unwrap().borrow_mut(), |opt| {
if opt.is_none() {
*opt = Some(LayerState::default());
}
opt.as_mut().unwrap()
})
}
/// A [`LayerSurface`] represents a single layer surface as given by the wlr-layer-shell protocol.
#[derive(Debug, Clone)]
pub struct LayerSurface(pub(crate) Rc<LayerSurfaceInner>);
impl PartialEq for LayerSurface {
fn eq(&self, other: &Self) -> bool {
self.0.id == other.0.id
}
}
impl Eq for LayerSurface {}
impl Hash for LayerSurface {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.id.hash(state);
}
}
#[derive(Debug)]
pub(crate) struct LayerSurfaceInner {
pub(crate) id: usize,
surface: WlrLayerSurface,
namespace: String,
userdata: UserDataMap,
}
impl Drop for LayerSurfaceInner {
fn drop(&mut self) {
LAYER_IDS.lock().unwrap().remove(&self.id);
}
}
impl LayerSurface {
/// Create a new [`LayerSurface`] from a given [`WlrLayerSurface`] and its namespace.
pub fn new(surface: WlrLayerSurface, namespace: String) -> LayerSurface {
LayerSurface(Rc::new(LayerSurfaceInner {
id: next_layer_id(),
surface,
namespace,
userdata: UserDataMap::new(),
}))
}
/// Checks if the surface is still alive
pub fn alive(&self) -> bool {
self.0.surface.alive()
}
/// Returns the underlying [`WlrLayerSurface`]
pub fn layer_surface(&self) -> &WlrLayerSurface {
&self.0.surface
}
/// Returns the underlying [`WlSurface`]
pub fn get_surface(&self) -> Option<&WlSurface> {
self.0.surface.get_surface()
}
/// Returns the cached protocol state
pub fn cached_state(&self) -> Option<LayerSurfaceCachedState> {
self.0.surface.get_surface().map(|surface| {
with_states(surface, |states| {
*states.cached_state.current::<LayerSurfaceCachedState>()
})
.unwrap()
})
}
/// Returns true, if the surface has indicated, that it is able to process keyboard events.
pub fn can_receive_keyboard_focus(&self) -> bool {
self.0
.surface
.get_surface()
.map(|surface| {
with_states(surface, |states| {
match states
.cached_state
.current::<LayerSurfaceCachedState>()
.keyboard_interactivity
{
KeyboardInteractivity::Exclusive | KeyboardInteractivity::OnDemand => true,
KeyboardInteractivity::None => false,
}
})
.unwrap()
})
.unwrap_or(false)
}
/// Returns the layer this surface resides on, if any yet.
pub fn layer(&self) -> Option<WlrLayer> {
self.0.surface.get_surface().map(|surface| {
with_states(surface, |states| {
states.cached_state.current::<LayerSurfaceCachedState>().layer
})
.unwrap()
})
}
/// Returns the namespace of this surface
pub fn namespace(&self) -> &str {
&self.0.namespace
}
/// Returns the bounding box over this layer surface and its subsurfaces.
pub fn bbox(&self) -> Rectangle<i32, Logical> {
if let Some(surface) = self.0.surface.get_surface() {
bbox_from_surface_tree(surface, (0, 0))
} else {
Rectangle::from_loc_and_size((0, 0), (0, 0))
}
}
/// Returns the bounding box over this layer surface, it subsurfaces as well as any popups.
///
/// Note: You need to use a [`PopupManager`] to track popups, otherwise the bounding box
/// will not include the popups.
pub fn bbox_with_popups(&self) -> Rectangle<i32, Logical> {
let mut bounding_box = self.bbox();
if let Some(surface) = self.0.surface.get_surface() {
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, location));
}
}
}
bounding_box
}
/// Finds the topmost surface under this point if any and returns it together with the location of this
/// surface.
///
/// - `point` needs to be relative to (0,0) of the layer surface.
pub fn surface_under<P: Into<Point<f64, Logical>>>(
&self,
point: P,
) -> Option<(WlSurface, Point<i32, Logical>)> {
let point = point.into();
if let Some(surface) = self.get_surface() {
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(result) = popup
.get_surface()
.and_then(|surface| under_from_surface_tree(surface, point, location))
{
return Some(result);
}
}
under_from_surface_tree(surface, point, (0, 0))
} else {
None
}
}
/// Returns the damage of all the surfaces of this layer surface.
///
/// If `for_values` is `Some(_)` it will only return the damage on the
/// first call for a given [`Space`] and [`Output`], if the buffer hasn't changed.
/// Subsequent calls will return an empty vector until the buffer is updated again.
pub(super) fn accumulated_damage(
&self,
for_values: Option<(&Space, &Output)>,
) -> Vec<Rectangle<i32, Logical>> {
let mut damage = Vec::new();
if let Some(surface) = self.get_surface() {
damage.extend(
damage_from_surface_tree(surface, (0, 0), for_values)
.into_iter()
.flat_map(|rect| rect.intersection(self.bbox())),
);
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
let bbox = bbox_from_surface_tree(surface, location);
let popup_damage = damage_from_surface_tree(surface, location, for_values);
damage.extend(popup_damage.into_iter().flat_map(|rect| rect.intersection(bbox)));
}
}
}
damage
}
/// Sends the frame callback to all the subsurfaces in this
/// window that requested it
pub fn send_frame(&self, time: u32) {
if let Some(wl_surface) = self.0.surface.get_surface() {
send_frames_surface_tree(wl_surface, time)
}
}
/// Returns a [`UserDataMap`] to allow associating arbitrary data with this surface.
pub fn user_data(&self) -> &UserDataMap {
&self.0.userdata
}
}
/// Renders a given [`LayerSurface`] using a provided renderer and frame.
///
/// - `scale` needs to be equivalent to the fractional scale the rendered result should have.
/// - `location` is the position the layer surface should be drawn at.
/// - `damage` is the set of regions of the layer surface that should be drawn.
///
/// Note: This function will render nothing, if you are not using
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
/// to let smithay handle buffer management.
pub fn draw_layer_surface<R, E, F, T, P>(
renderer: &mut R,
frame: &mut F,
layer: &LayerSurface,
scale: f64,
location: P,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error>
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture + 'static,
P: Into<Point<i32, Logical>>,
{
let location = location.into();
if let Some(surface) = layer.get_surface() {
draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?;
for (popup, p_location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
let damage = damage
.iter()
.cloned()
.map(|mut geo| {
geo.loc -= p_location;
geo
})
.collect::<Vec<_>>();
draw_surface_tree(
renderer,
frame,
surface,
scale,
location + p_location,
&damage,
log,
)?;
}
}
}
Ok(())
}

61
src/desktop/mod.rs Normal file
View File

@ -0,0 +1,61 @@
//! Desktop management helpers
//!
//! This module contains helpers to organize and interact with desktop-style shells.
//!
//! It is therefore a lot more opinionated than for example the [xdg-shell handler](crate::wayland::shell::xdg::xdg_shell_init)
//! and tightly integrates with some protocols (e.g. xdg-shell).
//!
//! The usage of this module is therefor entirely optional and depending on your use-case you might also only want
//! to use a limited set of the helpers provided.
//!
//! ## Helpers
//!
//! ### [`Window`]
//!
//! A window represents what is typically understood by the end-user as a single application window.
//!
//! Currently it abstracts over xdg-shell toplevels and Xwayland surfaces (TODO).
//! It provides a bunch of methods to calculate and retrieve its size, manage itself, attach additional user_data
//! as well as a [drawing function](`draw_window`) to ease rendering it's related surfaces.
//!
//! Note that a [`Window`] on it's own has no position. For that it needs to be placed inside a [`Space`].
//!
//! ### [`Space`]
//!
//! A space represents a two-dimensional plane of undefined dimensions.
//! [`Window`]s and [`Output`](crate::wayland::output::Output)s can be mapped onto it.
//!
//! Windows get a position and stacking order through mapping. Outputs become views of a part of the [`Space`]
//! and can be rendered via [`Space::render_output`]. Rendering results of spaces are automatically damage-tracked.
//!
//! ### Layer Shell
//!
//! A [`LayerSurface`] represents a surface as provided by e.g. the layer-shell protocol.
//! It provides similar helper methods as a [`Window`] does to toplevel surfaces.
//!
//! Each [`Output`](crate::wayland::output::Output) can be associated a [`LayerMap`] by calling [`layer_map_for_output`],
//! which [`LayerSurface`]s can be mapped upon. Associated layer maps are automatically rendered by [`Space::render_output`],
//! but a [draw function](`draw_layer_surface`) is also provided for manual layer-surface management.
//!
//! ### Popups
//!
//! Provides a [`PopupManager`], which can be used to automatically keep track of popups and their
//! relations to one-another. Popups are then automatically rendered with their matching toplevel surfaces,
//! when either [`draw_window`], [`draw_layer_surface`] or [`Space::render_output`] is called.
//!
//! ## Remarks
//!
//! Note that the desktop abstractions are concerned with easing rendering different clients and therefore need to be able
//! to manage client buffers to do so. If you plan to use the provided drawing functions, you need to use
//! [`on_commit_buffer_handler`](crate::backend::renderer::utils::on_commit_buffer_handler).
pub(crate) mod layer;
mod popup;
pub mod space;
pub mod utils;
mod window;
pub use self::layer::{draw_layer_surface, layer_map_for_output, LayerMap, LayerSurface};
pub use self::popup::*;
pub use self::space::Space;
pub use self::window::*;

288
src/desktop/popup.rs Normal file
View File

@ -0,0 +1,288 @@
use crate::{
utils::{DeadResource, Logical, Point, Rectangle},
wayland::{
compositor::{get_role, with_states},
shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE},
},
};
use std::sync::{Arc, Mutex};
use wayland_server::protocol::wl_surface::WlSurface;
/// Helper to track popups.
#[derive(Debug)]
pub struct PopupManager {
unmapped_popups: Vec<PopupKind>,
popup_trees: Vec<PopupTree>,
logger: ::slog::Logger,
}
impl PopupManager {
/// Create a new [`PopupManager`].
pub fn new<L: Into<Option<::slog::Logger>>>(logger: L) -> Self {
PopupManager {
unmapped_popups: Vec::new(),
popup_trees: Vec::new(),
logger: crate::slog_or_fallback(logger),
}
}
/// Start tracking a new popup.
pub fn track_popup(&mut self, kind: PopupKind) -> Result<(), DeadResource> {
if kind.parent().is_some() {
self.add_popup(kind)
} else {
slog::trace!(self.logger, "Adding unmapped popups: {:?}", kind);
self.unmapped_popups.push(kind);
Ok(())
}
}
/// Needs to be called for [`PopupManager`] to correctly update its internal state.
pub fn commit(&mut self, surface: &WlSurface) {
if get_role(surface) == Some(XDG_POPUP_ROLE) {
if let Some(i) = self
.unmapped_popups
.iter()
.position(|p| p.get_surface() == Some(surface))
{
slog::trace!(self.logger, "Popup got mapped");
let popup = self.unmapped_popups.swap_remove(i);
// at this point the popup must have a parent,
// or it would have raised a protocol error
let _ = self.add_popup(popup);
}
}
}
fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> {
let mut parent = popup.parent().unwrap();
while get_role(&parent) == Some(XDG_POPUP_ROLE) {
parent = with_states(&parent, |states| {
states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.parent
.as_ref()
.cloned()
.unwrap()
})?;
}
with_states(&parent, |states| {
let tree = PopupTree::default();
if states.data_map.insert_if_missing(|| tree.clone()) {
self.popup_trees.push(tree);
};
let tree = states.data_map.get::<PopupTree>().unwrap();
if !tree.alive() {
// if it previously had no popups, we likely removed it from our list already
self.popup_trees.push(tree.clone());
}
slog::trace!(self.logger, "Adding popup {:?} to parent {:?}", popup, parent);
tree.insert(popup);
})
}
/// Finds the popup belonging to a given [`WlSurface`], if any.
pub fn find_popup(&self, surface: &WlSurface) -> Option<PopupKind> {
self.unmapped_popups
.iter()
.find(|p| p.get_surface() == Some(surface))
.cloned()
.or_else(|| {
self.popup_trees
.iter()
.map(|tree| tree.iter_popups())
.flatten()
.find(|(p, _)| p.get_surface() == Some(surface))
.map(|(p, _)| p)
})
}
/// Returns the popups and their relative positions for a given toplevel surface, if any.
pub fn popups_for_surface(
surface: &WlSurface,
) -> Result<impl Iterator<Item = (PopupKind, Point<i32, Logical>)>, DeadResource> {
with_states(surface, |states| {
states
.data_map
.get::<PopupTree>()
.map(|x| x.iter_popups())
.into_iter()
.flatten()
})
}
/// Needs to be called periodically (but not necessarily frequently)
/// to cleanup internal resources.
pub fn cleanup(&mut self) {
// retain_mut is sadly still unstable
self.popup_trees.iter_mut().for_each(|tree| tree.cleanup());
self.popup_trees.retain(|tree| tree.alive());
self.unmapped_popups.retain(|surf| surf.alive());
}
}
#[derive(Debug, Default, Clone)]
struct PopupTree(Arc<Mutex<Vec<PopupNode>>>);
#[derive(Debug, Clone)]
struct PopupNode {
surface: PopupKind,
children: Vec<PopupNode>,
}
impl PopupTree {
fn iter_popups(&self) -> impl Iterator<Item = (PopupKind, Point<i32, Logical>)> {
self.0
.lock()
.unwrap()
.iter()
.map(|n| n.iter_popups_relative_to((0, 0)).map(|(p, l)| (p.clone(), l)))
.flatten()
.collect::<Vec<_>>()
.into_iter()
}
fn insert(&self, popup: PopupKind) {
let children = &mut *self.0.lock().unwrap();
for child in children.iter_mut() {
if child.insert(popup.clone()) {
return;
}
}
children.push(PopupNode::new(popup));
}
fn cleanup(&mut self) {
let mut children = self.0.lock().unwrap();
for child in children.iter_mut() {
child.cleanup();
}
children.retain(|n| n.surface.alive());
}
fn alive(&self) -> bool {
!self.0.lock().unwrap().is_empty()
}
}
impl PopupNode {
fn new(surface: PopupKind) -> Self {
PopupNode {
surface,
children: Vec::new(),
}
}
fn iter_popups_relative_to<P: Into<Point<i32, Logical>>>(
&self,
loc: P,
) -> impl Iterator<Item = (&PopupKind, Point<i32, Logical>)> {
let relative_to = loc.into() + self.surface.location();
std::iter::once((&self.surface, relative_to)).chain(
self.children
.iter()
.map(move |x| {
Box::new(x.iter_popups_relative_to(relative_to))
as Box<dyn Iterator<Item = (&PopupKind, Point<i32, Logical>)>>
})
.flatten(),
)
}
fn insert(&mut self, popup: PopupKind) -> bool {
let parent = popup.parent().unwrap();
if self.surface.get_surface() == Some(&parent) {
self.children.push(PopupNode::new(popup));
true
} else {
for child in &mut self.children {
if child.insert(popup.clone()) {
return true;
}
}
false
}
}
fn cleanup(&mut self) {
for child in &mut self.children {
child.cleanup();
}
self.children.retain(|n| n.surface.alive());
}
}
/// Represents a popup surface
#[derive(Debug, Clone)]
pub enum PopupKind {
/// xdg-shell [`PopupSurface`]
Xdg(PopupSurface),
}
impl PopupKind {
fn alive(&self) -> bool {
match *self {
PopupKind::Xdg(ref t) => t.alive(),
}
}
/// Retrieves the underlying [`WlSurface`]
pub fn get_surface(&self) -> Option<&WlSurface> {
match *self {
PopupKind::Xdg(ref t) => t.get_surface(),
}
}
fn parent(&self) -> Option<WlSurface> {
match *self {
PopupKind::Xdg(ref t) => t.get_parent_surface(),
}
}
/// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry`
pub fn geometry(&self) -> Rectangle<i32, Logical> {
let wl_surface = match self.get_surface() {
Some(s) => s,
None => return Rectangle::from_loc_and_size((0, 0), (0, 0)),
};
with_states(wl_surface, |states| {
states
.cached_state
.current::<SurfaceCachedState>()
.geometry
.unwrap_or_default()
})
.unwrap()
}
fn location(&self) -> Point<i32, Logical> {
let wl_surface = match self.get_surface() {
Some(s) => s,
None => return (0, 0).into(),
};
with_states(wl_surface, |states| {
states
.data_map
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap()
.current
.geometry
})
.unwrap_or_default()
.loc
}
}
impl From<PopupSurface> for PopupKind {
fn from(p: PopupSurface) -> PopupKind {
PopupKind::Xdg(p)
}
}

View File

@ -0,0 +1,208 @@
use crate::{
backend::renderer::{Frame, ImportAll, Renderer, Texture},
desktop::{space::*, utils::*},
utils::{Logical, Point, Rectangle},
wayland::output::Output,
};
use std::{
any::{Any, TypeId},
hash::{Hash, Hasher},
};
use wayland_server::protocol::wl_surface::WlSurface;
/// Trait for custom elements to be rendered during [`Space::render_output`].
pub trait RenderElement<R, F, E, T>
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture + 'static,
Self: Any + 'static,
{
/// Returns an id unique to this element for the type of Self.
fn id(&self) -> usize;
#[doc(hidden)]
fn type_of(&self) -> TypeId {
std::any::Any::type_id(self)
}
/// Returns the bounding box of this element including its position in the space.
fn geometry(&self) -> Rectangle<i32, Logical>;
/// Returns the damage of the element since it's last update.
///
/// If you receive `Some(_)` for `for_values` you may cache that you
/// send the damage for this `Space` and `Output` combination once
/// and return an empty vector for subsequent calls until the contents
/// of this element actually change again for optimization reasons.
///
/// Returning `vec![Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))]` is always
/// correct, but very inefficient.
fn accumulated_damage(
&self,
for_values: Option<SpaceOutputTuple<'_, '_>>,
) -> Vec<Rectangle<i32, Logical>>;
/// Draws the element using the provided `Frame` and `Renderer`.
///
/// - `scale` provides the current fractional scale value to render as
/// - `damage` provides the regions you need to re-draw and *may* not
/// be equivalent to the damage returned by `accumulated_damage`.
/// Redrawing other parts of the element is not valid and may cause rendering artifacts.
fn draw(
&self,
renderer: &mut R,
frame: &mut F,
scale: f64,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error>;
}
pub(crate) trait SpaceElement<R, F, E, T>
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture,
{
fn id(&self) -> usize;
fn type_of(&self) -> TypeId;
fn location(&self, space_id: usize) -> Point<i32, Logical> {
self.geometry(space_id).loc
}
fn geometry(&self, space_id: usize) -> Rectangle<i32, Logical>;
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>>;
#[allow(clippy::too_many_arguments)]
fn draw(
&self,
space_id: usize,
renderer: &mut R,
frame: &mut F,
scale: f64,
location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error>;
}
impl<R, F, E, T> SpaceElement<R, F, E, T> for Box<dyn RenderElement<R, F, E, T>>
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll + 'static,
F: Frame<Error = E, TextureId = T> + 'static,
E: std::error::Error + 'static,
T: Texture + 'static,
{
fn id(&self) -> usize {
(&**self as &dyn RenderElement<R, F, E, T>).id()
}
fn type_of(&self) -> TypeId {
(&**self as &dyn RenderElement<R, F, E, T>).type_of()
}
fn geometry(&self, _space_id: usize) -> Rectangle<i32, Logical> {
(&**self as &dyn RenderElement<R, F, E, T>).geometry()
}
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
(&**self as &dyn RenderElement<R, F, E, T>).accumulated_damage(for_values.map(SpaceOutputTuple::from))
}
fn draw(
&self,
_space_id: usize,
renderer: &mut R,
frame: &mut F,
scale: f64,
_location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error> {
(&**self as &dyn RenderElement<R, F, E, T>).draw(renderer, frame, scale, damage, log)
}
}
/// Generic helper for drawing [`WlSurface`]s and their subsurfaces
/// as custom elements via [`RenderElement`].
///
/// For example useful for cursor or drag-and-drop surfaces.
///
/// Note: This element will render nothing, if you are not using
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
/// to let smithay handle buffer management.
#[derive(Debug)]
pub struct SurfaceTree {
/// Surface to be drawn
pub surface: WlSurface,
/// Position to draw add
pub position: Point<i32, Logical>,
}
impl<R, F, E, T> RenderElement<R, F, E, T> for SurfaceTree
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture + 'static,
{
fn id(&self) -> usize {
self.surface.as_ref().id() as usize
}
fn geometry(&self) -> Rectangle<i32, Logical> {
let mut bbox = bbox_from_surface_tree(&self.surface, (0, 0));
bbox.loc += self.position;
bbox
}
fn accumulated_damage(
&self,
for_values: Option<SpaceOutputTuple<'_, '_>>,
) -> Vec<Rectangle<i32, Logical>> {
damage_from_surface_tree(&self.surface, (0, 0), for_values.map(|x| (x.0, x.1)))
}
fn draw(
&self,
renderer: &mut R,
frame: &mut F,
scale: f64,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error> {
crate::backend::renderer::utils::draw_surface_tree(
renderer,
frame,
&self.surface,
scale,
self.position,
damage,
log,
)
}
}
/// Newtype for (&Space, &Output) to provide a `Hash` implementation for damage tracking
#[derive(Debug, PartialEq)]
pub struct SpaceOutputTuple<'a, 'b>(pub &'a Space, pub &'b Output);
impl<'a, 'b> Hash for SpaceOutputTuple<'a, 'b> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.id.hash(state);
(std::sync::Arc::as_ptr(&self.1.inner) as *const () as usize).hash(state);
}
}
impl<'a, 'b> SpaceOutputTuple<'a, 'b> {
/// Returns an owned version that produces and equivalent hash
pub fn owned_hash(&self) -> SpaceOutputHash {
SpaceOutputHash(
self.0.id,
std::sync::Arc::as_ptr(&self.1.inner) as *const () as usize,
)
}
}
impl<'a, 'b> From<(&'a Space, &'b Output)> for SpaceOutputTuple<'a, 'b> {
fn from((space, output): (&'a Space, &'b Output)) -> SpaceOutputTuple<'a, 'b> {
SpaceOutputTuple(space, output)
}
}
/// Type to use as an owned hashable value equal to [`SpaceOutputTuple`]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct SpaceOutputHash(usize, usize);

View File

@ -0,0 +1,72 @@
use crate::{
backend::renderer::{Frame, ImportAll, Renderer, Texture},
desktop::{
layer::{layer_state as output_layer_state, *},
space::{Space, SpaceElement},
},
utils::{Logical, Point, Rectangle},
wayland::output::Output,
};
use std::{
any::TypeId,
cell::{RefCell, RefMut},
collections::HashMap,
};
#[derive(Default)]
pub struct LayerState {
pub drawn: bool,
}
type LayerUserdata = RefCell<HashMap<usize, LayerState>>;
pub fn layer_state(space: usize, l: &LayerSurface) -> RefMut<'_, LayerState> {
let userdata = l.user_data();
userdata.insert_if_missing(LayerUserdata::default);
RefMut::map(userdata.get::<LayerUserdata>().unwrap().borrow_mut(), |m| {
m.entry(space).or_default()
})
}
impl<R, F, E, T> SpaceElement<R, F, E, T> for LayerSurface
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture + 'static,
{
fn id(&self) -> usize {
self.0.id
}
fn type_of(&self) -> TypeId {
TypeId::of::<LayerSurface>()
}
fn geometry(&self, _space_id: usize) -> Rectangle<i32, Logical> {
let mut bbox = self.bbox_with_popups();
let state = output_layer_state(self);
bbox.loc += state.location;
bbox
}
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
self.accumulated_damage(for_values)
}
fn draw(
&self,
space_id: usize,
renderer: &mut R,
frame: &mut F,
scale: f64,
location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error> {
let res = draw_layer_surface(renderer, frame, self, scale, location, damage, log);
if res.is_ok() {
layer_state(space_id, self).drawn = true;
}
res
}
}

713
src/desktop/space/mod.rs Normal file
View File

@ -0,0 +1,713 @@
//! This module contains the [`Space`] helper class as well has related
//! rendering helpers to add custom elements or different clients to a space.
use crate::{
backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform},
desktop::{
layer::{layer_map_for_output, LayerSurface},
window::Window,
},
utils::{Logical, Point, Rectangle},
wayland::{
compositor::{
get_parent, is_sync_subsurface, with_surface_tree_downward, SubsurfaceCachedState,
TraversalAction,
},
output::Output,
shell::wlr_layer::Layer as WlrLayer,
},
};
use indexmap::{IndexMap, IndexSet};
use std::{cell::RefCell, collections::VecDeque, fmt};
use wayland_server::protocol::wl_surface::WlSurface;
mod element;
mod layer;
mod output;
mod window;
pub use self::element::*;
use self::layer::*;
use self::output::*;
use self::window::*;
crate::utils::ids::id_gen!(next_space_id, SPACE_ID, SPACE_IDS);
/// Represents two dimensional plane to map windows and outputs upon.
#[derive(Debug)]
pub struct Space {
pub(super) id: usize,
// in z-order, back to front
windows: IndexSet<Window>,
outputs: Vec<Output>,
logger: ::slog::Logger,
}
/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups.
pub type DynamicRenderElements<R> =
Box<dyn RenderElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>>;
impl PartialEq for Space {
fn eq(&self, other: &Space) -> bool {
self.id == other.id
}
}
impl Drop for Space {
fn drop(&mut self) {
SPACE_IDS.lock().unwrap().remove(&self.id);
}
}
impl Space {
/// Create a new [`Space`]
pub fn new<L>(log: L) -> Space
where
L: Into<Option<slog::Logger>>,
{
Space {
id: next_space_id(),
windows: IndexSet::new(),
outputs: Vec::new(),
logger: crate::slog_or_fallback(log),
}
}
/// Map a [`Window`] and move it to top of the stack
///
/// This can safely be called on an already mapped window
/// to update its location inside the space.
///
/// If activate is true it will set the new windows state
/// to be activate and removes that state from every
/// other mapped window.
pub fn map_window<P: Into<Point<i32, Logical>>>(&mut self, window: &Window, location: P, activate: bool) {
self.insert_window(window, activate);
window_state(self.id, window).location = location.into();
}
/// Moves an already mapped [`Window`] to top of the stack
///
/// This function does nothing for unmapped windows.
///
/// If activate is true it will set the new windows state
/// to be activate and removes that state from every
/// other mapped window.
pub fn raise_window(&mut self, window: &Window, activate: bool) {
if self.windows.shift_remove(window) {
self.insert_window(window, activate);
}
}
fn insert_window(&mut self, window: &Window, activate: bool) {
self.windows.insert(window.clone());
if activate {
window.set_activated(true);
for w in self.windows.iter() {
if w != window {
w.set_activated(false);
}
}
}
}
/// Unmap a [`Window`] from this space.
///
/// This function does nothing for already unmapped windows
pub fn unmap_window(&mut self, window: &Window) {
if let Some(map) = window.user_data().get::<WindowUserdata>() {
map.borrow_mut().remove(&self.id);
}
self.windows.shift_remove(window);
}
/// Iterate window in z-order back to front
pub fn windows(&self) -> impl DoubleEndedIterator<Item = &Window> {
self.windows.iter()
}
/// Get a reference to the window under a given point, if any
pub fn window_under<P: Into<Point<f64, Logical>>>(&self, point: P) -> Option<&Window> {
let point = point.into();
self.windows.iter().rev().find(|w| {
let bbox = window_rect(w, &self.id);
bbox.to_f64().contains(point)
})
}
/// Get a reference to the outputs under a given point
pub fn output_under<P: Into<Point<f64, Logical>>>(&self, point: P) -> impl Iterator<Item = &Output> {
let point = point.into();
self.outputs.iter().rev().filter(move |o| {
let bbox = self.output_geometry(o);
bbox.map(|bbox| bbox.to_f64().contains(point)).unwrap_or(false)
})
}
/// Returns the window matching a given surface, if any
pub fn window_for_surface(&self, surface: &WlSurface) -> Option<&Window> {
if !surface.as_ref().is_alive() {
return None;
}
self.windows
.iter()
.find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false))
}
/// Returns the layer matching a given surface, if any
pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<LayerSurface> {
if !surface.as_ref().is_alive() {
return None;
}
self.outputs.iter().find_map(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(surface).cloned()
})
}
/// Returns the geometry of a [`Window`] including its relative position inside the Space.
pub fn window_geometry(&self, w: &Window) -> Option<Rectangle<i32, Logical>> {
if !self.windows.contains(w) {
return None;
}
Some(window_geo(w, &self.id))
}
/// Returns the bounding box of a [`Window`] including its relative position inside the Space.
pub fn window_bbox(&self, w: &Window) -> Option<Rectangle<i32, Logical>> {
if !self.windows.contains(w) {
return None;
}
Some(window_rect(w, &self.id))
}
/// Maps an [`Output`] inside the space.
///
/// Can be safely called on an already mapped
/// [`Output`] to update its scale or location.
///
/// The scale is the what is rendered for the given output
/// and may be fractional. It is independent from the integer scale
/// reported to clients by the output.
///
/// *Note:* Remapping an output does reset it's damage memory.
pub fn map_output<P: Into<Point<i32, Logical>>>(&mut self, output: &Output, scale: f64, location: P) {
let mut state = output_state(self.id, output);
*state = OutputState {
location: location.into(),
render_scale: scale,
// keep surfaces, we still need to inform them of leaving,
// if they don't overlap anymore during refresh.
surfaces: state.surfaces.drain(..).collect::<Vec<_>>(),
// resets last_seen and old_damage, if remapped
..Default::default()
};
if !self.outputs.contains(output) {
self.outputs.push(output.clone());
}
}
/// Iterate over all mapped [`Output`]s of this space.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
self.outputs.iter()
}
/// Unmap an [`Output`] from this space.
///
/// Does nothing if the output was not previously mapped.
pub fn unmap_output(&mut self, output: &Output) {
if !self.outputs.contains(output) {
return;
}
if let Some(map) = output.user_data().get::<OutputUserdata>() {
map.borrow_mut().remove(&self.id);
}
self.outputs.retain(|o| o != output);
}
/// Returns the geometry of the output including it's relative position inside the space.
///
/// The size is matching the amount of logical pixels of the space visible on the output
/// given is current mode and render scale.
pub fn output_geometry(&self, o: &Output) -> Option<Rectangle<i32, Logical>> {
if !self.outputs.contains(o) {
return None;
}
let transform: Transform = o.current_transform().into();
let state = output_state(self.id, o);
o.current_mode().map(|mode| {
Rectangle::from_loc_and_size(
state.location,
transform
.transform_size(mode.size)
.to_f64()
.to_logical(state.render_scale)
.to_i32_round(),
)
})
}
/// Returns the reder scale of a mapped output.
///
/// If the output was not previously mapped to the `Space`
/// this function returns `None`.
pub fn output_scale(&self, o: &Output) -> Option<f64> {
if !self.outputs.contains(o) {
return None;
}
let state = output_state(self.id, o);
Some(state.render_scale)
}
/// Returns all [`Output`]s a [`Window`] overlaps with.
pub fn outputs_for_window(&self, w: &Window) -> Vec<Output> {
if !self.windows.contains(w) {
return Vec::new();
}
let w_geo = window_rect(w, &self.id);
let mut outputs = self
.outputs
.iter()
.cloned()
.filter(|o| {
let o_geo = self.output_geometry(o).unwrap();
w_geo.overlaps(o_geo)
})
.collect::<Vec<Output>>();
outputs.sort_by(|o1, o2| {
let overlap = |rect1: Rectangle<i32, Logical>, rect2: Rectangle<i32, Logical>| -> i32 {
// x overlap
std::cmp::max(0, std::cmp::min(rect1.loc.x + rect1.size.w, rect2.loc.x + rect2.size.w) - std::cmp::max(rect1.loc.x, rect2.loc.x))
// y overlap
* std::cmp::max(0, std::cmp::min(rect1.loc.y + rect1.size.h, rect2.loc.y + rect2.size.h) - std::cmp::max(rect1.loc.y, rect2.loc.y))
};
let o1_area = overlap(self.output_geometry(o1).unwrap(), w_geo);
let o2_area = overlap(self.output_geometry(o2).unwrap(), w_geo);
o1_area.cmp(&o2_area)
});
outputs
}
/// Refresh some internal values and update client state,
/// meaning this will handle output enter and leave events
/// for mapped outputs and windows based on their position.
///
/// Needs to be called periodically, at best before every
/// wayland socket flush.
pub fn refresh(&mut self) {
self.windows.retain(|w| w.toplevel().alive());
for output in &mut self.outputs {
output_state(self.id, output)
.surfaces
.retain(|s| s.as_ref().is_alive());
}
for window in &self.windows {
let bbox = window_rect(window, &self.id);
let kind = window.toplevel();
for output in &self.outputs {
let output_geometry = self
.output_geometry(output)
.unwrap_or_else(|| Rectangle::from_loc_and_size((0, 0), (0, 0)));
let mut output_state = output_state(self.id, output);
// Check if the bounding box of the toplevel intersects with
// the output, if not no surface in the tree can intersect with
// the output.
if !output_geometry.overlaps(bbox) {
if let Some(surface) = kind.get_surface() {
with_surface_tree_downward(
surface,
(),
|_, _, _| TraversalAction::DoChildren(()),
|wl_surface, _, _| {
if output_state.surfaces.contains(wl_surface) {
slog::trace!(
self.logger,
"surface ({:?}) leaving output {:?}",
wl_surface,
output.name()
);
output.leave(wl_surface);
output_state.surfaces.retain(|s| s != wl_surface);
}
},
|_, _, _| true,
)
}
continue;
}
if let Some(surface) = kind.get_surface() {
with_surface_tree_downward(
surface,
window_loc(window, &self.id),
|_, states, location| {
let mut location = *location;
let data = states.data_map.get::<RefCell<SurfaceState>>();
if data.is_some() {
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
location += current.location;
}
TraversalAction::DoChildren(location)
} else {
// If the parent surface is unmapped, then the child surfaces are hidden as
// well, no need to consider them here.
TraversalAction::SkipChildren
}
},
|wl_surface, states, &loc| {
let data = states.data_map.get::<RefCell<SurfaceState>>();
if let Some(size) = data.and_then(|d| d.borrow().size()) {
let surface_rectangle = Rectangle { loc, size };
if output_geometry.overlaps(surface_rectangle) {
// We found a matching output, check if we already sent enter
if !output_state.surfaces.contains(wl_surface) {
slog::trace!(
self.logger,
"surface ({:?}) entering output {:?}",
wl_surface,
output.name()
);
output.enter(wl_surface);
output_state.surfaces.push(wl_surface.clone());
}
} else {
// Surface does not match output, if we sent enter earlier
// we should now send leave
if output_state.surfaces.contains(wl_surface) {
slog::trace!(
self.logger,
"surface ({:?}) leaving output {:?}",
wl_surface,
output.name()
);
output.leave(wl_surface);
output_state.surfaces.retain(|s| s != wl_surface);
}
}
} else {
// Maybe the the surface got unmapped, send leave on output
if output_state.surfaces.contains(wl_surface) {
slog::trace!(
self.logger,
"surface ({:?}) leaving output {:?}",
wl_surface,
output.name()
);
output.leave(wl_surface);
output_state.surfaces.retain(|s| s != wl_surface);
}
}
},
|_, _, _| true,
)
}
}
}
}
/// Should be called on commit to let the space automatically call [`Window::refresh`]
/// for the window that belongs to the given surface, if managed by this space.
pub fn commit(&self, surface: &WlSurface) {
if is_sync_subsurface(surface) {
return;
}
let mut root = surface.clone();
while let Some(parent) = get_parent(&root) {
root = parent;
}
if let Some(window) = self.windows().find(|w| w.toplevel().get_surface() == Some(&root)) {
window.refresh();
}
}
/// Render a given [`Output`] using a given [`Renderer`].
///
/// [`Space`] will render all mapped [`Window`]s, mapped [`LayerSurface`](super::LayerSurface)s
/// of the given [`Output`] and their popups (if tracked by a [`PopupManager`](super::PopupManager)).
/// `clear_color` will be used to fill all unoccupied regions.
///
/// Rendering using this function will automatically apply damage-tracking.
/// To facilitate this you need to provide age values of the buffers bound to
/// the given `renderer`. If you stop using `Space` temporarily for rendering
/// or apply additional rendering operations, you need to reset the age values
/// accordingly as `Space` will be unable to track your custom rendering operations
/// to avoid rendering artifacts.
///
/// To add aditional elements without breaking damage-tracking implement the `RenderElement`
/// trait and use `custom_elements` to provide them to this function. `custom_elements are rendered
/// after every other element.
///
/// Returns a list of updated regions (or `None` if that list would be empty) in case of success.
pub fn render_output<R>(
&mut self,
renderer: &mut R,
output: &Output,
age: usize,
clear_color: [f32; 4],
custom_elements: &[DynamicRenderElements<R>],
) -> Result<Option<Vec<Rectangle<i32, Logical>>>, RenderError<R>>
where
R: Renderer + ImportAll + 'static,
R::TextureId: 'static,
R::Error: 'static,
R::Frame: 'static,
{
if !self.outputs.contains(output) {
return Err(RenderError::UnmappedOutput);
}
type SpaceElem<R> =
dyn SpaceElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>;
let mut state = output_state(self.id, output);
let output_size = output
.current_mode()
.ok_or(RenderError::OutputNoMode)?
.size
.to_f64()
.to_logical(state.render_scale)
.to_i32_round();
let output_geo = Rectangle::from_loc_and_size(state.location, output_size);
let layer_map = layer_map_for_output(output);
// This will hold all the damage we need for this rendering step
let mut damage = Vec::<Rectangle<i32, Logical>>::new();
// First add damage for windows gone
for old_toplevel in state
.last_state
.iter()
.filter_map(|(id, geo)| {
if !self
.windows
.iter()
.map(|w| w as &SpaceElem<R>)
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
.any(|e| ToplevelId::from(e) == *id)
{
Some(*geo)
} else {
None
}
})
.collect::<Vec<Rectangle<i32, Logical>>>()
{
slog::trace!(self.logger, "Removing toplevel at: {:?}", old_toplevel);
damage.push(old_toplevel);
}
// lets iterate front to back and figure out, what new windows or unmoved windows we have
for element in self
.windows
.iter()
.map(|w| w as &SpaceElem<R>)
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
{
let geo = element.geometry(self.id);
let old_geo = state.last_state.get(&ToplevelId::from(element)).cloned();
// window was moved or resized
if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) {
// Add damage for the old position of the window
damage.push(old_geo.unwrap());
damage.push(geo);
} else {
// window stayed at its place
let loc = element.location(self.id);
damage.extend(element.accumulated_damage(Some((self, output))).into_iter().map(
|mut rect| {
rect.loc += loc;
rect
},
));
}
}
// That is all completely new damage, which we need to store for subsequent renders
let new_damage = damage.clone();
// We now add old damage states, if we have an age value
if age > 0 && state.old_damage.len() >= age {
// We do not need even older states anymore
state.old_damage.truncate(age);
damage.extend(state.old_damage.iter().flatten().copied());
} else {
// just damage everything, if we have no damage
damage = vec![output_geo];
}
// Optimize the damage for rendering
damage.dedup();
damage.retain(|rect| rect.overlaps(output_geo));
damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0);
// merge overlapping rectangles
damage = damage.into_iter().fold(Vec::new(), |mut new_damage, rect| {
if let Some(existing) = new_damage.iter_mut().find(|other| rect.overlaps(**other)) {
*existing = existing.merge(rect);
} else {
new_damage.push(rect);
}
new_damage
});
if damage.is_empty() {
return Ok(None);
}
let output_transform: Transform = output.current_transform().into();
let res = renderer.render(
output_transform
.transform_size(output_size)
.to_f64()
.to_physical(state.render_scale)
.to_i32_round(),
output_transform,
|renderer, frame| {
// First clear all damaged regions
slog::trace!(self.logger, "Clearing at {:#?}", damage);
frame.clear(
clear_color,
&damage
.iter()
.map(|geo| geo.to_f64().to_physical(state.render_scale).to_i32_round())
.collect::<Vec<_>>(),
)?;
// Then re-draw all windows & layers overlapping with a damage rect.
for element in layer_map
.layers_on(WlrLayer::Background)
.chain(layer_map.layers_on(WlrLayer::Bottom))
.map(|l| l as &SpaceElem<R>)
.chain(self.windows.iter().map(|w| w as &SpaceElem<R>))
.chain(
layer_map
.layers_on(WlrLayer::Top)
.chain(layer_map.layers_on(WlrLayer::Overlay))
.map(|l| l as &SpaceElem<R>),
)
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
{
let geo = element.geometry(self.id);
if damage.iter().any(|d| d.overlaps(geo)) {
let loc = element.location(self.id) - output_geo.loc;
let damage = damage
.iter()
.flat_map(|d| d.intersection(geo))
.map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size))
.collect::<Vec<_>>();
slog::trace!(
self.logger,
"Rendering toplevel at {:?} with damage {:#?}",
geo,
damage
);
element.draw(
self.id,
renderer,
frame,
state.render_scale,
loc,
&damage,
&self.logger,
)?;
}
}
Result::<(), R::Error>::Ok(())
},
);
if let Err(err) = res {
// if the rendering errors on us, we need to be prepared, that this whole buffer was partially updated and thus now unusable.
// thus clean our old states before returning
state.old_damage = VecDeque::new();
state.last_state = IndexMap::new();
return Err(RenderError::Rendering(err));
}
// If rendering was successful capture the state and add the damage
state.last_state = self
.windows
.iter()
.map(|w| w as &SpaceElem<R>)
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
.map(|elem| {
let geo = elem.geometry(self.id);
(ToplevelId::from(elem), geo)
})
.collect();
state.old_damage.push_front(new_damage.clone());
Ok(Some(new_damage))
}
/// Sends the frame callback to mapped [`Window`]s and [`LayerSurface`]s.
///
/// If `all` is set this will be send to `all` mapped surfaces.
/// Otherwise only windows and layers previously drawn during the
/// previous frame will be send frame events.
pub fn send_frames(&self, all: bool, time: u32) {
for window in self.windows.iter().filter(|w| {
all || {
let mut state = window_state(self.id, w);
std::mem::replace(&mut state.drawn, false)
}
}) {
window.send_frame(time);
}
for output in self.outputs.iter() {
let map = layer_map_for_output(output);
for layer in map.layers().filter(|l| {
all || {
let mut state = layer_state(self.id, l);
std::mem::replace(&mut state.drawn, false)
}
}) {
layer.send_frame(time);
}
}
}
}
/// Errors thrown by [`Space::render_output`]
#[derive(thiserror::Error)]
pub enum RenderError<R: Renderer> {
/// The provided [`Renderer`] did return an error during an operation
#[error(transparent)]
Rendering(R::Error),
/// The given [`Output`] has no set mode
#[error("Output has no active mode")]
OutputNoMode,
/// The given [`Output`] is not mapped to this [`Space`].
#[error("Output was not mapped to this space")]
UnmappedOutput,
}
impl<R: Renderer> fmt::Debug for RenderError<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RenderError::Rendering(err) => fmt::Debug::fmt(err, f),
RenderError::OutputNoMode => f.write_str("Output has no active move"),
RenderError::UnmappedOutput => f.write_str("Output was not mapped to this space"),
}
}
}

View File

@ -0,0 +1,57 @@
use crate::{
backend::renderer::{Frame, ImportAll, Renderer, Texture},
desktop::space::SpaceElement,
utils::{Logical, Point, Rectangle},
wayland::output::Output,
};
use indexmap::IndexMap;
use wayland_server::protocol::wl_surface::WlSurface;
use std::{
any::TypeId,
cell::{RefCell, RefMut},
collections::{HashMap, VecDeque},
};
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct ToplevelId {
t_id: TypeId,
id: usize,
}
impl<R, F, E, T> From<&dyn SpaceElement<R, F, E, T>> for ToplevelId
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll + 'static,
F: Frame<Error = E, TextureId = T> + 'static,
E: std::error::Error + 'static,
T: Texture + 'static,
{
fn from(elem: &dyn SpaceElement<R, F, E, T>) -> ToplevelId {
ToplevelId {
t_id: elem.type_of(),
id: elem.id(),
}
}
}
#[derive(Clone, Default)]
pub struct OutputState {
pub location: Point<i32, Logical>,
pub render_scale: f64,
// damage and last_state are in space coordinate space
pub old_damage: VecDeque<Vec<Rectangle<i32, Logical>>>,
pub last_state: IndexMap<ToplevelId, Rectangle<i32, Logical>>,
// surfaces for tracking enter and leave events
pub surfaces: Vec<WlSurface>,
}
pub type OutputUserdata = RefCell<HashMap<usize, OutputState>>;
pub fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> {
let userdata = o.user_data();
userdata.insert_if_missing(OutputUserdata::default);
RefMut::map(userdata.get::<OutputUserdata>().unwrap().borrow_mut(), |m| {
m.entry(space).or_default()
})
}

106
src/desktop/space/window.rs Normal file
View File

@ -0,0 +1,106 @@
use crate::{
backend::renderer::{Frame, ImportAll, Renderer, Texture},
desktop::{
space::{Space, SpaceElement},
window::{draw_window, Window},
},
utils::{Logical, Point, Rectangle},
wayland::output::Output,
};
use std::{
any::TypeId,
cell::{RefCell, RefMut},
collections::HashMap,
};
#[derive(Default)]
pub struct WindowState {
pub location: Point<i32, Logical>,
pub drawn: bool,
}
pub type WindowUserdata = RefCell<HashMap<usize, WindowState>>;
pub fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> {
let userdata = w.user_data();
userdata.insert_if_missing(WindowUserdata::default);
RefMut::map(userdata.get::<WindowUserdata>().unwrap().borrow_mut(), |m| {
m.entry(space).or_default()
})
}
pub fn window_geo(window: &Window, space_id: &usize) -> Rectangle<i32, Logical> {
let loc = window_loc(window, space_id);
let mut wgeo = window.geometry();
wgeo.loc = loc;
wgeo
}
pub fn window_rect(window: &Window, space_id: &usize) -> Rectangle<i32, Logical> {
let loc = window_loc(window, space_id);
let mut wgeo = window.bbox();
wgeo.loc += loc;
wgeo
}
pub fn window_rect_with_popups(window: &Window, space_id: &usize) -> Rectangle<i32, Logical> {
let loc = window_loc(window, space_id);
let mut wgeo = window.bbox_with_popups();
wgeo.loc += loc;
wgeo
}
pub fn window_loc(window: &Window, space_id: &usize) -> Point<i32, Logical> {
window
.user_data()
.get::<RefCell<HashMap<usize, WindowState>>>()
.unwrap()
.borrow()
.get(space_id)
.unwrap()
.location
}
impl<R, F, E, T> SpaceElement<R, F, E, T> for Window
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture + 'static,
{
fn id(&self) -> usize {
self.0.id
}
fn type_of(&self) -> TypeId {
TypeId::of::<Window>()
}
fn location(&self, space_id: usize) -> Point<i32, Logical> {
window_loc(self, &space_id)
}
fn geometry(&self, space_id: usize) -> Rectangle<i32, Logical> {
window_rect_with_popups(self, &space_id)
}
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
self.accumulated_damage(for_values)
}
fn draw(
&self,
space_id: usize,
renderer: &mut R,
frame: &mut F,
scale: f64,
location: Point<i32, Logical>,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error> {
let res = draw_window(renderer, frame, self, scale, location, damage, log);
if res.is_ok() {
window_state(space_id, self).drawn = true;
}
res
}
}

235
src/desktop/utils.rs Normal file
View File

@ -0,0 +1,235 @@
//! Helper functions to ease dealing with surface trees
use crate::{
backend::renderer::utils::SurfaceState,
desktop::Space,
utils::{Logical, Point, Rectangle, Size},
wayland::{
compositor::{
with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState,
SurfaceAttributes, TraversalAction,
},
output::Output,
},
};
use wayland_server::protocol::wl_surface;
use std::cell::RefCell;
impl SurfaceState {
/// Returns the size of the surface.
pub fn size(&self) -> Option<Size<i32, Logical>> {
self.buffer_dimensions
.map(|dims| dims.to_logical(self.buffer_scale))
}
fn contains_point<P: Into<Point<f64, Logical>>>(&self, attrs: &SurfaceAttributes, point: P) -> bool {
let point = point.into();
let size = match self.size() {
None => return false, // If the surface has no size, it can't have an input region.
Some(size) => size,
};
let rect = Rectangle {
loc: (0, 0).into(),
size,
}
.to_f64();
// The input region is always within the surface itself, so if the surface itself doesn't contain the
// point we can return false.
if !rect.contains(point) {
return false;
}
// If there's no input region, we're done.
if attrs.input_region.is_none() {
return true;
}
attrs
.input_region
.as_ref()
.unwrap()
.contains(point.to_i32_round())
}
}
/// Returns the bounding box of a given surface and all its subsurfaces.
///
/// - `location` can be set to offset the returned bounding box.
pub fn bbox_from_surface_tree<P>(surface: &wl_surface::WlSurface, location: P) -> Rectangle<i32, Logical>
where
P: Into<Point<i32, Logical>>,
{
let location = location.into();
let mut bounding_box = Rectangle::from_loc_and_size(location, (0, 0));
with_surface_tree_downward(
surface,
location,
|_, states, loc: &Point<i32, Logical>| {
let mut loc = *loc;
let data = states.data_map.get::<RefCell<SurfaceState>>();
if let Some(size) = data.and_then(|d| d.borrow().size()) {
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
loc += current.location;
}
// Update the bounding box.
bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size));
TraversalAction::DoChildren(loc)
} else {
// If the parent surface is unmapped, then the child surfaces are hidden as
// well, no need to consider them here.
TraversalAction::SkipChildren
}
},
|_, _, _| {},
|_, _, _| true,
);
bounding_box
}
/// Returns the damage rectangles of the current buffer for a given surface and its subsurfaces.
///
/// - `location` can be set to offset the returned bounding box.
/// - if a `key` is set the damage is only returned on the first call with the given key values.
/// Subsequent calls will return an empty vector until the buffer is updated again and new
/// damage values may be retrieved.
pub fn damage_from_surface_tree<P>(
surface: &wl_surface::WlSurface,
location: P,
key: Option<(&Space, &Output)>,
) -> Vec<Rectangle<i32, Logical>>
where
P: Into<Point<i32, Logical>>,
{
use super::space::SpaceOutputTuple;
let mut damage = Vec::new();
let key = key.map(|x| SpaceOutputTuple::from(x).owned_hash());
with_surface_tree_upward(
surface,
location.into(),
|_surface, states, location| {
let mut location = *location;
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
let data = data.borrow();
if key
.as_ref()
.map(|key| !data.damage_seen.contains(key))
.unwrap_or(true)
&& states.role == Some("subsurface")
{
let current = states.cached_state.current::<SubsurfaceCachedState>();
location += current.location;
}
}
TraversalAction::DoChildren(location)
},
|_surface, states, location| {
let mut location = *location;
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
let mut data = data.borrow_mut();
let attributes = states.cached_state.current::<SurfaceAttributes>();
if key
.as_ref()
.map(|key| !data.damage_seen.contains(key))
.unwrap_or(true)
{
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
location += current.location;
}
damage.extend(attributes.damage.iter().map(|dmg| {
let mut rect = match dmg {
Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale),
Damage::Surface(rect) => *rect,
};
rect.loc += location;
rect
}));
if let Some(key) = key {
data.damage_seen.insert(key);
}
}
}
},
|_, _, _| true,
);
damage
}
/// Returns the (sub-)surface under a given position given a surface, if any.
///
/// - `point` has to be the position to query, relative to (0, 0) of the given surface + `location`.
/// - `location` can be used to offset the returned point.
pub fn under_from_surface_tree<P>(
surface: &wl_surface::WlSurface,
point: Point<f64, Logical>,
location: P,
) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)>
where
P: Into<Point<i32, Logical>>,
{
let found = RefCell::new(None);
with_surface_tree_downward(
surface,
location.into(),
|wl_surface, states, location: &Point<i32, Logical>| {
let mut location = *location;
let data = states.data_map.get::<RefCell<SurfaceState>>();
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
location += current.location;
}
let contains_the_point = data
.map(|data| {
data.borrow()
.contains_point(&*states.cached_state.current(), point - location.to_f64())
})
.unwrap_or(false);
if contains_the_point {
*found.borrow_mut() = Some((wl_surface.clone(), location));
}
TraversalAction::DoChildren(location)
},
|_, _, _| {},
|_, _, _| {
// only continue if the point is not found
found.borrow().is_none()
},
);
found.into_inner()
}
/// Sends frame callbacks for a surface and its subsurfaces with the given `time`.
pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) {
with_surface_tree_downward(
surface,
(),
|_, _, &()| TraversalAction::DoChildren(()),
|_surf, states, &()| {
// the surface may not have any user_data if it is a subsurface and has not
// yet been commited
for callback in states
.cached_state
.current::<SurfaceAttributes>()
.frame_callbacks
.drain(..)
{
callback.done(time);
}
},
|_, _, &()| true,
);
}

336
src/desktop/window.rs Normal file
View File

@ -0,0 +1,336 @@
use crate::{
backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture},
desktop::{utils::*, PopupManager, Space},
utils::{Logical, Point, Rectangle},
wayland::{
compositor::with_states,
output::Output,
shell::xdg::{SurfaceCachedState, ToplevelSurface},
},
};
use std::{
cell::Cell,
hash::{Hash, Hasher},
rc::Rc,
};
use wayland_commons::user_data::UserDataMap;
use wayland_protocols::xdg_shell::server::xdg_toplevel;
use wayland_server::protocol::wl_surface;
crate::utils::ids::id_gen!(next_window_id, WINDOW_ID, WINDOW_IDS);
/// Abstraction around different toplevel kinds
#[derive(Debug, Clone, PartialEq)]
pub enum Kind {
/// xdg-shell [`ToplevelSurface`]
Xdg(ToplevelSurface),
/// XWayland surface (TODO)
#[cfg(feature = "xwayland")]
X11(X11Surface),
}
/// TODO
#[derive(Debug, Clone)]
pub struct X11Surface {
surface: wl_surface::WlSurface,
}
impl std::cmp::PartialEq for X11Surface {
fn eq(&self, other: &Self) -> bool {
self.alive() && other.alive() && self.surface == other.surface
}
}
impl X11Surface {
/// Checks if the surface is still alive.
pub fn alive(&self) -> bool {
self.surface.as_ref().is_alive()
}
/// Returns the underlying [`WlSurface`](wl_surface::WlSurface), if still any.
pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> {
if self.alive() {
Some(&self.surface)
} else {
None
}
}
}
impl Kind {
/// Checks if the surface is still alive.
pub fn alive(&self) -> bool {
match *self {
Kind::Xdg(ref t) => t.alive(),
#[cfg(feature = "xwayland")]
Kind::X11(ref t) => t.alive(),
}
}
/// Returns the underlying [`WlSurface`](wl_surface::WlSurface), if still any.
pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> {
match *self {
Kind::Xdg(ref t) => t.get_surface(),
#[cfg(feature = "xwayland")]
Kind::X11(ref t) => t.get_surface(),
}
}
}
#[derive(Debug)]
pub(super) struct WindowInner {
pub(super) id: usize,
toplevel: Kind,
bbox: Cell<Rectangle<i32, Logical>>,
user_data: UserDataMap,
}
impl Drop for WindowInner {
fn drop(&mut self) {
WINDOW_IDS.lock().unwrap().remove(&self.id);
}
}
/// Represents a single application window
#[derive(Debug, Clone)]
pub struct Window(pub(super) Rc<WindowInner>);
impl PartialEq for Window {
fn eq(&self, other: &Self) -> bool {
self.0.id == other.0.id
}
}
impl Eq for Window {}
impl Hash for Window {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.id.hash(state);
}
}
impl Window {
/// Construct a new [`Window`] from a given compatible toplevel surface
pub fn new(toplevel: Kind) -> Window {
let id = next_window_id();
Window(Rc::new(WindowInner {
id,
toplevel,
bbox: Cell::new(Rectangle::from_loc_and_size((0, 0), (0, 0))),
user_data: UserDataMap::new(),
}))
}
/// Returns the geometry of this window.
pub fn geometry(&self) -> Rectangle<i32, Logical> {
// It's the set geometry with the full bounding box as the fallback.
with_states(self.0.toplevel.get_surface().unwrap(), |states| {
states.cached_state.current::<SurfaceCachedState>().geometry
})
.unwrap()
.unwrap_or_else(|| self.bbox())
}
/// Returns a bounding box over this window and its children.
pub fn bbox(&self) -> Rectangle<i32, Logical> {
if self.0.toplevel.get_surface().is_some() {
self.0.bbox.get()
} else {
Rectangle::from_loc_and_size((0, 0), (0, 0))
}
}
/// Returns a bounding box over this window and children including popups.
///
/// Note: You need to use a [`PopupManager`] to track popups, otherwise the bounding box
/// will not include the popups.
pub fn bbox_with_popups(&self) -> Rectangle<i32, Logical> {
let mut bounding_box = self.bbox();
if let Some(surface) = self.0.toplevel.get_surface() {
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
let offset = self.geometry().loc + location - popup.geometry().loc;
bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, offset));
}
}
}
bounding_box
}
/// Activate/Deactivate this window
pub fn set_activated(&self, active: bool) -> bool {
match self.0.toplevel {
Kind::Xdg(ref t) => t
.with_pending_state(|state| {
if active {
state.states.set(xdg_toplevel::State::Activated)
} else {
state.states.unset(xdg_toplevel::State::Activated)
}
})
.unwrap_or(false),
#[cfg(feature = "xwayland")]
Kind::X11(ref _t) => unimplemented!(),
}
}
/// Commit any changes to this window
pub fn configure(&self) {
match self.0.toplevel {
Kind::Xdg(ref t) => t.send_configure(),
#[cfg(feature = "xwayland")]
Kind::X11(ref _t) => unimplemented!(),
}
}
/// Sends the frame callback to all the subsurfaces in this
/// window that requested it
pub fn send_frame(&self, time: u32) {
if let Some(surface) = self.0.toplevel.get_surface() {
send_frames_surface_tree(surface, time);
for (popup, _) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
send_frames_surface_tree(surface, time);
}
}
}
}
/// Updates internal values
///
/// Needs to be called whenever the toplevel surface or any unsynchronized subsurfaces of this window are updated
/// to correctly update the bounding box of this window.
pub fn refresh(&self) {
if let Some(surface) = self.0.toplevel.get_surface() {
self.0.bbox.set(bbox_from_surface_tree(surface, (0, 0)));
}
}
/// Finds the topmost surface under this point if any and returns it together with the location of this
/// surface.
///
/// - `point` should be relative to (0,0) of the window.
pub fn surface_under<P: Into<Point<f64, Logical>>>(
&self,
point: P,
) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)> {
let point = point.into();
if let Some(surface) = self.0.toplevel.get_surface() {
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
let offset = self.geometry().loc + location - popup.geometry().loc;
if let Some(result) = popup
.get_surface()
.and_then(|surface| under_from_surface_tree(surface, point, offset))
{
return Some(result);
}
}
under_from_surface_tree(surface, point, (0, 0))
} else {
None
}
}
/// Damage of all the surfaces of this window.
///
/// If `for_values` is `Some(_)` it will only return the damage on the
/// first call for a given [`Space`] and [`Output`], if the buffer hasn't changed.
/// Subsequent calls will return an empty vector until the buffer is updated again.
pub fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
let mut damage = Vec::new();
if let Some(surface) = self.0.toplevel.get_surface() {
damage.extend(
damage_from_surface_tree(surface, (0, 0), for_values)
.into_iter()
.flat_map(|rect| rect.intersection(self.bbox())),
);
for (popup, location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
let offset = self.geometry().loc + location - popup.geometry().loc;
let bbox = bbox_from_surface_tree(surface, offset);
let popup_damage = damage_from_surface_tree(surface, offset, for_values);
damage.extend(popup_damage.into_iter().flat_map(|rect| rect.intersection(bbox)));
}
}
}
damage
}
/// Returns the underlying toplevel
pub fn toplevel(&self) -> &Kind {
&self.0.toplevel
}
/// Returns a [`UserDataMap`] to allow associating arbitrary data with this window.
pub fn user_data(&self) -> &UserDataMap {
&self.0.user_data
}
}
/// Renders a given [`Window`] using a provided renderer and frame.
///
/// - `scale` needs to be equivalent to the fractional scale the rendered result should have.
/// - `location` is the position the window should be drawn at.
/// - `damage` is the set of regions of the window that should be drawn.
///
/// Note: This function will render nothing, if you are not using
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
/// to let smithay handle buffer management.
pub fn draw_window<R, E, F, T, P>(
renderer: &mut R,
frame: &mut F,
window: &Window,
scale: f64,
location: P,
damage: &[Rectangle<i32, Logical>],
log: &slog::Logger,
) -> Result<(), R::Error>
where
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error,
T: Texture + 'static,
P: Into<Point<i32, Logical>>,
{
let location = location.into();
if let Some(surface) = window.toplevel().get_surface() {
draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?;
for (popup, p_location) in PopupManager::popups_for_surface(surface)
.ok()
.into_iter()
.flatten()
{
if let Some(surface) = popup.get_surface() {
let offset = window.geometry().loc + p_location - popup.geometry().loc;
let damage = damage
.iter()
.cloned()
.map(|mut geo| {
geo.loc -= offset;
geo
})
.collect::<Vec<_>>();
draw_surface_tree(renderer, frame, surface, scale, location + offset, &damage, log)?;
}
}
}
Ok(())
}

View File

@ -51,6 +51,8 @@
pub extern crate nix;
pub mod backend;
#[cfg(feature = "desktop")]
pub mod desktop;
pub mod utils;
#[cfg(feature = "wayland_frontend")]
pub mod wayland;

View File

@ -296,6 +296,23 @@ impl<N: Coordinate, Kind> Point<N, Kind> {
}
}
impl<N: Coordinate, Kind> Point<N, Kind> {
/// Constrain this [`Point`] within a [`Rectangle`] with the same coordinates
///
/// The [`Point`] returned is guaranteed to be not smaller than the [`Rectangle`]
/// location and not greater than the [`Rectangle`] size.
#[inline]
pub fn constrain(self, rect: impl Into<Rectangle<N, Kind>>) -> Point<N, Kind> {
let rect = rect.into();
Point {
x: self.x.max(rect.loc.x).min(rect.size.w),
y: self.y.max(rect.loc.y).min(rect.size.h),
_kind: std::marker::PhantomData,
}
}
}
impl<N: Coordinate, Kind> Point<N, Kind> {
/// Convert the underlying numerical type to f64 for floating point manipulations
#[inline]
@ -543,6 +560,20 @@ impl<N: Coordinate, Kind> Size<N, Kind> {
}
}
impl<N: Coordinate, Kind> Size<N, Kind> {
/// Restrict this [`Size`] to min and max [`Size`] with the same coordinates
pub fn clamp(self, min: impl Into<Size<N, Kind>>, max: impl Into<Size<N, Kind>>) -> Size<N, Kind> {
let min = min.into();
let max = max.into();
Size {
w: self.w.max(min.w).min(max.w),
h: self.h.max(min.h).min(max.h),
_kind: std::marker::PhantomData,
}
}
}
impl<N: Coordinate, Kind> Size<N, Kind> {
/// Convert the underlying numerical type to f64 for floating point manipulations
#[inline]

37
src/utils/ids.rs Normal file
View File

@ -0,0 +1,37 @@
macro_rules! id_gen {
($func_name:ident, $id_name:ident, $ids_name:ident) => {
static $id_name: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
lazy_static::lazy_static! {
static ref $ids_name: std::sync::Mutex<std::collections::HashSet<usize>> =
std::sync::Mutex::new(std::collections::HashSet::new());
}
fn $func_name() -> usize {
let mut ids = $ids_name.lock().unwrap();
if ids.len() == usize::MAX {
panic!("Out of ids");
}
let id = loop {
let new_id = $id_name.fetch_update(
std::sync::atomic::Ordering::SeqCst,
std::sync::atomic::Ordering::SeqCst,
|mut id| {
while ids.iter().any(|k| *k == id) {
id += 1;
}
Some(id)
},
);
if let Ok(id) = new_id {
break id;
}
};
ids.insert(id);
id
}
};
}
pub(crate) use id_gen;

View File

@ -6,6 +6,8 @@ pub mod signaling;
#[cfg(feature = "x11rb_event_source")]
pub mod x11rb;
#[cfg(feature = "desktop")]
pub(crate) mod ids;
pub mod user_data;
pub use self::geometry::{Buffer, Coordinate, Logical, Physical, Point, Raw, Rectangle, Size};

View File

@ -449,7 +449,7 @@ mod tests {
#[test]
fn region_attributes_empty() {
let region = RegionAttributes { rects: vec![] };
assert_eq!(region.contains((0, 0)), false);
assert!(!region.contains((0, 0)));
}
#[test]
@ -458,7 +458,7 @@ mod tests {
rects: vec![(RectangleKind::Add, Rectangle::from_loc_and_size((0, 0), (10, 10)))],
};
assert_eq!(region.contains((0, 0)), true);
assert!(region.contains((0, 0)));
}
#[test]
@ -473,8 +473,8 @@ mod tests {
],
};
assert_eq!(region.contains((0, 0)), false);
assert_eq!(region.contains((5, 5)), true);
assert!(!region.contains((0, 0)));
assert!(region.contains((5, 5)));
}
#[test]
@ -490,8 +490,8 @@ mod tests {
],
};
assert_eq!(region.contains((0, 0)), false);
assert_eq!(region.contains((5, 5)), true);
assert_eq!(region.contains((2, 2)), true);
assert!(!region.contains((0, 0)));
assert!(region.contains((5, 5)));
assert!(region.contains((2, 2)));
}
}

View File

@ -61,7 +61,7 @@ use wayland_server::protocol::{
};
use wayland_server::{
protocol::wl_output::{Mode as WMode, WlOutput},
Client, Display, Filter, Global, Main,
Client, Display, Filter, Global, Main, UserDataMap,
};
use slog::{info, o, trace, warn};
@ -100,7 +100,7 @@ pub struct PhysicalProperties {
}
#[derive(Debug)]
struct Inner {
pub(crate) struct Inner {
name: String,
log: ::slog::Logger,
instances: Vec<WlOutput>,
@ -115,6 +115,8 @@ struct Inner {
xdg_output: Option<XdgOutput>,
}
type InnerType = Arc<(Mutex<Inner>, UserDataMap)>;
impl Inner {
fn new_global(&mut self, output: WlOutput) {
trace!(self.log, "New global instantiated.");
@ -166,9 +168,9 @@ impl Inner {
///
/// This handle is stored in the event loop, and allows you to notify clients
/// about any change in the properties of this output.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Output {
inner: Arc<Mutex<Inner>>,
pub(crate) inner: InnerType,
}
impl Output {
@ -190,19 +192,22 @@ impl Output {
info!(log, "Creating new wl_output"; "name" => &name);
let inner = Arc::new(Mutex::new(Inner {
name,
log,
instances: Vec::new(),
physical,
location: (0, 0).into(),
transform: Transform::Normal,
scale: 1,
modes: Vec::new(),
current_mode: None,
preferred_mode: None,
xdg_output: None,
}));
let inner = Arc::new((
Mutex::new(Inner {
name,
log,
instances: Vec::new(),
physical,
location: (0, 0).into(),
transform: Transform::Normal,
scale: 1,
modes: Vec::new(),
current_mode: None,
preferred_mode: None,
xdg_output: None,
}),
UserDataMap::default(),
));
let output = Output { inner: inner.clone() };
@ -210,8 +215,9 @@ impl Output {
3,
Filter::new(move |(output, _version): (Main<WlOutput>, _), _, _| {
output.assign_destructor(Filter::new(|output: WlOutput, _, _| {
let inner = output.as_ref().user_data().get::<Arc<Mutex<Inner>>>().unwrap();
let inner = output.as_ref().user_data().get::<InnerType>().unwrap();
inner
.0
.lock()
.unwrap()
.instances
@ -221,7 +227,7 @@ impl Output {
let inner = inner.clone();
move || inner
});
inner.lock().unwrap().new_global(output.deref().clone());
inner.0.lock().unwrap().new_global(output.deref().clone());
}),
);
@ -233,7 +239,7 @@ impl Output {
output
.as_ref()
.user_data()
.get::<Arc<Mutex<Inner>>>()
.get::<InnerType>()
.cloned()
.map(|inner| Output { inner })
}
@ -243,7 +249,7 @@ impl Output {
/// If the provided mode was not previously known to this output, it is added to its
/// internal list.
pub fn set_preferred(&self, mode: Mode) {
let mut inner = self.inner.lock().unwrap();
let mut inner = self.inner.0.lock().unwrap();
inner.preferred_mode = Some(mode);
if inner.modes.iter().all(|&m| m != mode) {
inner.modes.push(mode);
@ -252,18 +258,38 @@ impl Output {
/// Adds a mode to the list of known modes to this output
pub fn add_mode(&self, mode: Mode) {
let mut inner = self.inner.lock().unwrap();
let mut inner = self.inner.0.lock().unwrap();
if inner.modes.iter().all(|&m| m != mode) {
inner.modes.push(mode);
}
}
/// Returns the currently advertised mode of the output
pub fn current_mode(&self) -> Option<Mode> {
self.inner.0.lock().unwrap().current_mode
}
/// Returns the currently advertised transformation of the output
pub fn current_transform(&self) -> Transform {
self.inner.0.lock().unwrap().transform
}
/// Returns the currenly advertised scale of the output
pub fn current_scale(&self) -> i32 {
self.inner.0.lock().unwrap().scale
}
/// Returns the name of the output
pub fn name(&self) -> String {
self.inner.0.lock().unwrap().name.clone()
}
/// Removes a mode from the list of known modes
///
/// It will not de-advertise it from existing clients (the protocol does not
/// allow it), but it won't be advertised to now clients from now on.
pub fn delete_mode(&self, mode: Mode) {
let mut inner = self.inner.lock().unwrap();
let mut inner = self.inner.0.lock().unwrap();
inner.modes.retain(|&m| m != mode);
if inner.current_mode == Some(mode) {
inner.current_mode = None;
@ -289,7 +315,7 @@ impl Output {
new_scale: Option<i32>,
new_location: Option<Point<i32, Logical>>,
) {
let mut inner = self.inner.lock().unwrap();
let mut inner = self.inner.0.lock().unwrap();
if let Some(mode) = new_mode {
if inner.modes.iter().all(|&m| m != mode) {
inner.modes.push(mode);
@ -337,6 +363,7 @@ impl Output {
/// Check is given [`wl_output`](WlOutput) instance is managed by this [`Output`].
pub fn owns(&self, output: &WlOutput) -> bool {
self.inner
.0
.lock()
.unwrap()
.instances
@ -351,6 +378,7 @@ impl Output {
F: FnMut(&WlOutput),
{
self.inner
.0
.lock()
.unwrap()
.instances
@ -377,4 +405,17 @@ impl Output {
self.with_client_outputs(client, |output| surface.leave(output))
}
}
/// Returns the user data of this output
pub fn user_data(&self) -> &UserDataMap {
&self.inner.1
}
}
impl PartialEq for Output {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for Output {}

View File

@ -159,7 +159,7 @@ where
output: wl_output,
} => {
let output = Output::from_resource(&wl_output).unwrap();
let mut inner = output.inner.lock().unwrap();
let mut inner = output.inner.0.lock().unwrap();
if inner.xdg_output.is_none() {
inner.xdg_output = Some(XdgOutput::new(&inner, log.clone()));

View File

@ -469,6 +469,11 @@ impl KeyboardHandle {
.unwrap_or(false)
}
/// Check if keyboard has focus
pub fn is_focused(&self) -> bool {
self.arc.internal.borrow_mut().focus.is_some()
}
/// Register a new keyboard to this handler
///
/// The keymap will automatically be sent to it

View File

@ -50,7 +50,7 @@ impl Default for Layer {
/// - some applications are not interested in keyboard events
/// and not allowing them to be focused can improve the desktop experience
/// - some applications will want to take exclusive keyboard focus.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyboardInteractivity {
/// This value indicates that this surface is not interested in keyboard events
/// and the compositor should never assign it the keyboard focus.

View File

@ -18,6 +18,7 @@ use smithay::{
Client, Display,
},
},
utils::Rectangle,
wayland::{
output::{Mode, PhysicalProperties},
seat::CursorImageStatus,
@ -103,7 +104,10 @@ pub fn run(channel: Channel<WlcsEvent>) {
renderer
.render((800, 600).into(), Transform::Normal, |renderer, frame| {
frame.clear([0.8, 0.8, 0.9, 1.0])?;
frame.clear(
[0.8, 0.8, 0.9, 1.0],
&[Rectangle::from_loc_and_size((0, 0), (800, 600))],
)?;
// draw the windows
draw_windows(

View File

@ -97,7 +97,7 @@ impl Frame for DummyFrame {
type Error = SwapBuffersError;
type TextureId = DummyTexture;
fn clear(&mut self, _color: [f32; 4]) -> Result<(), Self::Error> {
fn clear(&mut self, _color: [f32; 4], _damage: &[Rectangle<i32, Physical>]) -> Result<(), Self::Error> {
Ok(())
}
@ -106,11 +106,16 @@ impl Frame for DummyFrame {
_texture: &Self::TextureId,
_src: Rectangle<i32, Buffer>,
_dst: Rectangle<f64, Physical>,
_damage: &[Rectangle<i32, Physical>],
_src_transform: Transform,
_alpha: f32,
) -> Result<(), Self::Error> {
Ok(())
}
fn transformation(&self) -> Transform {
Transform::Normal
}
}
pub struct DummyTexture {