Implement window resize animations

This commit is contained in:
Ivan Molodetskikh
2024-04-13 11:07:23 +04:00
parent 4fd9300bdb
commit 71be19b234
21 changed files with 1433 additions and 89 deletions
Generated
+2 -2
View File
@@ -3115,7 +3115,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smithay"
version = "0.3.0"
source = "git+https://github.com/Smithay/smithay.git#e237b077bd922e17849eff91ba05853c7a68f958"
source = "git+https://github.com/Smithay/smithay.git#c5e9a697e41f50dc56b918d9ef1e3d2e52c84ac0"
dependencies = [
"appendlist",
"bitflags 2.5.0",
@@ -3187,7 +3187,7 @@ dependencies = [
[[package]]
name = "smithay-drm-extras"
version = "0.1.0"
source = "git+https://github.com/Smithay/smithay.git#e237b077bd922e17849eff91ba05853c7a68f958"
source = "git+https://github.com/Smithay/smithay.git#c5e9a697e41f50dc56b918d9ef1e3d2e52c84ac0"
dependencies = [
"drm",
"edid-rs",
+14
View File
@@ -489,6 +489,8 @@ pub struct Animations {
pub window_open: Animation,
#[knuffel(child, default = Animation::default_window_close())]
pub window_close: Animation,
#[knuffel(child, default = Animation::default_window_resize())]
pub window_resize: Animation,
#[knuffel(child, default = Animation::default_config_notification_open_close())]
pub config_notification_open_close: Animation,
}
@@ -503,6 +505,7 @@ impl Default for Animations {
window_movement: Animation::default_window_movement(),
window_open: Animation::default_window_open(),
window_close: Animation::default_window_close(),
window_resize: Animation::default_window_resize(),
config_notification_open_close: Animation::default_config_notification_open_close(),
}
}
@@ -592,6 +595,17 @@ impl Animation {
}),
}
}
pub const fn default_window_resize() -> Self {
Self {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
+5 -4
View File
@@ -20,7 +20,7 @@ impl Tile {
pub fn freeform(size: Size<i32, Logical>) -> Self {
let window = TestWindow::freeform(0);
let mut rv = Self::with_window(window);
rv.tile.request_tile_size(size);
rv.tile.request_tile_size(size, false);
rv.window.communicate();
rv
}
@@ -28,7 +28,7 @@ impl Tile {
pub fn fixed_size(size: Size<i32, Logical>) -> Self {
let window = TestWindow::fixed_size(0);
let mut rv = Self::with_window(window);
rv.tile.request_tile_size(size);
rv.tile.request_tile_size(size, false);
rv.window.communicate();
rv
}
@@ -37,7 +37,7 @@ impl Tile {
let window = TestWindow::fixed_size(0);
window.set_csd_shadow_width(64);
let mut rv = Self::with_window(window);
rv.tile.request_tile_size(size);
rv.tile.request_tile_size(size, false);
rv.window.communicate();
rv
}
@@ -84,7 +84,8 @@ impl Tile {
impl TestCase for Tile {
fn resize(&mut self, width: i32, height: i32) {
self.tile.request_tile_size(Size::from((width, height)));
self.tile
.request_tile_size(Size::from((width, height)), false);
self.window.communicate();
}
+7 -7
View File
@@ -13,23 +13,23 @@ pub struct Window {
impl Window {
pub fn freeform(size: Size<i32, Logical>) -> Self {
let window = TestWindow::freeform(0);
window.request_size(size);
let mut window = TestWindow::freeform(0);
window.request_size(size, false);
window.communicate();
Self { window }
}
pub fn fixed_size(size: Size<i32, Logical>) -> Self {
let window = TestWindow::fixed_size(0);
window.request_size(size);
let mut window = TestWindow::fixed_size(0);
window.request_size(size, false);
window.communicate();
Self { window }
}
pub fn fixed_size_with_csd_shadow(size: Size<i32, Logical>) -> Self {
let window = TestWindow::fixed_size(0);
let mut window = TestWindow::fixed_size(0);
window.set_csd_shadow_width(64);
window.request_size(size);
window.request_size(size, false);
window.communicate();
Self { window }
}
@@ -37,7 +37,7 @@ impl Window {
impl TestCase for Window {
fn resize(&mut self, width: i32, height: i32) {
self.window.request_size(Size::from((width, height)));
self.window.request_size(Size::from((width, height)), false);
self.window.communicate();
}
+2 -1
View File
@@ -11,7 +11,7 @@ mod imp {
use anyhow::{ensure, Context};
use gtk::gdk;
use gtk::prelude::*;
use niri::render_helpers::shaders;
use niri::render_helpers::{resources, shaders};
use niri::utils::get_monotonic_time;
use smithay::backend::egl::ffi::egl;
use smithay::backend::egl::EGLContext;
@@ -194,6 +194,7 @@ mod imp {
let mut renderer = GlesRenderer::with_capabilities(egl_context, capabilities)
.context("error creating GlesRenderer")?;
resources::init(&mut renderer);
shaders::init(&mut renderer);
Ok(renderer)
+13 -3
View File
@@ -2,7 +2,9 @@ use std::cell::RefCell;
use std::cmp::{max, min};
use std::rc::Rc;
use niri::layout::{LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot};
use niri::layout::{
AnimationSnapshot, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
};
use niri::render_helpers::renderer::NiriRenderer;
use niri::render_helpers::{RenderSnapshot, RenderTarget};
use niri::window::ResolvedWindowRules;
@@ -177,7 +179,7 @@ impl LayoutElement for TestWindow {
RenderSnapshot::default()
}
fn request_size(&self, size: Size<i32, Logical>) {
fn request_size(&mut self, size: Size<i32, Logical>, _animate: bool) {
self.inner.borrow_mut().requested_size = Some(size);
self.inner.borrow_mut().pending_fullscreen = false;
}
@@ -214,7 +216,7 @@ impl LayoutElement for TestWindow {
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
fn send_pending_configure(&self) {}
fn send_pending_configure(&mut self) {}
fn is_fullscreen(&self) -> bool {
false
@@ -230,4 +232,12 @@ impl LayoutElement for TestWindow {
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
&EMPTY
}
fn animation_snapshot(&self) -> Option<&AnimationSnapshot> {
None
}
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot> {
None
}
}
+2 -1
View File
@@ -59,7 +59,7 @@ use super::{IpcOutputMap, RenderResult};
use crate::frame_clock::FrameClock;
use crate::niri::{Niri, RedrawState, State};
use crate::render_helpers::renderer::AsGlesRenderer;
use crate::render_helpers::{shaders, RenderTarget};
use crate::render_helpers::{resources, shaders, RenderTarget};
use crate::utils::{get_monotonic_time, logical_output};
const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888];
@@ -490,6 +490,7 @@ impl Tty {
warn!("error binding wl-display in EGL: {err:?}");
}
resources::init(renderer.as_gles_renderer());
shaders::init(renderer.as_gles_renderer());
// Create the dmabuf global.
+2 -1
View File
@@ -19,7 +19,7 @@ use smithay::reexports::winit::window::WindowBuilder;
use super::{IpcOutputMap, RenderResult};
use crate::niri::{Niri, RedrawState, State};
use crate::render_helpers::{shaders, RenderTarget};
use crate::render_helpers::{resources, shaders, RenderTarget};
use crate::utils::{get_monotonic_time, logical_output};
pub struct Winit {
@@ -130,6 +130,7 @@ impl Winit {
warn!("error binding renderer wl_display: {err}");
}
resources::init(renderer);
shaders::init(renderer);
niri.add_output(self.output.clone(), None);
+30 -3
View File
@@ -815,16 +815,34 @@ fn unconstrain_with_padding(
pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId {
add_pre_commit_hook::<State, _>(toplevel.wl_surface(), move |state, _dh, surface| {
let _span = tracy_client::span!("mapped toplevel pre-commit");
let Some((mapped, _)) = state.niri.layout.find_window_and_output_mut(surface) else {
error!("pre-commit hook for mapped surfaces must be removed upon unmapping");
return;
};
let got_unmapped = with_states(surface, |states| {
let attrs = states.cached_state.current::<SurfaceAttributes>();
matches!(attrs.buffer, Some(BufferAssignment::Removed))
let (got_unmapped, commit_serial) = with_states(surface, |states| {
let attrs = states.cached_state.pending::<SurfaceAttributes>();
let got_unmapped = matches!(attrs.buffer, Some(BufferAssignment::Removed));
let role = states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();
(got_unmapped, role.configure_serial)
});
let animate = if let Some(serial) = commit_serial {
mapped.should_animate_commit(serial)
} else {
error!("commit on a mapped surface without a configured serial");
false
};
if got_unmapped {
state.backend.with_primary_renderer(|renderer| {
mapped.render_and_store_snapshot(renderer);
@@ -832,6 +850,15 @@ pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId
} else {
// The toplevel remains mapped; clear any cached render snapshot.
let _ = mapped.take_last_render();
if animate {
state.backend.with_primary_renderer(|renderer| {
mapped.store_animation_snapshot(renderer);
});
let window = mapped.window.clone();
state.niri.layout.prepare_for_resize_animation(&window);
}
}
})
}
+52 -4
View File
@@ -60,6 +60,9 @@ pub mod monitor;
pub mod tile;
pub mod workspace;
/// Size changes up to this many pixels don't animate.
pub const RESIZE_ANIMATION_THRESHOLD: i32 = 10;
niri_render_elements! {
LayoutElementRenderElement<R> => {
Wayland = WaylandSurfaceRenderElement<R>,
@@ -70,6 +73,15 @@ niri_render_elements! {
pub type LayoutElementRenderSnapshot =
RenderSnapshot<BakedBuffer<TextureBuffer<GlesTexture>>, BakedBuffer<SolidColorBuffer>>;
/// Snapshot of an element for animation.
#[derive(Debug)]
pub struct AnimationSnapshot {
/// Snapshot of the render.
pub render: LayoutElementRenderSnapshot,
/// Visual size of the element at the point of the snapshot.
pub size: Size<i32, Logical>,
}
pub trait LayoutElement {
/// Type that can be used as a unique ID of this element.
type Id: PartialEq;
@@ -108,7 +120,7 @@ pub trait LayoutElement {
fn take_last_render(&self) -> LayoutElementRenderSnapshot;
fn request_size(&self, size: Size<i32, Logical>);
fn request_size(&mut self, size: Size<i32, Logical>, animate: bool);
fn request_fullscreen(&self, size: Size<i32, Logical>);
fn min_size(&self) -> Size<i32, Logical>;
fn max_size(&self) -> Size<i32, Logical>;
@@ -121,7 +133,7 @@ pub trait LayoutElement {
fn set_activated(&mut self, active: bool);
fn set_bounds(&self, bounds: Size<i32, Logical>);
fn send_pending_configure(&self);
fn send_pending_configure(&mut self);
/// Whether the element is currently fullscreen.
///
@@ -137,6 +149,9 @@ pub trait LayoutElement {
/// Runs periodic clean-up tasks.
fn refresh(&self);
fn animation_snapshot(&self) -> Option<&AnimationSnapshot>;
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot>;
}
#[derive(Debug)]
@@ -1787,6 +1802,31 @@ impl<W: LayoutElement> Layout<W> {
}
}
pub fn prepare_for_resize_animation(&mut self, window: &W::Id) {
let _span = tracy_client::span!("Layout::prepare_for_resize_animation");
match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => {
for mon in monitors {
for ws in &mut mon.workspaces {
if ws.has_window(window) {
ws.prepare_for_resize_animation(window);
return;
}
}
}
}
MonitorSet::NoOutputs { workspaces, .. } => {
for ws in workspaces {
if ws.has_window(window) {
ws.prepare_for_resize_animation(window);
return;
}
}
}
}
}
pub fn refresh(&mut self) {
let _span = tracy_client::span!("Layout::refresh");
@@ -1930,7 +1970,7 @@ mod tests {
RenderSnapshot::default()
}
fn request_size(&self, size: Size<i32, Logical>) {
fn request_size(&mut self, size: Size<i32, Logical>, _animate: bool) {
self.0.requested_size.set(Some(size));
self.0.pending_fullscreen.set(false);
}
@@ -1967,7 +2007,7 @@ mod tests {
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
fn send_pending_configure(&self) {}
fn send_pending_configure(&mut self) {}
fn is_fullscreen(&self) -> bool {
false
@@ -1983,6 +2023,14 @@ mod tests {
static EMPTY: ResolvedWindowRules = ResolvedWindowRules::empty();
&EMPTY
}
fn animation_snapshot(&self) -> Option<&AnimationSnapshot> {
None
}
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot> {
None
}
}
fn arbitrary_bbox() -> impl Strategy<Value = Rectangle<i32, Logical>> {
+334 -24
View File
@@ -1,21 +1,31 @@
use std::cell::OnceCell;
use std::cmp::max;
use std::rc::Rc;
use std::time::Duration;
use niri_config::BlockOutFrom;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
use smithay::backend::renderer::element::utils::RescaleRenderElement;
use smithay::backend::renderer::element::{Element, Kind};
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use super::focus_ring::{FocusRing, FocusRingRenderElement};
use super::{LayoutElement, LayoutElementRenderElement, Options};
use super::{
AnimationSnapshot, LayoutElement, LayoutElementRenderElement, Options,
RESIZE_ANIMATION_THRESHOLD,
};
use crate::animation::Animation;
use crate::niri_render_elements;
use crate::render_helpers::crossfade::CrossfadeRenderElement;
use crate::render_helpers::offscreen::OffscreenRenderElement;
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::{RenderSnapshot, RenderTarget, ToRenderElement};
use crate::render_helpers::shaders::Shaders;
use crate::render_helpers::{
render_to_encompassing_texture, RenderSnapshot, RenderTarget, ToRenderElement,
};
/// Toplevel window with decorations.
#[derive(Debug)]
@@ -47,6 +57,12 @@ pub struct Tile<W: LayoutElement> {
/// The animation upon opening a window.
open_animation: Option<Animation>,
/// The animation of the window resizing.
resize_animation: Option<ResizeAnimation>,
/// The animation of a tile visually moving.
move_animation: Option<MoveAnimation>,
/// Configurable properties of the layout.
options: Rc<Options>,
}
@@ -57,17 +73,41 @@ niri_render_elements! {
FocusRing = FocusRingRenderElement,
SolidColor = SolidColorRenderElement,
Offscreen = RescaleRenderElement<OffscreenRenderElement>,
Crossfade = CrossfadeRenderElement,
}
}
niri_render_elements! {
TileSnapshotContentsRenderElement => {
Texture = PrimaryGpuTextureRenderElement,
SolidColor = SolidColorRenderElement,
}
}
niri_render_elements! {
TileSnapshotRenderElement => {
Texture = PrimaryGpuTextureRenderElement,
Contents = RescaleRenderElement<TileSnapshotContentsRenderElement>,
FocusRing = FocusRingRenderElement,
SolidColor = SolidColorRenderElement,
}
}
#[derive(Debug)]
struct ResizeAnimation {
anim: Animation,
size_from: Size<i32, Logical>,
snapshot: AnimationSnapshot,
/// Snapshot rendered into a texture (happens lazily).
snapshot_texture: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
snapshot_blocked_out_texture: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
}
#[derive(Debug)]
struct MoveAnimation {
anim: Animation,
from: Point<i32, Logical>,
}
impl<W: LayoutElement> Tile<W> {
pub fn new(window: W, options: Rc<Options>) -> Self {
Self {
@@ -78,6 +118,8 @@ impl<W: LayoutElement> Tile<W> {
fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]),
fullscreen_size: Default::default(),
open_animation: None,
resize_animation: None,
move_animation: None,
options,
}
}
@@ -93,16 +135,76 @@ impl<W: LayoutElement> Tile<W> {
if self.fullscreen_size != Size::from((0, 0)) {
self.is_fullscreen = self.window.is_fullscreen();
}
if let Some(animate_from) = self.window.take_animation_snapshot() {
let size_from = if let Some(resize) = self.resize_animation.take() {
// Compute like in animated_window_size(), but using the snapshot geometry (since
// the current one is already overwritten).
let mut size = animate_from.size;
let val = resize.anim.value();
let size_from = resize.size_from;
size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32;
size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32;
size
} else {
animate_from.size
};
let change = self.window.size().to_point() - size_from.to_point();
let change = max(change.x.abs(), change.y.abs());
if change > RESIZE_ANIMATION_THRESHOLD {
let anim = Animation::new(
0.,
1.,
0.,
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
);
self.resize_animation = Some(ResizeAnimation {
anim,
size_from,
snapshot: animate_from,
snapshot_texture: OnceCell::new(),
snapshot_blocked_out_texture: OnceCell::new(),
});
} else {
self.resize_animation = None;
}
}
}
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
if let Some(anim) = &mut self.open_animation {
anim.set_current_time(current_time);
if anim.is_done() {
self.open_animation = None;
}
}
if let Some(resize) = &mut self.resize_animation {
resize.anim.set_current_time(current_time);
if resize.anim.is_done() {
self.resize_animation = None;
}
}
if let Some(move_) = &mut self.move_animation {
move_.anim.set_current_time(current_time);
if move_.anim.is_done() {
self.move_animation = None;
}
}
let draw_border_with_background = self
.window
.rules()
.draw_border_with_background
.unwrap_or_else(|| !self.window.has_ssd());
self.border
.update(self.window.size(), !draw_border_with_background);
.update(self.animated_window_size(), !draw_border_with_background);
self.border.set_active(is_active);
let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
@@ -111,22 +213,24 @@ impl<W: LayoutElement> Tile<W> {
draw_border_with_background
};
self.focus_ring
.update(self.tile_size(), !draw_focus_ring_with_background);
.update(self.animated_tile_size(), !draw_focus_ring_with_background);
self.focus_ring.set_active(is_active);
match &mut self.open_animation {
Some(anim) => {
anim.set_current_time(current_time);
if anim.is_done() {
self.open_animation = None;
}
}
None => (),
}
}
pub fn are_animations_ongoing(&self) -> bool {
self.open_animation.is_some()
|| self.resize_animation.is_some()
|| self.move_animation.is_some()
}
pub fn render_offset(&self) -> Point<i32, Logical> {
let mut offset = Point::from((0., 0.));
if let Some(move_) = &self.move_animation {
offset += move_.from.to_f64().upscale(move_.anim.value());
}
offset.to_i32_round()
}
pub fn start_open_animation(&mut self) {
@@ -143,6 +247,24 @@ impl<W: LayoutElement> Tile<W> {
&self.open_animation
}
pub fn resize_animation(&self) -> Option<&Animation> {
self.resize_animation.as_ref().map(|resize| &resize.anim)
}
pub fn animate_move_from_with_config(
&mut self,
from: Point<i32, Logical>,
config: niri_config::Animation,
default: niri_config::Animation,
) {
let current_offset = self.render_offset();
self.move_animation = Some(MoveAnimation {
anim: Animation::new(1., 0., 0., config, default),
from: from + current_offset,
});
}
pub fn window(&self) -> &W {
&self.window
}
@@ -217,6 +339,39 @@ impl<W: LayoutElement> Tile<W> {
self.window.size()
}
fn animated_window_size(&self) -> Size<i32, Logical> {
let mut size = self.window.size();
if let Some(resize) = &self.resize_animation {
let val = resize.anim.value();
let size_from = resize.size_from;
size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32;
size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32;
}
size
}
fn animated_tile_size(&self) -> Size<i32, Logical> {
let mut size = self.animated_window_size();
if self.is_fullscreen {
// Normally we'd just return the fullscreen size here, but this makes things a bit
// nicer if a fullscreen window is bigger than the fullscreen size for some reason.
size.w = max(size.w, self.fullscreen_size.w);
size.h = max(size.h, self.fullscreen_size.h);
return size;
}
if let Some(width) = self.effective_border_width() {
size.w = size.w.saturating_add(width * 2);
size.h = size.h.saturating_add(width * 2);
}
size
}
pub fn buf_loc(&self) -> Point<i32, Logical> {
let mut loc = Point::from((0, 0));
loc += self.window_loc();
@@ -234,7 +389,7 @@ impl<W: LayoutElement> Tile<W> {
activation_region.to_f64().contains(point)
}
pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>) {
pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>, animate: bool) {
// Can't go through effective_border_width() because we might be fullscreen.
if !self.border.is_off() {
let width = self.border.width();
@@ -242,7 +397,7 @@ impl<W: LayoutElement> Tile<W> {
size.h = max(1, size.h - width * 2);
}
self.window.request_size(size);
self.window.request_size(size, animate);
}
pub fn tile_width_for_window_width(&self, size: i32) -> i32 {
@@ -330,11 +485,85 @@ impl<W: LayoutElement> Tile<W> {
self.window.rules().opacity.unwrap_or(1.).clamp(0., 1.)
};
let rv = self
.window
.render(renderer, location + self.window_loc(), scale, alpha, target)
let window_loc = self.window_loc();
let window_size = self.window_size();
let animated_window_size = self.animated_window_size();
let window_render_loc = location + window_loc;
let area = Rectangle::from_loc_and_size(window_render_loc, animated_window_size);
let gles_renderer = renderer.as_gles_renderer();
// If we're resizing, try to render a crossfade, or a fallback.
let mut crossfade = None;
let mut crossfade_fallback = None;
if let Some(resize) = &self.resize_animation {
if Shaders::get(gles_renderer).crossfade.is_some() {
if let Some(texture_from) = resize.rendered_texture(gles_renderer, scale, target) {
let window_elements =
self.window
.render(gles_renderer, Point::from((0, 0)), scale, 1., target);
let current = render_to_encompassing_texture(
gles_renderer,
scale,
Transform::Normal,
Fourcc::Abgr8888,
&window_elements,
)
.map_err(|err| warn!("error rendering window to texture: {err:?}"))
.ok();
if let Some((texture_current, _sync_point, texture_current_geo)) = current {
let elem = CrossfadeRenderElement::new(
gles_renderer,
area,
scale,
texture_from.clone(),
resize.snapshot.size,
(texture_current, texture_current_geo),
window_size,
resize.anim.clamped_value().clamp(0., 1.) as f32,
alpha,
)
.expect("we checked the crossfade shader above");
self.window
.set_offscreen_element_id(Some(elem.id().clone()));
crossfade = Some(elem.into());
}
}
}
if crossfade.is_none() {
let fallback_buffer = SolidColorBuffer::new(area.size, [1., 0., 0., 1.]);
crossfade_fallback = Some(
SolidColorRenderElement::from_buffer(
&fallback_buffer,
area.loc.to_physical_precise_round(scale),
scale,
alpha,
Kind::Unspecified,
)
.into(),
);
self.window.set_offscreen_element_id(None);
}
}
// If we're not resizing, render the window itself.
let mut window = None;
if crossfade.is_none() && crossfade_fallback.is_none() {
window = Some(
self.window
.render(renderer, window_render_loc, scale, alpha, target)
.into_iter()
.map(Into::into);
.map(Into::into),
);
}
let rv = crossfade
.into_iter()
.chain(crossfade_fallback)
.chain(window.into_iter().flatten());
let elem = self.effective_border_width().map(|width| {
self.border
@@ -422,7 +651,7 @@ impl<W: LayoutElement> Tile<W> {
contents: Vec<C>,
) -> Vec<TileSnapshotRenderElement>
where
E: Into<TileSnapshotRenderElement>,
E: Into<TileSnapshotContentsRenderElement>,
C: ToRenderElement<RenderElement = E>,
{
let alpha = if self.is_fullscreen {
@@ -431,10 +660,18 @@ impl<W: LayoutElement> Tile<W> {
self.window.rules().opacity.unwrap_or(1.).clamp(0., 1.)
};
let window_size = self.window_size();
let animated_window_size = self.animated_window_size();
let animated_scale = animated_window_size.to_f64() / window_size.to_f64();
let mut rv = vec![];
for baked in contents {
let elem = baked.to_render_element(self.window_loc(), scale, alpha, Kind::Unspecified);
let elem: TileSnapshotContentsRenderElement = elem.into();
let origin = self.window_loc().to_physical_precise_round(scale);
let elem = RescaleRenderElement::from_element(elem, origin, animated_scale);
rv.push(elem.into());
}
@@ -483,3 +720,76 @@ impl<W: LayoutElement> Tile<W> {
}
}
}
impl ResizeAnimation {
fn rendered_texture(
&self,
renderer: &mut GlesRenderer,
scale: Scale<f64>,
target: RenderTarget,
) -> &Option<(GlesTexture, Rectangle<i32, Physical>)> {
let block_out = match self.snapshot.render.block_out_from {
None => false,
Some(BlockOutFrom::Screencast) => target == RenderTarget::Screencast,
Some(BlockOutFrom::ScreenCapture) => target != RenderTarget::Output,
};
if block_out {
self.snapshot_blocked_out_texture.get_or_init(|| {
let _span = tracy_client::span!("ResizeAnimation::rendered_texture");
let elements: Vec<_> = self
.snapshot
.render
.blocked_out_contents
.iter()
.map(|baked| {
baked.to_render_element(Point::from((0, 0)), scale, 1., Kind::Unspecified)
})
.collect();
match render_to_encompassing_texture(
renderer,
scale,
Transform::Normal,
Fourcc::Abgr8888,
&elements,
) {
Ok((texture, _sync_point, geo)) => Some((texture, geo)),
Err(err) => {
warn!("error rendering snapshot to texture: {err:?}");
None
}
}
})
} else {
self.snapshot_texture.get_or_init(|| {
let _span = tracy_client::span!("ResizeAnimation::rendered_texture");
let elements: Vec<_> = self
.snapshot
.render
.contents
.iter()
.map(|baked| {
baked.to_render_element(Point::from((0, 0)), scale, 1., Kind::Unspecified)
})
.collect();
match render_to_encompassing_texture(
renderer,
scale,
Transform::Normal,
Fourcc::Abgr8888,
&elements,
) {
Ok((texture, _sync_point, geo)) => Some((texture, geo)),
Err(err) => {
warn!("error rendering snapshot to texture: {err:?}");
None
}
}
})
}
}
}
+105 -24
View File
@@ -190,6 +190,9 @@ pub struct Column<W: LayoutElement> {
/// Animation of the render offset during window swapping.
move_animation: Option<Animation>,
/// Width right before a resize animation on one of the windows of the column.
pub width_before_resize: Option<i32>,
/// Latest known view size for this column's workspace.
view_size: Size<i32, Logical>,
@@ -815,7 +818,7 @@ impl<W: LayoutElement> Workspace<W> {
}
column.active_tile_idx = min(column.active_tile_idx, column.tiles.len() - 1);
column.update_tile_sizes();
column.update_tile_sizes(false);
window
}
@@ -879,16 +882,48 @@ impl<W: LayoutElement> Workspace<W> {
}
pub fn update_window(&mut self, window: &W::Id) {
let (idx, column) = self
let (col_idx, column) = self
.columns
.iter_mut()
.enumerate()
.find(|(_, col)| col.contains(window))
.unwrap();
column.update_window(window);
column.update_tile_sizes();
let tile_idx = column
.tiles
.iter()
.position(|tile| tile.window().id() == window)
.unwrap();
if idx == self.active_column_idx
let offset = column
.width_before_resize
.take()
.map_or(0, |prev| prev - column.width());
column.update_window(window);
column.update_tile_sizes(false);
// Move other columns in tandem with resizing.
if column.tiles[tile_idx].resize_animation().is_some() && offset != 0 {
if self.active_column_idx <= col_idx {
for col in &mut self.columns[col_idx + 1..] {
col.animate_move_from_with_config(
offset,
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
);
}
} else {
for col in &mut self.columns[..col_idx] {
col.animate_move_from_with_config(
-offset,
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
);
}
}
}
if col_idx == self.active_column_idx
&& !matches!(self.view_offset_adj, Some(ViewOffsetAdjustment::Gesture(_)))
{
// We might need to move the view to ensure the resized window is still visible.
@@ -896,7 +931,7 @@ impl<W: LayoutElement> Workspace<W> {
// FIXME: we will want to skip the animation in some cases here to make continuously
// resizing windows not look janky.
self.animate_view_offset_to_column(current_x, idx, None);
self.animate_view_offset_to_column(current_x, col_idx, None);
}
}
@@ -1005,6 +1040,16 @@ impl<W: LayoutElement> Workspace<W> {
}
}
pub fn prepare_for_resize_animation(&mut self, window: &W::Id) {
let column = self
.columns
.iter_mut()
.find(|col| col.contains(window))
.unwrap();
column.width_before_resize = Some(column.width());
}
#[cfg(test)]
pub fn verify_invariants(&self) {
assert!(self.view_size.w > 0);
@@ -1265,8 +1310,9 @@ impl<W: LayoutElement> Workspace<W> {
// Start with the active window since it's drawn on top.
let col = &self.columns[self.active_column_idx];
let tile = &col.tiles[col.active_tile_idx];
let tile_pos =
Point::from((-self.view_offset, col.tile_y(col.active_tile_idx))) + col.render_offset();
let tile_pos = Point::from((-self.view_offset, col.tile_y(col.active_tile_idx)))
+ col.render_offset()
+ tile.render_offset();
let first = iter::once((tile, tile_pos));
// Next, the rest of the tiles in the active column, since it should be drawn on top as a
@@ -1280,7 +1326,9 @@ impl<W: LayoutElement> Workspace<W> {
return None;
}
let tile_pos = Point::from((-self.view_offset, y)) + col.render_offset();
let tile_pos = Point::from((-self.view_offset, y))
+ col.render_offset()
+ tile.render_offset();
Some((tile, tile_pos))
});
@@ -1305,7 +1353,7 @@ impl<W: LayoutElement> Workspace<W> {
})
.flat_map(move |(col, x)| {
zip(&col.tiles, col.tile_ys()).map(move |(tile, y)| {
let tile_pos = Point::from((x, y)) + col.render_offset();
let tile_pos = Point::from((x, y)) + col.render_offset() + tile.render_offset();
(tile, tile_pos)
})
});
@@ -1409,7 +1457,7 @@ impl<W: LayoutElement> Workspace<W> {
let window = col.tiles.remove(tile_idx).into_window();
col.heights.remove(tile_idx);
col.active_tile_idx = min(col.active_tile_idx, col.tiles.len() - 1);
col.update_tile_sizes();
col.update_tile_sizes(false);
let width = col.width;
let is_full_width = col.is_full_width;
@@ -1744,6 +1792,7 @@ impl<W: LayoutElement> Column<W> {
is_full_width,
is_fullscreen: false,
move_animation: None,
width_before_resize: None,
view_size,
working_area,
options,
@@ -1768,7 +1817,7 @@ impl<W: LayoutElement> Column<W> {
self.view_size = size;
self.working_area = working_area;
self.update_tile_sizes();
self.update_tile_sizes(false);
}
fn update_config(&mut self, options: Rc<Options>) {
@@ -1798,14 +1847,14 @@ impl<W: LayoutElement> Column<W> {
self.options = options;
if update_sizes {
self.update_tile_sizes();
self.update_tile_sizes(false);
}
}
fn set_width(&mut self, width: ColumnWidth) {
self.width = width;
self.is_full_width = false;
self.update_tile_sizes();
self.update_tile_sizes(true);
}
pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) {
@@ -1840,14 +1889,27 @@ impl<W: LayoutElement> Column<W> {
}
pub fn animate_move_from(&mut self, from_x_offset: i32) {
self.animate_move_from_with_config(
from_x_offset,
self.options.animations.window_movement,
niri_config::Animation::default_window_movement(),
);
}
pub fn animate_move_from_with_config(
&mut self,
from_x_offset: i32,
config: niri_config::Animation,
default: niri_config::Animation,
) {
let current_offset = self.move_animation.as_ref().map_or(0., Animation::value);
self.move_animation = Some(Animation::new(
f64::from(from_x_offset) + current_offset,
0.,
0.,
self.options.animations.window_movement,
niri_config::Animation::default_window_movement(),
config,
default,
));
}
@@ -1875,19 +1937,38 @@ impl<W: LayoutElement> Column<W> {
self.is_fullscreen = false;
self.tiles.push(tile);
self.heights.push(WindowHeight::Auto);
self.update_tile_sizes();
self.update_tile_sizes(false);
}
fn update_window(&mut self, window: &W::Id) {
let tile = self
let (tile_idx, tile) = self
.tiles
.iter_mut()
.find(|tile| tile.window().id() == window)
.enumerate()
.find(|(_, tile)| tile.window().id() == window)
.unwrap();
let height = tile.window().size().h;
let offset = tile
.window()
.animation_snapshot()
.map_or(0, |from| from.size.h - height);
tile.update_window();
// Move windows below in tandem with resizing.
if tile.resize_animation().is_some() && offset != 0 {
for tile in &mut self.tiles[tile_idx + 1..] {
tile.animate_move_from_with_config(
Point::from((0, offset)),
self.options.animations.window_resize,
niri_config::Animation::default_window_resize(),
);
}
}
}
fn update_tile_sizes(&mut self) {
fn update_tile_sizes(&mut self, animate: bool) {
if self.is_fullscreen {
self.tiles[0].request_fullscreen(self.view_size);
return;
@@ -2040,7 +2121,7 @@ impl<W: LayoutElement> Column<W> {
};
let size = Size::from((width, height));
tile.request_tile_size(size);
tile.request_tile_size(size, animate);
}
}
@@ -2127,7 +2208,7 @@ impl<W: LayoutElement> Column<W> {
fn toggle_full_width(&mut self) {
self.is_full_width = !self.is_full_width;
self.update_tile_sizes();
self.update_tile_sizes(true);
}
fn set_column_width(&mut self, change: SizeChange) {
@@ -2226,13 +2307,13 @@ impl<W: LayoutElement> Column<W> {
}
self.heights[self.active_tile_idx] = WindowHeight::Fixed(window_height.clamp(1, MAX_PX));
self.update_tile_sizes();
self.update_tile_sizes(true);
}
fn set_fullscreen(&mut self, is_fullscreen: bool) {
assert_eq!(self.tiles.len(), 1);
self.is_fullscreen = is_fullscreen;
self.update_tile_sizes();
self.update_tile_sizes(false);
}
pub fn window_y(&self, tile_idx: usize) -> i32 {
+156
View File
@@ -0,0 +1,156 @@
use std::collections::HashMap;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size, Transform};
use super::primary_gpu_pixel_shader_with_textures::PrimaryGpuPixelShaderWithTexturesRenderElement;
use super::renderer::AsGlesFrame;
use super::shaders::Shaders;
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
#[derive(Debug)]
pub struct CrossfadeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement);
impl CrossfadeRenderElement {
#[allow(clippy::too_many_arguments)]
pub fn new(
renderer: &mut GlesRenderer,
area: Rectangle<i32, Logical>,
scale: Scale<f64>,
texture_from: (GlesTexture, Rectangle<i32, Physical>),
size_from: Size<i32, Logical>,
texture_to: (GlesTexture, Rectangle<i32, Physical>),
size_to: Size<i32, Logical>,
amount: f32,
result_alpha: f32,
) -> Option<Self> {
let (texture_from, texture_from_geo) = texture_from;
let (texture_to, texture_to_geo) = texture_to;
let scale_from = area.size.to_f64() / size_from.to_f64();
let scale_to = area.size.to_f64() / size_to.to_f64();
let tex_from_geo = texture_from_geo.to_f64().upscale(scale_from);
let tex_to_geo = texture_to_geo.to_f64().upscale(scale_to);
let combined_geo = tex_from_geo.merge(tex_to_geo);
let area = Rectangle::from_loc_and_size(
area.loc + combined_geo.loc.to_logical(scale).to_i32_round(),
combined_geo.size.to_logical(scale).to_i32_round(),
);
let tex_from_loc = (tex_from_geo.loc - combined_geo.loc)
.downscale((combined_geo.size.w, combined_geo.size.h));
let tex_to_loc = (tex_to_geo.loc - combined_geo.loc)
.downscale((combined_geo.size.w, combined_geo.size.h));
let tex_from_size = tex_from_geo.size / combined_geo.size;
let tex_to_size = tex_to_geo.size / combined_geo.size;
// FIXME: cropping this element will mess up the coordinates.
Shaders::get(renderer).crossfade.clone().map(|shader| {
Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new(
shader,
HashMap::from([
(String::from("tex_from"), texture_from),
(String::from("tex_to"), texture_to),
]),
area,
None,
result_alpha,
vec![
Uniform::new(
"tex_from_loc",
(tex_from_loc.x as f32, tex_from_loc.y as f32),
),
Uniform::new(
"tex_from_size",
(tex_from_size.x as f32, tex_from_size.y as f32),
),
Uniform::new("tex_to_loc", (tex_to_loc.x as f32, tex_to_loc.y as f32)),
Uniform::new("tex_to_size", (tex_to_size.x as f32, tex_to_size.y as f32)),
Uniform::new("amount", amount),
],
Kind::Unspecified,
))
})
}
}
impl Element for CrossfadeRenderElement {
fn id(&self) -> &Id {
self.0.id()
}
fn current_commit(&self) -> CommitCounter {
self.0.current_commit()
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
self.0.geometry(scale)
}
fn transform(&self) -> Transform {
self.0.transform()
}
fn src(&self) -> Rectangle<f64, Buffer> {
self.0.src()
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
self.0.damage_since(scale, commit)
}
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
self.0.opaque_regions(scale)
}
fn alpha(&self) -> f32 {
self.0.alpha()
}
fn kind(&self) -> Kind {
self.0.kind()
}
}
impl RenderElement<GlesRenderer> for CrossfadeRenderElement {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), GlesError> {
RenderElement::<GlesRenderer>::draw(&self.0, frame, src, dst, damage)?;
Ok(())
}
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
self.0.underlying_storage(renderer)
}
}
impl<'render> RenderElement<TtyRenderer<'render>> for CrossfadeRenderElement {
fn draw(
&self,
frame: &mut TtyFrame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), TtyRendererError<'render>> {
let gles_frame = frame.as_gles_frame();
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
Ok(())
}
fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> {
self.0.underlying_storage(renderer)
}
}
+3
View File
@@ -17,12 +17,15 @@ use smithay::wayland::shm;
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
pub mod crossfade;
pub mod gradient;
pub mod offscreen;
pub mod primary_gpu_pixel_shader;
pub mod primary_gpu_pixel_shader_with_textures;
pub mod primary_gpu_texture;
pub mod render_elements;
pub mod renderer;
pub mod resources;
pub mod shaders;
pub mod surface;
@@ -0,0 +1,423 @@
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::rc::Rc;
use glam::{Mat3, Vec2};
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{
ffi, link_program, Capability, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform,
UniformDesc, UniformName,
};
use smithay::backend::renderer::utils::CommitCounter;
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
use super::renderer::AsGlesFrame;
use super::resources::Resources;
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU.
///
/// The shader accepts textures as input.
#[derive(Debug)]
pub struct PrimaryGpuPixelShaderWithTexturesRenderElement {
shader: PixelWithTexturesProgram,
textures: HashMap<String, GlesTexture>,
id: Id,
commit_counter: CommitCounter,
area: Rectangle<i32, Logical>,
opaque_regions: Vec<Rectangle<i32, Logical>>,
alpha: f32,
additional_uniforms: Vec<Uniform<'static>>,
kind: Kind,
}
#[derive(Debug, Clone)]
pub struct PixelWithTexturesProgram(Rc<PixelWithTexturesProgramInner>);
#[derive(Debug)]
struct PixelWithTexturesProgramInner {
program: ffi::types::GLuint,
uniform_tex_matrix: ffi::types::GLint,
uniform_matrix: ffi::types::GLint,
uniform_size: ffi::types::GLint,
uniform_alpha: ffi::types::GLint,
attrib_vert: ffi::types::GLint,
attrib_vert_position: ffi::types::GLint,
additional_uniforms: HashMap<String, UniformDesc>,
texture_uniforms: HashMap<String, ffi::types::GLint>,
}
unsafe fn compile_program(
gl: &ffi::Gles2,
src: &str,
additional_uniforms: &[UniformName<'_>],
texture_uniforms: &[&str],
// destruction_callback_sender: Sender<CleanupResource>,
) -> Result<PixelWithTexturesProgram, GlesError> {
let shader = src;
let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), shader)? };
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
let vert_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated");
let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
let size = CStr::from_bytes_with_nul(b"size\0").expect("NULL terminated");
let alpha = CStr::from_bytes_with_nul(b"alpha\0").expect("NULL terminated");
Ok(PixelWithTexturesProgram(Rc::new(
PixelWithTexturesProgramInner {
program,
uniform_matrix: gl
.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
uniform_tex_matrix: gl
.GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
uniform_size: gl
.GetUniformLocation(program, size.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_vert_position: gl
.GetAttribLocation(program, vert_position.as_ptr() as *const ffi::types::GLchar),
additional_uniforms: additional_uniforms
.iter()
.map(|uniform| {
let name =
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
let location =
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
(
uniform.name.clone().into_owned(),
UniformDesc {
location,
type_: uniform.type_,
},
)
})
.collect(),
texture_uniforms: texture_uniforms
.iter()
.map(|name_| {
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
let location =
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
(name_.to_string(), location)
})
.collect(),
},
)))
}
impl PixelWithTexturesProgram {
pub fn compile(
renderer: &mut GlesRenderer,
src: &str,
additional_uniforms: &[UniformName<'_>],
texture_uniforms: &[&str],
) -> Result<Self, GlesError> {
renderer.with_context(move |gl| unsafe {
compile_program(gl, src, additional_uniforms, texture_uniforms)
})?
}
}
impl PrimaryGpuPixelShaderWithTexturesRenderElement {
pub fn new(
shader: PixelWithTexturesProgram,
textures: HashMap<String, GlesTexture>,
area: Rectangle<i32, Logical>,
opaque_regions: Option<Vec<Rectangle<i32, Logical>>>,
alpha: f32,
additional_uniforms: Vec<Uniform<'_>>,
kind: Kind,
) -> Self {
Self {
shader,
textures,
id: Id::new(),
commit_counter: CommitCounter::default(),
area,
opaque_regions: opaque_regions.unwrap_or_default(),
alpha,
additional_uniforms: additional_uniforms
.into_iter()
.map(|u| u.into_owned())
.collect(),
kind,
}
}
}
impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement {
fn id(&self) -> &Id {
&self.id
}
fn current_commit(&self) -> CommitCounter {
self.commit_counter
}
fn src(&self) -> Rectangle<f64, Buffer> {
self.area
.to_f64()
.to_buffer(1.0, Transform::Normal, &self.area.size.to_f64())
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
self.area.to_physical_precise_round(scale)
}
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
self.opaque_regions
.iter()
.map(|region| region.to_physical_precise_round(scale))
.collect()
}
fn alpha(&self) -> f32 {
1.0
}
fn kind(&self) -> Kind {
self.kind
}
}
impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElement {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
_src: Rectangle<f64, Buffer>,
dest: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), GlesError> {
let frame = frame.as_gles_frame();
let Some(resources) = Resources::get(frame) else {
return Ok(());
};
let mut resources = resources.borrow_mut();
let supports_instancing = frame.capabilities().contains(&Capability::Instancing);
// prepare the vertices
resources.vertices.clear();
if supports_instancing {
resources.vertices.extend(damage.iter().flat_map(|rect| {
let dest_size = dest.size;
let rect_constrained_loc = rect
.loc
.constrain(Rectangle::from_extemities((0, 0), dest_size.to_point()));
let rect_clamped_size = rect.size.clamp(
(0, 0),
(dest_size.to_point() - rect_constrained_loc).to_size(),
);
let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size);
[
rect.loc.x as f32,
rect.loc.y as f32,
rect.size.w as f32,
rect.size.h as f32,
]
}));
} else {
resources.vertices.extend(damage.iter().flat_map(|rect| {
let dest_size = dest.size;
let rect_constrained_loc = rect
.loc
.constrain(Rectangle::from_extemities((0, 0), dest_size.to_point()));
let rect_clamped_size = rect.size.clamp(
(0, 0),
(dest_size.to_point() - rect_constrained_loc).to_size(),
);
let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size);
// Add the 4 f32s per damage rectangle for each of the 6 vertices.
(0..6).flat_map(move |_| {
[
rect.loc.x as f32,
rect.loc.y as f32,
rect.size.w as f32,
rect.size.h as f32,
]
})
}));
}
if resources.vertices.is_empty() {
return Ok(());
}
// dest position and scale
let mut matrix = Mat3::from_translation(Vec2::new(dest.loc.x as f32, dest.loc.y as f32));
let tex_matrix = Mat3::from_scale(Vec2::new(
(1.0f64 / dest.size.w as f64) as f32,
(1.0f64 / dest.size.h as f64) as f32,
));
//apply output transformation
matrix = Mat3::from_cols_array(frame.projection()) * matrix;
let program = &self.shader.0;
// render
frame.with_context(move |gl| -> Result<(), GlesError> {
unsafe {
for (i, texture) in self.textures.values().enumerate() {
gl.ActiveTexture(ffi::TEXTURE0 + i as u32);
gl.BindTexture(ffi::TEXTURE_2D, texture.tex_id());
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MAG_FILTER, ffi::LINEAR as i32);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_S,
ffi::CLAMP_TO_BORDER as i32,
);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_T,
ffi::CLAMP_TO_BORDER as i32,
);
}
gl.UseProgram(program.program);
for (i, name) in self.textures.keys().enumerate() {
gl.Uniform1i(program.texture_uniforms[name], i as i32);
}
gl.UniformMatrix3fv(
program.uniform_matrix,
1,
ffi::FALSE,
matrix.as_ref().as_ptr(),
);
gl.UniformMatrix3fv(
program.uniform_tex_matrix,
1,
ffi::FALSE,
tex_matrix.as_ref().as_ptr(),
);
gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32);
gl.Uniform1f(program.uniform_alpha, self.alpha);
for uniform in &self.additional_uniforms {
let desc =
program
.additional_uniforms
.get(&*uniform.name)
.ok_or_else(|| {
GlesError::UnknownUniform(uniform.name.clone().into_owned())
})?;
uniform.value.set(gl, desc)?;
}
gl.EnableVertexAttribArray(program.attrib_vert as u32);
gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[0]);
gl.VertexAttribPointer(
program.attrib_vert as u32,
2,
ffi::FLOAT,
ffi::FALSE,
0,
std::ptr::null(),
);
// vert_position
gl.EnableVertexAttribArray(program.attrib_vert_position as u32);
gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[1]);
gl.BufferData(
ffi::ARRAY_BUFFER,
(std::mem::size_of::<ffi::types::GLfloat>() * resources.vertices.len())
as isize,
resources.vertices.as_ptr() as *const _,
ffi::STREAM_DRAW,
);
gl.VertexAttribPointer(
program.attrib_vert_position as u32,
4,
ffi::FLOAT,
ffi::FALSE,
0,
std::ptr::null(),
);
let damage_len = damage.len() as i32;
if supports_instancing {
gl.VertexAttribDivisor(program.attrib_vert as u32, 0);
gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1);
gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len);
} else {
// When we have more than 10 rectangles, draw them in batches of 10.
for i in 0..(damage_len - 1) / 10 {
gl.DrawArrays(ffi::TRIANGLES, 0, 6);
// Set damage pointer to the next 10 rectangles.
let offset =
(i + 1) as usize * 6 * 4 * std::mem::size_of::<ffi::types::GLfloat>();
gl.VertexAttribPointer(
program.attrib_vert_position as u32,
4,
ffi::FLOAT,
ffi::FALSE,
0,
offset as *const _,
);
}
// Draw the up to 10 remaining rectangles.
let count = ((damage_len - 1) % 10 + 1) * 6;
gl.DrawArrays(ffi::TRIANGLES, 0, count);
}
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
gl.BindTexture(ffi::TEXTURE_2D, 0);
gl.ActiveTexture(ffi::TEXTURE0);
gl.BindTexture(ffi::TEXTURE_2D, 0);
gl.DisableVertexAttribArray(program.attrib_vert as u32);
gl.DisableVertexAttribArray(program.attrib_vert_position as u32);
}
Ok(())
})??;
Ok(())
}
fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
// If scanout for things other than Wayland buffers is implemented, this will need to take
// the target GPU into account.
None
}
}
impl<'render> RenderElement<TtyRenderer<'render>>
for PrimaryGpuPixelShaderWithTexturesRenderElement
{
fn draw(
&self,
frame: &mut TtyFrame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), TtyRendererError<'render>> {
let frame = frame.as_gles_frame();
RenderElement::<GlesRenderer>::draw(self, frame, src, dst, damage)?;
Ok(())
}
fn underlying_storage(
&self,
_renderer: &mut TtyRenderer<'render>,
) -> Option<UnderlyingStorage> {
// If scanout for things other than Wayland buffers is implemented, this will need to take
// the target GPU into account.
None
}
}
+106
View File
@@ -0,0 +1,106 @@
use std::cell::RefCell;
use std::rc::Rc;
use smithay::backend::renderer::gles::{ffi, Capability, GlesError, GlesFrame, GlesRenderer};
pub struct Resources {
pub vertices: Vec<f32>,
pub vbos: [ffi::types::GLuint; 2],
}
static INSTANCED_VERTS: [ffi::types::GLfloat; 8] = [
1.0, 0.0, // top right
0.0, 0.0, // top left
1.0, 1.0, // bottom right
0.0, 1.0, // bottom left
];
/// Vertices for rendering individual triangles.
const MAX_RECTS_PER_DRAW: usize = 10;
const TRIANGLE_VERTS: [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] = triangle_verts();
const fn triangle_verts() -> [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] {
let mut verts = [0.; 12 * MAX_RECTS_PER_DRAW];
let mut i = 0;
loop {
// Top Left.
verts[i * 12] = 0.0;
verts[i * 12 + 1] = 0.0;
// Bottom left.
verts[i * 12 + 2] = 0.0;
verts[i * 12 + 3] = 1.0;
// Bottom right.
verts[i * 12 + 4] = 1.0;
verts[i * 12 + 5] = 1.0;
// Top left.
verts[i * 12 + 6] = 0.0;
verts[i * 12 + 7] = 0.0;
// Bottom right.
verts[i * 12 + 8] = 1.0;
verts[i * 12 + 9] = 1.0;
// Top right.
verts[i * 12 + 10] = 1.0;
verts[i * 12 + 11] = 0.0;
i += 1;
if i == MAX_RECTS_PER_DRAW {
break;
}
}
verts
}
impl Resources {
fn create(renderer: &mut GlesRenderer) -> Result<Self, GlesError> {
let _span = tracy_client::span!("Resources::init");
let supports_instancing = renderer.capabilities().contains(&Capability::Instancing);
renderer.with_context(|gl| unsafe {
let vertices: &[ffi::types::GLfloat] = if supports_instancing {
&INSTANCED_VERTS
} else {
&TRIANGLE_VERTS
};
let mut vbos = [0; 2];
gl.GenBuffers(vbos.len() as i32, vbos.as_mut_ptr());
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
gl.BufferData(
ffi::ARRAY_BUFFER,
std::mem::size_of_val(vertices) as isize,
vertices.as_ptr() as *const _,
ffi::STATIC_DRAW,
);
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
Self {
vertices: vec![],
vbos,
}
})
}
pub fn get(frame: &mut GlesFrame) -> Option<Rc<RefCell<Self>>> {
let data = frame.egl_context().user_data();
data.get().cloned()
}
}
pub fn init(renderer: &mut GlesRenderer) {
match Resources::create(renderer) {
Ok(resources) => {
let data = renderer.egl_context().user_data();
if !data.insert_if_missing(|| Rc::new(RefCell::new(resources))) {
error!("resources were already initialized");
}
}
Err(err) => {
warn!("error creating resources for rendering: {err:?}");
}
}
}
+31
View File
@@ -0,0 +1,31 @@
#version 100
precision mediump float;
uniform sampler2D tex_from;
uniform vec2 tex_from_loc;
uniform vec2 tex_from_size;
uniform sampler2D tex_to;
uniform vec2 tex_to_loc;
uniform vec2 tex_to_size;
uniform float alpha;
uniform float amount;
uniform vec2 size;
varying vec2 v_coords;
void main() {
vec2 coords_from = (v_coords - tex_from_loc) / tex_from_size;
vec2 coords_to = (v_coords - tex_to_loc) / tex_to_size;
vec4 color_from = texture2D(tex_from, coords_from);
vec4 color_to = texture2D(tex_to, coords_to);
vec4 color = mix(color_from, color_to, amount);
color = color * alpha;
gl_FragColor = color;
}
+23 -1
View File
@@ -1,9 +1,11 @@
use smithay::backend::renderer::gles::{GlesPixelProgram, GlesRenderer, UniformName, UniformType};
use super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram;
use super::renderer::NiriRenderer;
pub struct Shaders {
pub gradient_border: Option<GlesPixelProgram>,
pub crossfade: Option<PixelWithTexturesProgram>,
}
impl Shaders {
@@ -26,7 +28,27 @@ impl Shaders {
})
.ok();
Self { gradient_border }
let crossfade = PixelWithTexturesProgram::compile(
renderer,
include_str!("crossfade.frag"),
&[
UniformName::new("tex_from_loc", UniformType::_2f),
UniformName::new("tex_from_size", UniformType::_2f),
UniformName::new("tex_to_loc", UniformType::_2f),
UniformName::new("tex_to_size", UniformType::_2f),
UniformName::new("amount", UniformType::_1f),
],
&["tex_from", "tex_to"],
)
.map_err(|err| {
warn!("error compiling crossfade shader: {err:?}");
})
.ok();
Self {
gradient_border,
crossfade,
}
}
pub fn get(renderer: &mut impl NiriRenderer) -> &Self {
+25
View File
@@ -0,0 +1,25 @@
#version 100
uniform mat3 matrix;
uniform mat3 tex_matrix;
attribute vec2 vert;
attribute vec4 vert_position;
varying vec2 v_coords;
mat2 scale(vec2 scale_vec){
return mat2(
scale_vec.x, 0.0,
0.0, scale_vec.y
);
}
void main() {
vec2 vert_transform_translation = vert_position.xy;
vec2 vert_transform_scale = vert_position.zw;
vec3 position = vec3(vert * scale(vert_transform_scale) + vert_transform_translation, 1.0);
v_coords = (tex_matrix * position).xy;
gl_Position = vec4(matrix * position, 1.0);
}
+77 -14
View File
@@ -11,14 +11,16 @@ use smithay::output::Output;
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
use smithay::wayland::compositor::{
remove_pre_commit_hook, send_surface_state, with_states, HookId,
};
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
use super::{ResolvedWindowRules, WindowRef};
use crate::layout::{LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot};
use crate::layout::{
AnimationSnapshot, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
};
use crate::niri::WindowOffscreenId;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::surface::render_snapshot_from_surface_tree;
@@ -48,6 +50,15 @@ pub struct Mapped {
/// Snapshot of the last render for use in the close animation.
last_render: RefCell<LayoutElementRenderSnapshot>,
/// Whether the next configure should be animated, if the configured state changed.
animate_next_configure: bool,
/// Serials of commits that should be animated.
animate_serials: Vec<Serial>,
/// Snapshot right before an animated commit.
animation_snapshot: Option<AnimationSnapshot>,
}
impl Mapped {
@@ -60,6 +71,9 @@ impl Mapped {
is_focused: false,
block_out_buffer: RefCell::new(SolidColorBuffer::new((0, 0), [0., 0., 0., 1.])),
last_render: RefCell::new(RenderSnapshot::default()),
animate_next_configure: false,
animate_serials: Vec::new(),
animation_snapshot: None,
}
}
@@ -101,15 +115,13 @@ impl Mapped {
self.need_to_recompute_rules = true;
}
pub fn render_and_store_snapshot(&self, renderer: &mut GlesRenderer) {
let mut snapshot = self.last_render.borrow_mut();
if !snapshot.contents.is_empty() {
return;
}
fn render_snapshot(&self, renderer: &mut GlesRenderer) -> LayoutElementRenderSnapshot {
let _span = tracy_client::span!("Mapped::render_snapshot");
snapshot.contents.clear();
snapshot.blocked_out_contents.clear();
snapshot.block_out_from = self.rules.block_out_from;
let mut snapshot = RenderSnapshot {
block_out_from: self.rules.block_out_from,
..Default::default()
};
let mut buffer = self.block_out_buffer.borrow_mut();
buffer.resize(self.window.geometry().size);
@@ -135,6 +147,37 @@ impl Mapped {
}
render_snapshot_from_surface_tree(renderer, surface, buf_pos, &mut snapshot.contents);
snapshot
}
pub fn render_and_store_snapshot(&self, renderer: &mut GlesRenderer) {
let mut snapshot = self.last_render.borrow_mut();
if !snapshot.contents.is_empty() {
return;
}
*snapshot = self.render_snapshot(renderer);
}
pub fn should_animate_commit(&mut self, commit_serial: Serial) -> bool {
let mut should_animate = false;
self.animate_serials.retain_mut(|serial| {
if commit_serial.is_no_older_than(serial) {
should_animate = true;
false
} else {
true
}
});
should_animate
}
pub fn store_animation_snapshot(&mut self, renderer: &mut GlesRenderer) {
self.animation_snapshot = Some(AnimationSnapshot {
render: self.render_snapshot(renderer),
size: self.size(),
});
}
}
@@ -200,11 +243,17 @@ impl LayoutElement for Mapped {
self.last_render.take()
}
fn request_size(&self, size: Size<i32, Logical>) {
self.toplevel().with_pending_state(|state| {
fn request_size(&mut self, size: Size<i32, Logical>, animate: bool) {
let changed = self.toplevel().with_pending_state(|state| {
let changed = state.size != Some(size);
state.size = Some(size);
state.states.unset(xdg_toplevel::State::Fullscreen);
changed
});
if changed && animate {
self.animate_next_configure = true;
}
}
fn request_fullscreen(&self, size: Size<i32, Logical>) {
@@ -303,8 +352,14 @@ impl LayoutElement for Mapped {
});
}
fn send_pending_configure(&self) {
self.toplevel().send_pending_configure();
fn send_pending_configure(&mut self) {
if let Some(serial) = self.toplevel().send_pending_configure() {
if self.animate_next_configure {
self.animate_serials.push(serial);
}
}
self.animate_next_configure = false;
}
fn is_fullscreen(&self) -> bool {
@@ -326,4 +381,12 @@ impl LayoutElement for Mapped {
fn rules(&self) -> &ResolvedWindowRules {
&self.rules
}
fn animation_snapshot(&self) -> Option<&AnimationSnapshot> {
self.animation_snapshot.as_ref()
}
fn take_animation_snapshot(&mut self) -> Option<AnimationSnapshot> {
self.animation_snapshot.take()
}
}
+21
View File
@@ -38,6 +38,10 @@ animations {
curve "ease-out-quad"
}
window-resize {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
config-notification-open-close {
spring damping-ratio=0.6 stiffness=1000 epsilon=0.001
}
@@ -196,6 +200,23 @@ animations {
}
```
#### `window-resize`
<sup>Since: 0.1.5</sup>
Window resize animation.
Only manual window resizes are animated, i.e. when you resize the window with `switch-preset-column-width` or `maximize-column`.
Also, very small resizes (up to 10 pixels) are not animated.
```
animations {
window-resize {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
}
}
```
#### `config-notification-open-close`
The open/close animation of the config parse error and new default config notifications.