mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement xray background effect
This commit is contained in:
@@ -1006,6 +1006,29 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct BackgroundEffectRule {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub xray: Option<bool>,
|
||||
}
|
||||
|
||||
/// Resolved background effect rule.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct BackgroundEffect {
|
||||
/// Whether to render with xray effect (see through).
|
||||
///
|
||||
/// - `None`: xray if any background effect is active
|
||||
/// - `Some(false)`: no xray
|
||||
/// - `Some(true)`: xray even if no other background effect is active
|
||||
pub xray: Option<bool>,
|
||||
}
|
||||
|
||||
impl MergeWith<BackgroundEffectRule> for BackgroundEffect {
|
||||
fn merge_with(&mut self, part: &BackgroundEffectRule) {
|
||||
merge_clone_opt!((self, part), xray);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::{assert_debug_snapshot, assert_snapshot};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::appearance::{BlockOutFrom, CornerRadius, ShadowRule};
|
||||
use crate::appearance::{BackgroundEffectRule, BlockOutFrom, CornerRadius, ShadowRule};
|
||||
use crate::utils::RegexEq;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
@@ -20,6 +20,8 @@ pub struct LayerRule {
|
||||
pub place_within_backdrop: Option<bool>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub baba_is_float: Option<bool>,
|
||||
#[knuffel(child, default)]
|
||||
pub background_effect: BackgroundEffectRule,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
|
||||
@@ -1845,6 +1845,9 @@ mod tests {
|
||||
),
|
||||
scroll_factor: None,
|
||||
tiled_state: None,
|
||||
background_effect: BackgroundEffectRule {
|
||||
xray: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
layer_rules: [
|
||||
@@ -1880,6 +1883,9 @@ mod tests {
|
||||
geometry_corner_radius: None,
|
||||
place_within_backdrop: None,
|
||||
baba_is_float: None,
|
||||
background_effect: BackgroundEffectRule {
|
||||
xray: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
binds: Binds(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use niri_ipc::ColumnDisplay;
|
||||
|
||||
use crate::appearance::{BlockOutFrom, BorderRule, CornerRadius, ShadowRule, TabIndicatorRule};
|
||||
use crate::appearance::{
|
||||
BackgroundEffectRule, BlockOutFrom, BorderRule, CornerRadius, ShadowRule, TabIndicatorRule,
|
||||
};
|
||||
use crate::layout::DefaultPresetSize;
|
||||
use crate::utils::RegexEq;
|
||||
use crate::FloatOrInt;
|
||||
@@ -72,6 +74,8 @@ pub struct WindowRule {
|
||||
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub tiled_state: Option<bool>,
|
||||
#[knuffel(child, default)]
|
||||
pub background_effect: BackgroundEffectRule,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
|
||||
@@ -273,6 +273,7 @@ impl TestCase for Layout {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
self.layout
|
||||
.monitor_for_output(&self.output)
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri::layout::Options;
|
||||
use niri::render_helpers::xray::XrayPos;
|
||||
use niri::render_helpers::{RenderCtx, RenderTarget};
|
||||
use niri_config::Color;
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
@@ -124,10 +125,13 @@ impl TestCase for Tile {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
self.tile.render(ctx, location, true, &mut |elem| {
|
||||
rv.push(Box::new(elem) as _)
|
||||
});
|
||||
let xray_pos = XrayPos::new(location, 1.);
|
||||
self.tile
|
||||
.render(ctx, location, xray_pos, true, &mut |elem| {
|
||||
rv.push(Box::new(elem) as _)
|
||||
});
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ impl TestCase for Window {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
self.window
|
||||
.render_normal(ctx, location, Scale::from(1.), 1., &mut |elem| {
|
||||
|
||||
@@ -1868,6 +1868,7 @@ impl Tty {
|
||||
let ctx = RenderCtx {
|
||||
renderer: &mut renderer,
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
let mut elements = niri.render_to_vec(ctx, output, true);
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ impl Winit {
|
||||
let ctx = RenderCtx {
|
||||
renderer: self.backend.renderer(),
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
let mut elements = niri.render_to_vec(ctx, output, true);
|
||||
|
||||
|
||||
@@ -486,11 +486,10 @@ impl CompositorHandler for State {
|
||||
// subsurface is destroyed; in the case of alacritty, this is the top CSD shadow. But, it
|
||||
// gets most of the job done.
|
||||
if let Some(root) = self.niri.root_surface.get(surface) {
|
||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(root) {
|
||||
if let Some((mapped, output)) = self.niri.layout.find_window_and_output(root) {
|
||||
let window = mapped.window.clone();
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
self.niri.layout.store_unmap_snapshot(renderer, &window);
|
||||
});
|
||||
let output = output.cloned();
|
||||
self.store_unmap_snapshot(&window, output.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -846,9 +846,7 @@ impl XdgShellHandler for State {
|
||||
self.niri
|
||||
.stop_casts_for_target(CastTarget::Window { id: id.get() });
|
||||
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
self.niri.layout.store_unmap_snapshot(renderer, &window);
|
||||
});
|
||||
self.store_unmap_snapshot(&window, output.as_ref());
|
||||
|
||||
let transaction = Transaction::new();
|
||||
let blocker = transaction.blocker();
|
||||
@@ -1445,7 +1443,7 @@ pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId
|
||||
let span =
|
||||
trace_span!("toplevel pre-commit", surface = %surface.id(), serial = Empty).entered();
|
||||
|
||||
let Some((mapped, _)) = state.niri.layout.find_window_and_output_mut(surface) else {
|
||||
let Some((mapped, output)) = state.niri.layout.find_window_and_output_mut(surface) else {
|
||||
error!("pre-commit hook for mapped surfaces must be removed upon unmapping");
|
||||
return;
|
||||
};
|
||||
@@ -1547,9 +1545,8 @@ pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId
|
||||
|
||||
let window = mapped.window.clone();
|
||||
if got_unmapped {
|
||||
state.backend.with_primary_renderer(|renderer| {
|
||||
state.niri.layout.store_unmap_snapshot(renderer, &window);
|
||||
});
|
||||
let output = output.cloned();
|
||||
state.store_unmap_snapshot(&window, output.as_ref());
|
||||
} else {
|
||||
if animate {
|
||||
state.backend.with_primary_renderer(|renderer| {
|
||||
|
||||
@@ -53,6 +53,7 @@ impl PickColorGrab {
|
||||
renderer,
|
||||
// This is an interactive operation so we can render without blocking out.
|
||||
target: RenderTarget::Output,
|
||||
xray: None,
|
||||
};
|
||||
let elements = data.niri.render_to_vec(ctx, &output, false);
|
||||
|
||||
|
||||
+29
-6
@@ -3,7 +3,7 @@ use niri_config::{Config, LayerRule};
|
||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::desktop::{LayerSurface, PopupManager};
|
||||
use smithay::utils::{Logical, Point, Scale, Size};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
||||
use smithay::wayland::compositor::{remove_pre_commit_hook, HookId};
|
||||
use smithay::wayland::shell::wlr_layer::{ExclusiveZone, Layer};
|
||||
|
||||
@@ -11,11 +11,13 @@ use super::ResolvedLayerRules;
|
||||
use crate::animation::Clock;
|
||||
use crate::layout::shadow::Shadow;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::background_effect::BackgroundEffectElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::surface::push_elements_from_surface_tree;
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::render_helpers::xray::XrayPos;
|
||||
use crate::render_helpers::{background_effect, RenderCtx};
|
||||
use crate::utils::{baba_is_float_offset, round_logical_in_physical};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -55,6 +57,7 @@ niri_render_elements! {
|
||||
Wayland = WaylandSurfaceRenderElement<R>,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
Shadow = ShadowRenderElement,
|
||||
BackgroundEffect = BackgroundEffectElement,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,15 +180,22 @@ impl MappedLayer {
|
||||
|
||||
pub fn render_normal<R: NiriRenderer>(
|
||||
&self,
|
||||
ctx: RenderCtx<R>,
|
||||
mut ctx: RenderCtx<R>,
|
||||
location: Point<f64, Logical>,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(LayerSurfaceRenderElement<R>),
|
||||
) {
|
||||
let scale = Scale::from(self.scale);
|
||||
let alpha = self.rules.opacity.unwrap_or(1.).clamp(0., 1.);
|
||||
let location = location + self.bob_offset();
|
||||
|
||||
if ctx.target.should_block_out(self.rules.block_out_from) {
|
||||
let bob_offset = self.bob_offset();
|
||||
let location = location + bob_offset;
|
||||
let xray_pos = xray_pos.offset(bob_offset);
|
||||
|
||||
let surface = self.surface.wl_surface();
|
||||
|
||||
let should_block_out = ctx.target.should_block_out(self.rules.block_out_from);
|
||||
if should_block_out {
|
||||
// Round to physical pixels.
|
||||
let location = location.to_physical_precise_round(scale).to_logical(scale);
|
||||
|
||||
@@ -201,7 +211,6 @@ impl MappedLayer {
|
||||
// Layer surfaces don't have extra geometry like windows.
|
||||
let buf_pos = location;
|
||||
|
||||
let surface = self.surface.wl_surface();
|
||||
push_elements_from_surface_tree(
|
||||
ctx.renderer,
|
||||
surface,
|
||||
@@ -216,6 +225,20 @@ impl MappedLayer {
|
||||
let location = location.to_physical_precise_round(scale).to_logical(scale);
|
||||
self.shadow
|
||||
.render(ctx.renderer, location, &mut |elem| push(elem.into()));
|
||||
|
||||
let geometry = Rectangle::new(location, self.block_out_buffer.size());
|
||||
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
|
||||
background_effect::render_for_tile(
|
||||
ctx.as_gles(),
|
||||
geometry,
|
||||
self.scale,
|
||||
false,
|
||||
surface,
|
||||
radius,
|
||||
self.rules.background_effect,
|
||||
xray_pos,
|
||||
&mut |elem| push(elem.into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_popups<R: NiriRenderer>(
|
||||
|
||||
+8
-1
@@ -1,6 +1,6 @@
|
||||
use niri_config::layer_rule::{LayerRule, Match};
|
||||
use niri_config::utils::MergeWith as _;
|
||||
use niri_config::{BlockOutFrom, CornerRadius, ShadowRule};
|
||||
use niri_config::{BackgroundEffect, BlockOutFrom, CornerRadius, ShadowRule};
|
||||
use smithay::desktop::LayerSurface;
|
||||
use smithay::wayland::shell::wlr_layer::Layer;
|
||||
|
||||
@@ -27,6 +27,9 @@ pub struct ResolvedLayerRules {
|
||||
|
||||
/// Whether to bob this window up and down.
|
||||
pub baba_is_float: bool,
|
||||
|
||||
/// Background effect configuration.
|
||||
pub background_effect: BackgroundEffect,
|
||||
}
|
||||
|
||||
impl ResolvedLayerRules {
|
||||
@@ -71,6 +74,10 @@ impl ResolvedLayerRules {
|
||||
}
|
||||
|
||||
resolved.shadow.merge_with(&rule.shadow);
|
||||
|
||||
resolved
|
||||
.background_effect
|
||||
.merge_with(&rule.background_effect);
|
||||
}
|
||||
|
||||
resolved
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::render_helpers::shader_element::ShaderRenderElement;
|
||||
use crate::render_helpers::shaders::{mat3_uniform, ProgramType, Shaders};
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::render_helpers::{render_to_encompassing_texture, RenderCtx};
|
||||
use crate::render_helpers::{render_to_encompassing_texture, RenderCtx, RenderTarget};
|
||||
use crate::utils::transaction::TransactionBlocker;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -29,6 +29,12 @@ pub struct ClosingWindow {
|
||||
/// Contents of the window.
|
||||
buffer: TextureBuffer<GlesTexture>,
|
||||
|
||||
/// Contents that are not blocked out, but the background is blocked out.
|
||||
///
|
||||
/// If `None` then the background doesn't have any blocked-out surfaces, and normal `buffer`
|
||||
/// can be used instead.
|
||||
buffer_with_blocked_out_bg: Option<TextureBuffer<GlesTexture>>,
|
||||
|
||||
/// Blocked-out contents of the window.
|
||||
blocked_out_buffer: TextureBuffer<GlesTexture>,
|
||||
|
||||
@@ -44,6 +50,9 @@ pub struct ClosingWindow {
|
||||
/// How much the texture should be offset.
|
||||
buffer_offset: Point<f64, Logical>,
|
||||
|
||||
/// How much the texture with blocked-out bg should be offset.
|
||||
buffer_with_blocked_out_bg_offset: Point<f64, Logical>,
|
||||
|
||||
/// How much the blocked-out texture should be offset.
|
||||
blocked_out_buffer_offset: Point<f64, Logical>,
|
||||
|
||||
@@ -121,17 +130,27 @@ impl ClosingWindow {
|
||||
|
||||
let (buffer, buffer_offset) =
|
||||
render_to_texture(snapshot.contents).context("error rendering contents")?;
|
||||
let (buffer_with_blocked_out_bg, buffer_with_blocked_out_bg_offset) =
|
||||
if let Some(contents) = snapshot.contents_with_blocked_out_bg {
|
||||
let (buffer, offset) = render_to_texture(contents)
|
||||
.context("error rendering contents with blocked-out bg")?;
|
||||
(Some(buffer), offset)
|
||||
} else {
|
||||
(None, Point::default())
|
||||
};
|
||||
let (blocked_out_buffer, blocked_out_buffer_offset) =
|
||||
render_to_texture(snapshot.blocked_out_contents)
|
||||
.context("error rendering blocked-out contents")?;
|
||||
|
||||
Ok(Self {
|
||||
buffer,
|
||||
buffer_with_blocked_out_bg,
|
||||
blocked_out_buffer,
|
||||
block_out_from: snapshot.block_out_from,
|
||||
geo_size,
|
||||
pos,
|
||||
buffer_offset,
|
||||
buffer_with_blocked_out_bg_offset,
|
||||
blocked_out_buffer_offset,
|
||||
anim_state: AnimationState::new(blocker, anim),
|
||||
random_seed: fastrand::f32(),
|
||||
@@ -165,6 +184,11 @@ impl ClosingWindow {
|
||||
) -> ClosingWindowRenderElement {
|
||||
let (buffer, offset) = if ctx.target.should_block_out(self.block_out_from) {
|
||||
(&self.blocked_out_buffer, self.blocked_out_buffer_offset)
|
||||
} else if ctx.target != RenderTarget::Output && self.buffer_with_blocked_out_bg.is_some() {
|
||||
(
|
||||
self.buffer_with_blocked_out_bg.as_ref().unwrap(),
|
||||
self.buffer_with_blocked_out_bg_offset,
|
||||
)
|
||||
} else {
|
||||
(&self.buffer, self.buffer_offset)
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ use super::{
|
||||
use crate::animation::{Animation, Clock};
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::xray::XrayPos;
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::utils::transaction::TransactionBlocker;
|
||||
use crate::utils::{
|
||||
@@ -1056,6 +1057,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
||||
pub fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
xray_pos: XrayPos,
|
||||
view_rect: Rectangle<f64, Logical>,
|
||||
focus_ring: bool,
|
||||
push: &mut dyn FnMut(FloatingSpaceRenderElement<R>),
|
||||
@@ -1075,7 +1077,10 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
||||
// For the active tile, draw the focus ring.
|
||||
let focus_ring = focus_ring && Some(tile.window().id()) == active.as_ref();
|
||||
|
||||
tile.render(ctx.r(), tile_pos, focus_ring, &mut |elem| push(elem.into()));
|
||||
let xray_pos = xray_pos.offset(tile_pos);
|
||||
tile.render(ctx.r(), tile_pos, xray_pos, focus_ring, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+66
-13
@@ -59,11 +59,13 @@ use crate::animation::{Animation, Clock};
|
||||
use crate::input::swipe_tracker::SwipeTracker;
|
||||
use crate::layout::scrolling::ScrollDirection;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::background_effect::BackgroundEffectElement;
|
||||
use crate::render_helpers::offscreen::OffscreenData;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::texture::TextureBuffer;
|
||||
use crate::render_helpers::xray::{Xray, XrayPos};
|
||||
use crate::render_helpers::{BakedBuffer, RenderCtx};
|
||||
use crate::rubber_band::RubberBand;
|
||||
use crate::utils::transaction::{Transaction, TransactionBlocker};
|
||||
@@ -188,6 +190,20 @@ pub trait LayoutElement {
|
||||
let _ = (ctx, location, scale, alpha, push);
|
||||
}
|
||||
|
||||
/// Renders the background effect behind the main surface of the element.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_background_effect(
|
||||
&self,
|
||||
_ctx: RenderCtx<GlesRenderer>,
|
||||
_geometry: Rectangle<f64, Logical>,
|
||||
_scale: f64,
|
||||
_clip_to_geometry: bool,
|
||||
_radius: CornerRadius,
|
||||
_xray_pos: XrayPos,
|
||||
_push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
) {
|
||||
}
|
||||
|
||||
/// Requests the element to change its size.
|
||||
///
|
||||
/// The size request is stored and will be continuously sent to the element on any further
|
||||
@@ -4605,12 +4621,33 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_unmap_snapshot(&mut self, renderer: &mut GlesRenderer, window: &W::Id) {
|
||||
pub fn store_unmap_snapshot(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
xray: Option<&mut Xray>,
|
||||
xray_has_blocked_out_layers: bool,
|
||||
window: &W::Id,
|
||||
) {
|
||||
let _span = tracy_client::span!("Layout::store_unmap_snapshot");
|
||||
|
||||
let zoom = self.overview_zoom();
|
||||
|
||||
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
|
||||
if move_.tile.window().id() == window {
|
||||
move_.tile.store_unmap_snapshot_if_empty(renderer);
|
||||
let pos_within_output = move_.tile_render_location(zoom);
|
||||
|
||||
// Computation matches update_render_elements().
|
||||
let view_rect =
|
||||
Rectangle::new(pos_within_output.upscale(-1.), output_size(&move_.output))
|
||||
.downscale(zoom);
|
||||
move_.tile.update_render_elements(false, view_rect);
|
||||
|
||||
move_.tile.store_unmap_snapshot_if_empty(
|
||||
renderer,
|
||||
xray,
|
||||
xray_has_blocked_out_layers,
|
||||
XrayPos::new(pos_within_output, zoom),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4618,9 +4655,15 @@ impl<W: LayoutElement> Layout<W> {
|
||||
match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
for ws in &mut mon.workspaces {
|
||||
for (ws, geo) in mon.workspaces_with_render_geo_mut(false) {
|
||||
if ws.has_window(window) {
|
||||
ws.store_unmap_snapshot_if_empty(renderer, window);
|
||||
ws.store_unmap_snapshot_if_empty(
|
||||
renderer,
|
||||
xray,
|
||||
xray_has_blocked_out_layers,
|
||||
XrayPos::new(geo.loc, zoom),
|
||||
window,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4629,7 +4672,13 @@ impl<W: LayoutElement> Layout<W> {
|
||||
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||
for ws in workspaces {
|
||||
if ws.has_window(window) {
|
||||
ws.store_unmap_snapshot_if_empty(renderer, window);
|
||||
ws.store_unmap_snapshot_if_empty(
|
||||
renderer,
|
||||
xray,
|
||||
xray_has_blocked_out_layers,
|
||||
XrayPos::default(),
|
||||
window,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4748,14 +4797,18 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
let scale = Scale::from(move_.output.current_scale().fractional_scale());
|
||||
let zoom = self.overview_zoom();
|
||||
let location = move_.tile_render_location(zoom);
|
||||
move_.tile.render(ctx, location, true, &mut |elem| {
|
||||
push(RescaleRenderElement::from_element(
|
||||
elem,
|
||||
location.to_physical_precise_round(scale),
|
||||
zoom,
|
||||
));
|
||||
});
|
||||
let pos_in_backdrop = move_.tile_render_location(zoom);
|
||||
let xray_pos = XrayPos::new(pos_in_backdrop, zoom);
|
||||
|
||||
move_
|
||||
.tile
|
||||
.render(ctx, pos_in_backdrop, xray_pos, true, &mut |elem| {
|
||||
push(RescaleRenderElement::from_element(
|
||||
elem,
|
||||
pos_in_backdrop.to_physical_precise_round(scale),
|
||||
zoom,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self, is_active: bool) {
|
||||
|
||||
@@ -24,6 +24,7 @@ use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::solid_color::SolidColorRenderElement;
|
||||
use crate::render_helpers::xray::XrayPos;
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::rubber_band::RubberBand;
|
||||
use crate::utils::transaction::Transaction;
|
||||
@@ -1733,7 +1734,9 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}};
|
||||
}
|
||||
|
||||
ws.render_floating(ctx.r(), focus_ring, push!());
|
||||
let xray_pos = XrayPos::new(geo.loc, zoom);
|
||||
|
||||
ws.render_floating(ctx.r(), xray_pos, focus_ring, push!());
|
||||
|
||||
if let Some(loc) = insert_hint_render_loc {
|
||||
if loc.workspace == InsertWorkspace::Existing(ws.id()) {
|
||||
@@ -1742,7 +1745,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
}
|
||||
|
||||
ws.render_scrolling(ctx.r(), focus_ring, push!());
|
||||
ws.render_scrolling(ctx.r(), xray_pos, focus_ring, push!());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::input::swipe_tracker::SwipeTracker;
|
||||
use crate::layout::SizingMode;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::xray::XrayPos;
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::utils::transaction::{Transaction, TransactionBlocker};
|
||||
use crate::utils::ResizeEdge;
|
||||
@@ -2900,6 +2901,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
pub fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
xray_pos: XrayPos,
|
||||
focus_ring: bool,
|
||||
push: &mut dyn FnMut(ScrollingSpaceRenderElement<R>),
|
||||
) {
|
||||
@@ -2954,7 +2956,10 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
continue;
|
||||
}
|
||||
|
||||
tile.render(ctx.r(), tile_pos, focus_ring, &mut |elem| push(elem.into()));
|
||||
let xray_pos = xray_pos.offset(tile_pos);
|
||||
tile.render(ctx.r(), tile_pos, xray_pos, focus_ring, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,10 +116,12 @@ impl TestWindow {
|
||||
if self.0.animate_next_configure.get() {
|
||||
self.0.animation_snapshot.replace(Some(RenderSnapshot {
|
||||
contents: Vec::new(),
|
||||
contents_with_blocked_out_bg: None,
|
||||
blocked_out_contents: Vec::new(),
|
||||
block_out_from: None,
|
||||
size: self.0.bbox.get().size.to_f64(),
|
||||
texture: OnceCell::new(),
|
||||
texture_with_blocked_out_bg: Default::default(),
|
||||
blocked_out_texture: OnceCell::new(),
|
||||
}));
|
||||
}
|
||||
|
||||
+126
-13
@@ -18,6 +18,7 @@ use super::{
|
||||
use crate::animation::{Animation, Clock};
|
||||
use crate::layout::SizingMode;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::background_effect::BackgroundEffectElement;
|
||||
use crate::render_helpers::border::BorderRenderElement;
|
||||
use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage};
|
||||
use crate::render_helpers::damage::ExtraDamage;
|
||||
@@ -27,6 +28,7 @@ use crate::render_helpers::resize::ResizeRenderElement;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::xray::{Xray, XrayPos};
|
||||
use crate::render_helpers::{RenderCtx, RenderTarget};
|
||||
use crate::utils::transaction::Transaction;
|
||||
use crate::utils::{
|
||||
@@ -130,6 +132,7 @@ niri_render_elements! {
|
||||
ClippedSurface = ClippedSurfaceRenderElement<R>,
|
||||
Offscreen = OffscreenRenderElement,
|
||||
ExtraDamage = ExtraDamage,
|
||||
BackgroundEffect = BackgroundEffectElement,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,6 +1013,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
location: Point<f64, Logical>,
|
||||
mut xray_pos: XrayPos,
|
||||
focus_ring: bool,
|
||||
push: &mut dyn FnMut(TileRenderElement<R>),
|
||||
) {
|
||||
@@ -1037,13 +1041,16 @@ impl<W: LayoutElement> Tile<W> {
|
||||
//
|
||||
// This isn't to say that adding it here is perfect; indeed, it kind of breaks view_rect
|
||||
// passed to update_render_elements(). But, it works well enough for what it is.
|
||||
let location = location + self.bob_offset();
|
||||
let bob_offset = self.bob_offset();
|
||||
let location = location + bob_offset;
|
||||
xray_pos = xray_pos.offset(bob_offset);
|
||||
|
||||
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::new(window_render_loc, animated_window_size);
|
||||
xray_pos = xray_pos.offset(window_loc);
|
||||
|
||||
let rules = self.window.rules();
|
||||
|
||||
@@ -1272,12 +1279,23 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.shadow
|
||||
.render(ctx.renderer, location, &mut |elem| push(elem.into()));
|
||||
}
|
||||
|
||||
self.window.render_background_effect(
|
||||
ctx.as_gles(),
|
||||
area,
|
||||
self.scale,
|
||||
clip_to_geometry,
|
||||
radius,
|
||||
xray_pos,
|
||||
&mut |elem| push(elem.into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
location: Point<f64, Logical>,
|
||||
xray_pos: XrayPos,
|
||||
focus_ring: bool,
|
||||
push: &mut dyn FnMut(TileRenderElement<R>),
|
||||
) {
|
||||
@@ -1296,9 +1314,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
if let Some(open) = &self.open_animation {
|
||||
let mut ctx = ctx.as_gles();
|
||||
let mut elements = Vec::new();
|
||||
self.render_inner(ctx.r(), Point::from((0., 0.)), focus_ring, &mut |elem| {
|
||||
elements.push(elem)
|
||||
});
|
||||
self.render_inner(
|
||||
ctx.r(),
|
||||
Point::new(0., 0.),
|
||||
xray_pos,
|
||||
focus_ring,
|
||||
&mut |elem| elements.push(elem),
|
||||
);
|
||||
match open.render(
|
||||
ctx.renderer,
|
||||
&elements,
|
||||
@@ -1319,9 +1341,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
} else if let Some(alpha) = &self.alpha_animation {
|
||||
let mut ctx = ctx.as_gles();
|
||||
let mut elements = Vec::new();
|
||||
self.render_inner(ctx.r(), Point::from((0., 0.)), focus_ring, &mut |elem| {
|
||||
elements.push(elem)
|
||||
});
|
||||
self.render_inner(
|
||||
ctx.r(),
|
||||
Point::new(0., 0.),
|
||||
xray_pos,
|
||||
focus_ring,
|
||||
&mut |elem| elements.push(elem),
|
||||
);
|
||||
match alpha.offscreen.render(ctx.renderer, scale, &elements) {
|
||||
Ok((elem, _sync, data)) => {
|
||||
let offset = elem.offset();
|
||||
@@ -1338,50 +1364,137 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
|
||||
if !pushed {
|
||||
self.render_inner(ctx, location, focus_ring, &mut |elem| push(elem));
|
||||
self.render_inner(ctx, location, xray_pos, focus_ring, &mut |elem| push(elem));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_unmap_snapshot_if_empty(&mut self, renderer: &mut GlesRenderer) {
|
||||
pub fn store_unmap_snapshot_if_empty(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
xray: Option<&mut Xray>,
|
||||
xray_has_blocked_out_layers: bool,
|
||||
xray_pos: XrayPos,
|
||||
) {
|
||||
if self.unmap_snapshot.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.unmap_snapshot = Some(self.render_snapshot(renderer));
|
||||
self.unmap_snapshot =
|
||||
Some(self.render_snapshot(renderer, xray, xray_has_blocked_out_layers, xray_pos));
|
||||
}
|
||||
|
||||
fn render_snapshot(&self, renderer: &mut GlesRenderer) -> TileRenderSnapshot {
|
||||
fn render_snapshot(
|
||||
&self,
|
||||
renderer: &mut GlesRenderer,
|
||||
mut xray: Option<&mut Xray>,
|
||||
xray_has_blocked_out_layers: bool,
|
||||
xray_pos: XrayPos,
|
||||
) -> TileRenderSnapshot {
|
||||
let _span = tracy_client::span!("Tile::render_snapshot");
|
||||
|
||||
let mut contents = Vec::new();
|
||||
self.render(
|
||||
RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Output,
|
||||
renderer,
|
||||
xray: xray.as_deref(),
|
||||
},
|
||||
Point::from((0., 0.)),
|
||||
xray_pos,
|
||||
false,
|
||||
&mut |elem| contents.push(elem),
|
||||
);
|
||||
|
||||
let mut contents_with_blocked_out_bg = None;
|
||||
|
||||
// Do a bit of pointer surgery on Xray.
|
||||
//
|
||||
// The idea is to avoid the combinatorial combination of rendering snapshots for target
|
||||
// (Output, Screencast) × Xray target (Output, Screencast, ScreenCapture).
|
||||
//
|
||||
// Our main goals:
|
||||
// - Everything must look unblocked for RenderTarget::Output.
|
||||
// - If anything is potentially blocked-out, it must not show up on any screen capture.
|
||||
//
|
||||
// Right above we rendered a fully-unblocked snapshot for the Output, so that's covered.
|
||||
//
|
||||
// Next, *only if Xray has any blocked-out surfaces* (which is a rare case), we will render
|
||||
// a snapshot where the window itself is unblocked, but the Xray background is blocked. To
|
||||
// do this, we swap the Output target buffers in Xray with the Screencast target buffers
|
||||
// (which were prepared for us higher up the stack).
|
||||
//
|
||||
// Finally, we render a fully blocked-out snapshot. If Xray has blocked-out surfaces, then
|
||||
// Xray's Screencast buffers are already filled-in, but if not, then we swap in the Output
|
||||
// buffers, to avoid an extra render. This is safe since we know there are no blocked
|
||||
// surfaces there.
|
||||
let output_idx = RenderTarget::Output as usize;
|
||||
let screencast_idx = RenderTarget::Screencast as usize;
|
||||
let mut screencast_background = None;
|
||||
let mut screencast_backdrop = None;
|
||||
let mut output_background = None;
|
||||
let mut output_backdrop = None;
|
||||
if let Some(xray) = &mut xray {
|
||||
screencast_background = Some(Rc::clone(&xray.background[screencast_idx]));
|
||||
screencast_backdrop = Some(Rc::clone(&xray.backdrop[screencast_idx]));
|
||||
output_background = Some(Rc::clone(&xray.background[output_idx]));
|
||||
output_backdrop = Some(Rc::clone(&xray.backdrop[output_idx]));
|
||||
|
||||
if xray_has_blocked_out_layers {
|
||||
xray.background[output_idx] = screencast_background.clone().unwrap();
|
||||
xray.backdrop[output_idx] = screencast_backdrop.clone().unwrap();
|
||||
|
||||
let mut contents = Vec::new();
|
||||
self.render(
|
||||
RenderCtx {
|
||||
target: RenderTarget::Output,
|
||||
renderer,
|
||||
xray: Some(xray),
|
||||
},
|
||||
Point::from((0., 0.)),
|
||||
xray_pos,
|
||||
false,
|
||||
&mut |elem| contents.push(elem),
|
||||
);
|
||||
contents_with_blocked_out_bg = Some(contents);
|
||||
} else {
|
||||
xray.background[screencast_idx] = output_background.clone().unwrap();
|
||||
xray.backdrop[screencast_idx] = output_backdrop.clone().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// A bit of a hack to render blocked out as for screencast, but I think it's fine here.
|
||||
let mut blocked_out_contents = Vec::new();
|
||||
self.render(
|
||||
RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Screencast,
|
||||
renderer,
|
||||
xray: xray.as_deref(),
|
||||
},
|
||||
Point::from((0., 0.)),
|
||||
xray_pos,
|
||||
false,
|
||||
&mut |elem| blocked_out_contents.push(elem),
|
||||
);
|
||||
|
||||
// Put everything back to normal.
|
||||
if let Some(xray) = &mut xray {
|
||||
if xray_has_blocked_out_layers {
|
||||
xray.background[output_idx] = output_background.take().unwrap();
|
||||
xray.backdrop[output_idx] = output_backdrop.take().unwrap();
|
||||
} else {
|
||||
xray.background[screencast_idx] = screencast_background.take().unwrap();
|
||||
xray.backdrop[screencast_idx] = screencast_backdrop.take().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
RenderSnapshot {
|
||||
contents,
|
||||
contents_with_blocked_out_bg,
|
||||
blocked_out_contents,
|
||||
block_out_from: self.window.rules().block_out_from,
|
||||
size: self.animated_tile_size(),
|
||||
texture: Default::default(),
|
||||
texture_with_blocked_out_bg: Default::default(),
|
||||
blocked_out_texture: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
+22
-4
@@ -32,6 +32,7 @@ use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::xray::{Xray, XrayPos};
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::utils::id::IdCounter;
|
||||
use crate::utils::transaction::{Transaction, TransactionBlocker};
|
||||
@@ -1627,17 +1628,21 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
pub fn render_scrolling<R: NiriRenderer>(
|
||||
&self,
|
||||
ctx: RenderCtx<R>,
|
||||
xray_pos: XrayPos,
|
||||
focus_ring: bool,
|
||||
push: &mut dyn FnMut(WorkspaceRenderElement<R>),
|
||||
) {
|
||||
let scrolling_focus_ring = focus_ring && !self.floating_is_active();
|
||||
self.scrolling
|
||||
.render(ctx, scrolling_focus_ring, &mut |elem| push(elem.into()));
|
||||
.render(ctx, xray_pos, scrolling_focus_ring, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render_floating<R: NiriRenderer>(
|
||||
&self,
|
||||
ctx: RenderCtx<R>,
|
||||
xray_pos: XrayPos,
|
||||
focus_ring: bool,
|
||||
push: &mut dyn FnMut(WorkspaceRenderElement<R>),
|
||||
) {
|
||||
@@ -1648,7 +1653,7 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
let view_rect = Rectangle::from_size(self.view_size);
|
||||
let floating_focus_ring = focus_ring && self.floating_is_active();
|
||||
self.floating
|
||||
.render(ctx, view_rect, floating_focus_ring, &mut |elem| {
|
||||
.render(ctx, xray_pos, view_rect, floating_focus_ring, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}
|
||||
@@ -1682,14 +1687,27 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
) || !self.render_above_top_layer()
|
||||
}
|
||||
|
||||
pub fn store_unmap_snapshot_if_empty(&mut self, renderer: &mut GlesRenderer, window: &W::Id) {
|
||||
pub fn store_unmap_snapshot_if_empty(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
xray: Option<&mut Xray>,
|
||||
xray_has_blocked_out_layers: bool,
|
||||
xray_pos: XrayPos,
|
||||
window: &W::Id,
|
||||
) {
|
||||
let view_size = self.view_size();
|
||||
for (tile, tile_pos) in self.tiles_with_render_positions_mut(false) {
|
||||
if tile.window().id() == window {
|
||||
let view_pos = Point::from((-tile_pos.x, -tile_pos.y));
|
||||
let view_rect = Rectangle::new(view_pos, view_size);
|
||||
tile.update_render_elements(false, view_rect);
|
||||
tile.store_unmap_snapshot_if_empty(renderer);
|
||||
let xray_pos = xray_pos.offset(tile_pos);
|
||||
tile.store_unmap_snapshot_if_empty(
|
||||
renderer,
|
||||
xray,
|
||||
xray_has_blocked_out_layers,
|
||||
xray_pos,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+280
-17
@@ -38,7 +38,8 @@ use smithay::desktop::utils::{
|
||||
bbox_from_surface_tree, output_update, send_dmabuf_feedback_surface_tree,
|
||||
send_frames_surface_tree, surface_presentation_feedback_flags_from_states,
|
||||
surface_primary_scanout_output, take_presentation_feedback_surface_tree,
|
||||
under_from_surface_tree, update_surface_primary_scanout_output, OutputPresentationFeedback,
|
||||
under_from_surface_tree, update_surface_primary_scanout_output, with_surfaces_surface_tree,
|
||||
OutputPresentationFeedback,
|
||||
};
|
||||
use smithay::desktop::{
|
||||
find_popup_root_surface, layer_map_for_output, LayerMap, LayerSurface, PopupGrab, PopupManager,
|
||||
@@ -154,6 +155,7 @@ use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::surface::push_elements_from_surface_tree;
|
||||
use crate::render_helpers::texture::TextureBuffer;
|
||||
use crate::render_helpers::xray::{Xray, XrayPos};
|
||||
use crate::render_helpers::{
|
||||
encompassing_geo, render_to_dmabuf, render_to_encompassing_texture, render_to_shm,
|
||||
render_to_texture, render_to_vec, shaders, RenderCtx, RenderTarget,
|
||||
@@ -471,6 +473,7 @@ pub struct OutputState {
|
||||
/// Solid color buffer for the backdrop that we use instead of clearing to avoid damage
|
||||
/// tracking issues and make screenshots easier.
|
||||
pub backdrop_buffer: SolidColorBuffer,
|
||||
pub xray: Xray,
|
||||
pub lock_render_state: LockRenderState,
|
||||
pub lock_surface: Option<LockSurface>,
|
||||
pub lock_color_buffer: SolidColorBuffer,
|
||||
@@ -2024,6 +2027,51 @@ impl State {
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
|
||||
pub fn store_unmap_snapshot(&mut self, window: &Window, output: Option<&Output>) {
|
||||
// The unmapping tile may have an xray background, in which case we will render xray
|
||||
// elements, so they need to be updated.
|
||||
self.niri.update_xray_render_elements(output);
|
||||
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
if let Some(output) = output {
|
||||
let mut ctx = RenderCtx {
|
||||
target: RenderTarget::Output,
|
||||
renderer,
|
||||
xray: None,
|
||||
};
|
||||
|
||||
self.niri.fill_xray_elements(ctx.r(), output);
|
||||
|
||||
// If any background layer has block_out_from, also fill the Screencast xray
|
||||
// buffer so the unmap snapshot can render a buffer with blocked-out background.
|
||||
//
|
||||
// This will be used in Tile::render_snapshot().
|
||||
let has_blocked_out = self.niri.has_blocked_out_background_layers(output);
|
||||
if has_blocked_out {
|
||||
let screencast_ctx = RenderCtx {
|
||||
target: RenderTarget::Screencast,
|
||||
..ctx.r()
|
||||
};
|
||||
self.niri.fill_xray_elements(screencast_ctx, output);
|
||||
}
|
||||
|
||||
let state = self.niri.output_state.get_mut(output).unwrap();
|
||||
self.niri.layout.store_unmap_snapshot(
|
||||
renderer,
|
||||
Some(&mut state.xray),
|
||||
has_blocked_out,
|
||||
window,
|
||||
);
|
||||
|
||||
self.niri.clear_xray_elements(output);
|
||||
} else {
|
||||
self.niri
|
||||
.layout
|
||||
.store_unmap_snapshot(renderer, None, false, window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xdp-gnome-screencast"))]
|
||||
pub fn set_dynamic_cast_target(&mut self, _target: CastTarget) {}
|
||||
|
||||
@@ -2805,6 +2853,7 @@ impl Niri {
|
||||
vblank_throttle: VBlankThrottle::new(self.event_loop.clone(), name.connector.clone()),
|
||||
frame_callback_sequence: 0,
|
||||
backdrop_buffer: SolidColorBuffer::new(size, backdrop_color),
|
||||
xray: Xray::new(),
|
||||
lock_render_state,
|
||||
lock_surface: None,
|
||||
lock_color_buffer: SolidColorBuffer::new(size, CLEAR_COLOR_LOCKED),
|
||||
@@ -3985,6 +4034,7 @@ impl Niri {
|
||||
}
|
||||
|
||||
pub fn update_render_elements(&mut self, output: Option<&Output>) {
|
||||
self.update_xray_render_elements(output);
|
||||
self.layout.update_render_elements(output);
|
||||
|
||||
for (out, state) in self.output_state.iter_mut() {
|
||||
@@ -4011,6 +4061,46 @@ impl Niri {
|
||||
}
|
||||
}
|
||||
|
||||
// Updates only those render elements that go in the xray buffer.
|
||||
pub fn update_xray_render_elements(&mut self, output: Option<&Output>) {
|
||||
for (out, state) in self.output_state.iter_mut() {
|
||||
if output.is_none_or(|output| out == output) {
|
||||
let scale = Scale::from(out.current_scale().fractional_scale());
|
||||
let mode = out.current_mode().unwrap();
|
||||
let transform = out.current_transform();
|
||||
let size = transform.transform_size(mode.size);
|
||||
|
||||
state.xray.workspaces.clear();
|
||||
let mon = self.layout.monitor_for_output(out).unwrap();
|
||||
for (ws, geo) in mon.workspaces_with_render_geo() {
|
||||
let bg_color = ws.render_background().color();
|
||||
state.xray.workspaces.push((geo, bg_color));
|
||||
}
|
||||
state.xray.backdrop_color = state.backdrop_buffer.color();
|
||||
for buf in &state.xray.background {
|
||||
let mut buffer = buf.borrow_mut();
|
||||
buffer.update_size(size, scale);
|
||||
}
|
||||
for buf in &state.xray.backdrop {
|
||||
let mut buffer = buf.borrow_mut();
|
||||
buffer.update_size(size, scale);
|
||||
}
|
||||
|
||||
let layer_map = layer_map_for_output(out);
|
||||
for surface in layer_map.layers_on(Layer::Background) {
|
||||
let Some(mapped) = self.mapped_layer_surfaces.get_mut(surface) else {
|
||||
continue;
|
||||
};
|
||||
let Some(geo) = layer_map.layer_geometry(surface) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
mapped.update_render_elements(geo.size.to_f64());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_shaders(&mut self) {
|
||||
self.layout.update_shaders();
|
||||
|
||||
@@ -4050,6 +4140,25 @@ impl Niri {
|
||||
}
|
||||
}
|
||||
|
||||
self.fill_xray_elements(ctx.as_gles(), output);
|
||||
|
||||
// Reborrow to shorten lifetime to be able to put in xray.
|
||||
let mut ctx = ctx.r();
|
||||
let state = self.output_state.get(output).unwrap();
|
||||
ctx.xray = Some(&state.xray);
|
||||
|
||||
self.render_inner(ctx, output, include_pointer, push);
|
||||
|
||||
self.clear_xray_elements(output);
|
||||
}
|
||||
|
||||
fn render_inner<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
output: &Output,
|
||||
include_pointer: bool,
|
||||
push: &mut dyn FnMut(OutputRenderElements<R>),
|
||||
) {
|
||||
let state = self.output_state.get(output).unwrap();
|
||||
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
||||
|
||||
@@ -4168,17 +4277,21 @@ impl Niri {
|
||||
}};
|
||||
}
|
||||
macro_rules! push_normal_from_layer {
|
||||
($layer:expr, $backdrop:expr, $push:expr) => {{
|
||||
self.render_layer_normal(ctx.r(), &layer_map, $layer, $backdrop, $push);
|
||||
($layer:expr, $xray_pos:expr, $backdrop:expr, $push:expr) => {{
|
||||
self.render_layer_normal(ctx.r(), &layer_map, $layer, $xray_pos, $backdrop, $push);
|
||||
}};
|
||||
($layer:expr, true) => {{
|
||||
push_normal_from_layer!($layer, true, &mut |elem| push(elem.into()));
|
||||
push_normal_from_layer!($layer, XrayPos::default(), true, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}};
|
||||
($layer:expr, $push:expr) => {{
|
||||
push_normal_from_layer!($layer, false, $push);
|
||||
($layer:expr, $xray_pos:expr, $push:expr) => {{
|
||||
push_normal_from_layer!($layer, $xray_pos, false, $push);
|
||||
}};
|
||||
($layer:expr) => {{
|
||||
push_normal_from_layer!($layer, false, &mut |elem| push(elem.into()));
|
||||
push_normal_from_layer!($layer, XrayPos::default(), false, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -4236,8 +4349,9 @@ impl Niri {
|
||||
mon.render_workspaces(ctx.r(), focus_ring, &mut |elem| push(elem.into()));
|
||||
|
||||
for (ws, geo) in mon.workspaces_with_render_geo() {
|
||||
push_normal_from_layer!(Layer::Bottom, process!(geo));
|
||||
push_normal_from_layer!(Layer::Background, process!(geo));
|
||||
let xray_pos = XrayPos::new(geo.loc, zoom);
|
||||
push_normal_from_layer!(Layer::Bottom, xray_pos, process!(geo));
|
||||
push_normal_from_layer!(Layer::Background, xray_pos, process!(geo));
|
||||
|
||||
process!(geo)(ws.render_background());
|
||||
}
|
||||
@@ -4252,6 +4366,90 @@ impl Niri {
|
||||
push(backdrop);
|
||||
}
|
||||
|
||||
pub fn fill_xray_elements(&self, mut ctx: RenderCtx<GlesRenderer>, output: &Output) {
|
||||
let _span = tracy_client::span!("Niri::fill_xray_elements");
|
||||
|
||||
// Make sure the xrayed elements themselves cannot use xray by mistake.
|
||||
ctx.xray = None;
|
||||
|
||||
let state = self.output_state.get(output).unwrap();
|
||||
let xray = &state.xray;
|
||||
let layer_map = layer_map_for_output(output);
|
||||
|
||||
// FIXME: it would be cool to call this code on-demand. It's even relatively simple to do:
|
||||
// move this function to after the render_inner() call, check if
|
||||
// Rc::strong_count(&xray.background) > 1, and only then construct the elements. This way,
|
||||
// only if something referenced the xray buffer will the elements get constructed.
|
||||
//
|
||||
// Unfortunately, currently this runs into an important limitation: offscreens are rendered
|
||||
// immediately deep inside render_inner(), and when they are, they already need the xray
|
||||
// elements filled.
|
||||
//
|
||||
// Perhaps in the future when offscreen rendering becomes on-demand, this optimization will
|
||||
// be possible.
|
||||
|
||||
let mut buffer = xray.background[ctx.target as usize].borrow_mut();
|
||||
{
|
||||
let elements = buffer.elements();
|
||||
elements.clear();
|
||||
self.render_layer_normal(
|
||||
ctx.r(),
|
||||
&layer_map,
|
||||
Layer::Background,
|
||||
XrayPos::default(),
|
||||
false,
|
||||
&mut |elem| elements.push(elem.into()),
|
||||
);
|
||||
// Avoid unused capacity remaining forever.
|
||||
elements.shrink_to_fit();
|
||||
}
|
||||
|
||||
let mut buffer = xray.backdrop[ctx.target as usize].borrow_mut();
|
||||
{
|
||||
let elements = buffer.elements();
|
||||
elements.clear();
|
||||
self.render_layer_normal(
|
||||
ctx.r(),
|
||||
&layer_map,
|
||||
Layer::Background,
|
||||
XrayPos::default(),
|
||||
true,
|
||||
&mut |elem| elements.push(elem.into()),
|
||||
);
|
||||
// Avoid unused capacity remaining forever.
|
||||
elements.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_xray_elements(&self, output: &Output) {
|
||||
let state = self.output_state.get(output).unwrap();
|
||||
let xray = &state.xray;
|
||||
|
||||
// Clear the xray elements for all render targets after all rendering that could use them
|
||||
// did so.
|
||||
for buf in &xray.background {
|
||||
buf.borrow_mut().elements().clear();
|
||||
}
|
||||
for buf in &xray.backdrop {
|
||||
buf.borrow_mut().elements().clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if any background layer surface has `block_out_from` set.
|
||||
pub fn has_blocked_out_background_layers(&self, output: &Output) -> bool {
|
||||
let layer_map = layer_map_for_output(output);
|
||||
for for_backdrop in [false, true] {
|
||||
for (mapped, _geo) in
|
||||
self.layers_in_render_order(&layer_map, Layer::Background, for_backdrop)
|
||||
{
|
||||
if mapped.rules().block_out_from.is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn layers_in_render_order<'a>(
|
||||
&'a self,
|
||||
layer_map: &'a LayerMap,
|
||||
@@ -4271,16 +4469,20 @@ impl Niri {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_layer_normal<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
layer_map: &LayerMap,
|
||||
layer: Layer,
|
||||
xray_pos: XrayPos,
|
||||
for_backdrop: bool,
|
||||
push: &mut dyn FnMut(LayerSurfaceRenderElement<R>),
|
||||
) {
|
||||
for (mapped, geo) in self.layers_in_render_order(layer_map, layer, for_backdrop) {
|
||||
mapped.render_normal(ctx.r(), geo.loc.to_f64(), push);
|
||||
let loc = geo.loc.to_f64();
|
||||
let xray_pos = xray_pos.offset(loc);
|
||||
mapped.render_normal(ctx.r(), loc, xray_pos, push);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4558,17 +4760,65 @@ impl Niri {
|
||||
});
|
||||
}
|
||||
|
||||
for surface in layer_map_for_output(output).layers() {
|
||||
surface.with_surfaces(|surface, states| {
|
||||
update_surface_primary_scanout_output(
|
||||
surface,
|
||||
let xray = &self.output_state[output].xray;
|
||||
let xray_bg = xray.background[RenderTarget::Output as usize].borrow();
|
||||
let xray_bd = xray.backdrop[RenderTarget::Output as usize].borrow();
|
||||
|
||||
for layer in layer_map_for_output(output).layers() {
|
||||
let surface = layer.wl_surface();
|
||||
let is_background = layer.layer() == Layer::Background;
|
||||
|
||||
with_surfaces_surface_tree(surface, |surface, states| {
|
||||
let primary_scanout_output = states
|
||||
.data_map
|
||||
.get_or_insert_threadsafe(Mutex::<PrimaryScanoutOutput>::default);
|
||||
let mut primary_scanout_output = primary_scanout_output.lock().unwrap();
|
||||
let mut id = Id::from_wayland_resource(surface);
|
||||
|
||||
// Background layers may be invisible normally but visible through an xray
|
||||
// background effect. Try to find it and use the xray element's id in this case.
|
||||
//
|
||||
// FIXME: this won't work if there's another layer of offscreen (e.g. window with
|
||||
// an xray background during its opening animation). But hopefully with the
|
||||
// refactor to draw background effects outside offscreens it won't be a problem.
|
||||
if is_background && !render_element_states.element_was_presented(id.clone()) {
|
||||
// A layer may be present either in background or backdrop, never in both.
|
||||
if xray_bg
|
||||
.render_element_states()
|
||||
.is_some_and(|s| s.element_was_presented(id.clone()))
|
||||
{
|
||||
id = xray_bg.id().clone();
|
||||
} else if xray_bd
|
||||
.render_element_states()
|
||||
.is_some_and(|s| s.element_was_presented(id.clone()))
|
||||
{
|
||||
id = xray_bd.id().clone();
|
||||
}
|
||||
}
|
||||
|
||||
primary_scanout_output.update_from_render_element_states(
|
||||
id,
|
||||
output,
|
||||
states,
|
||||
render_element_states,
|
||||
// Layer surfaces are shown only on one output at a time.
|
||||
|_, _, output, _| output,
|
||||
);
|
||||
});
|
||||
|
||||
// Popups never go into xray buffers.
|
||||
for (popup, _) in PopupManager::popups_for_surface(surface) {
|
||||
let surface = popup.wl_surface();
|
||||
with_surfaces_surface_tree(surface, |surface, states| {
|
||||
update_surface_primary_scanout_output(
|
||||
surface,
|
||||
output,
|
||||
states,
|
||||
render_element_states,
|
||||
// Layer surfaces are shown only on one output at a time.
|
||||
|_, _, output, _| output,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(surface) = &self.output_state[output].lock_surface {
|
||||
@@ -4920,6 +5170,7 @@ impl Niri {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::ScreenCapture,
|
||||
xray: None,
|
||||
};
|
||||
self.render_to_vec(ctx, output, true)
|
||||
});
|
||||
@@ -4987,6 +5238,7 @@ impl Niri {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::ScreenCapture,
|
||||
xray: None,
|
||||
};
|
||||
let elements = self.render_to_vec(ctx, output, screencopy.overlay_cursor());
|
||||
|
||||
@@ -5112,7 +5364,11 @@ impl Niri {
|
||||
RenderTarget::ScreenCapture,
|
||||
];
|
||||
let screenshot = targets.map(|target| {
|
||||
let ctx = RenderCtx { renderer, target };
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target,
|
||||
xray: None,
|
||||
};
|
||||
let elements = self.render_to_vec(ctx, &output, false);
|
||||
let elements = elements.iter().rev();
|
||||
|
||||
@@ -5193,6 +5449,7 @@ impl Niri {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::ScreenCapture,
|
||||
xray: None,
|
||||
};
|
||||
let elements = self.render_to_vec(ctx, output, include_pointer);
|
||||
let elements = elements.iter().rev();
|
||||
@@ -5247,6 +5504,7 @@ impl Niri {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::ScreenCapture,
|
||||
xray: None,
|
||||
};
|
||||
mapped.render(
|
||||
ctx,
|
||||
@@ -5411,6 +5669,7 @@ impl Niri {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::ScreenCapture,
|
||||
xray: None,
|
||||
};
|
||||
let elements = self.render_to_vec(ctx, &output, include_pointer);
|
||||
let elements = elements.iter().rev();
|
||||
@@ -5885,7 +6144,11 @@ impl Niri {
|
||||
RenderTarget::ScreenCapture,
|
||||
];
|
||||
let textures = targets.map(|target| {
|
||||
let ctx = RenderCtx { renderer, target };
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target,
|
||||
xray: None,
|
||||
};
|
||||
let elements = self.render_to_vec(ctx, &output, false);
|
||||
let elements = elements.iter().rev();
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use niri_config::CornerRadius;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Rectangle};
|
||||
use smithay::wayland::compositor::{with_states, SurfaceData};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::damage::ExtraDamage;
|
||||
use crate::render_helpers::xray::{XrayElement, XrayPos};
|
||||
use crate::render_helpers::RenderCtx;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BackgroundEffect {
|
||||
/// Damage when options change.
|
||||
damage: ExtraDamage,
|
||||
/// Corner radius for clipping.
|
||||
///
|
||||
/// Stored here in addition to `RenderParams` to damage when it changes.
|
||||
// FIXME: would be good to remove this duplication of radius.
|
||||
corner_radius: CornerRadius,
|
||||
options: Options,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Options {
|
||||
pub xray: bool,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn is_visible(&self) -> bool {
|
||||
self.xray
|
||||
}
|
||||
}
|
||||
|
||||
/// Render-time parameters.
|
||||
#[derive(Debug)]
|
||||
pub struct RenderParams {
|
||||
/// Geometry of the background effect.
|
||||
pub geometry: Rectangle<f64, Logical>,
|
||||
/// Geometry and radius for clipping in the same coordinate space as `geometry`.
|
||||
pub clip: Option<(Rectangle<f64, Logical>, CornerRadius)>,
|
||||
/// Scale to use for rounding to physical pixels.
|
||||
pub scale: f64,
|
||||
}
|
||||
|
||||
impl RenderParams {
|
||||
fn fit_clip_radius(&mut self) {
|
||||
if let Some((geo, radius)) = &mut self.clip {
|
||||
// HACK: increase radius to avoid slight bleed on rounded corners.
|
||||
*radius = radius.expanded_by(1.);
|
||||
|
||||
*radius = radius.fit_to(geo.size.w as f32, geo.size.h as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
niri_render_elements! {
|
||||
BackgroundEffectElement => {
|
||||
Xray = XrayElement,
|
||||
ExtraDamage = ExtraDamage,
|
||||
}
|
||||
}
|
||||
|
||||
impl BackgroundEffect {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
damage: ExtraDamage::new(),
|
||||
corner_radius: CornerRadius::default(),
|
||||
options: Options::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_render_elements(
|
||||
&mut self,
|
||||
corner_radius: CornerRadius,
|
||||
effect: niri_config::BackgroundEffect,
|
||||
) {
|
||||
let options = Options {
|
||||
xray: effect.xray == Some(true),
|
||||
};
|
||||
|
||||
if self.options == options && self.corner_radius == corner_radius {
|
||||
return;
|
||||
}
|
||||
|
||||
self.options = options;
|
||||
self.corner_radius = corner_radius;
|
||||
self.damage.damage_all();
|
||||
}
|
||||
|
||||
pub fn is_visible(&self) -> bool {
|
||||
self.options.is_visible()
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
ctx: RenderCtx<GlesRenderer>,
|
||||
mut params: RenderParams,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
) {
|
||||
if !self.is_visible() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(clip) = &mut params.clip {
|
||||
clip.1 = self.corner_radius;
|
||||
}
|
||||
params.fit_clip_radius();
|
||||
|
||||
let damage = self.damage.render(params.geometry);
|
||||
|
||||
if self.options.xray {
|
||||
let Some(xray) = ctx.xray else {
|
||||
return;
|
||||
};
|
||||
|
||||
push(damage.into());
|
||||
xray.render(ctx, params, xray_pos, &mut |elem| push(elem.into()));
|
||||
} else {
|
||||
// Render non-xray effect.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-surface background effect stored in its data map.
|
||||
struct SurfaceBackgroundEffect(Mutex<BackgroundEffect>);
|
||||
|
||||
impl SurfaceBackgroundEffect {
|
||||
fn get(states: &SurfaceData) -> &Self {
|
||||
states
|
||||
.data_map
|
||||
.get_or_insert(|| SurfaceBackgroundEffect(Mutex::new(BackgroundEffect::new())))
|
||||
}
|
||||
}
|
||||
|
||||
// Silence, Clippy
|
||||
// A Smithay user is talking
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_for_tile(
|
||||
ctx: RenderCtx<GlesRenderer>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
scale: f64,
|
||||
clip_to_geometry: bool,
|
||||
surface: &WlSurface,
|
||||
radius: CornerRadius,
|
||||
effect: niri_config::BackgroundEffect,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
) {
|
||||
with_states(surface, |states| {
|
||||
let background_effect = SurfaceBackgroundEffect::get(states);
|
||||
let mut background_effect = background_effect.0.lock().unwrap();
|
||||
|
||||
background_effect.update_render_elements(radius, effect);
|
||||
|
||||
if !background_effect.is_visible() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Effects not requested by the surface itself are drawn to match the geometry.
|
||||
let _ = clip_to_geometry;
|
||||
let params = RenderParams {
|
||||
geometry,
|
||||
clip: Some((geometry, CornerRadius::default())),
|
||||
scale,
|
||||
};
|
||||
|
||||
let xray_pos = xray_pos.offset(params.geometry.loc - geometry.loc);
|
||||
background_effect.render(ctx, params, xray_pos, push);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
use std::mem;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::damage::OutputDamageTracker;
|
||||
use smithay::backend::renderer::element::{Id, RenderElementStates};
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::utils::CommitCounter;
|
||||
use smithay::backend::renderer::{
|
||||
Bind as _, Color32F, ContextId, Offscreen as _, Renderer as _, Texture,
|
||||
};
|
||||
use smithay::utils::{Buffer, Logical, Physical, Scale, Size, Transform};
|
||||
|
||||
use crate::niri::OutputRenderElements;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EffectBuffer {
|
||||
/// Id to be used for this effect buffer's elements.
|
||||
id: Id,
|
||||
|
||||
/// Size of the effect buffer.
|
||||
size: Size<i32, Buffer>,
|
||||
/// Scale of the effect buffer.
|
||||
scale: Scale<f64>,
|
||||
|
||||
/// Elements to be rendered on demand.
|
||||
elements: Elements,
|
||||
/// Offscreen buffer where elements get rendered.
|
||||
offscreen: Option<Offscreen>,
|
||||
|
||||
/// Commit counter for the offscreen texture.
|
||||
commit_counter: CommitCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Elements {
|
||||
/// Contents remain unchanged.
|
||||
Unchanged(
|
||||
// Storage to avoid reallocating it every time.
|
||||
Vec<OutputRenderElements<GlesRenderer>>,
|
||||
),
|
||||
/// New contents, need to check damage and render.
|
||||
New(Vec<OutputRenderElements<GlesRenderer>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Offscreen {
|
||||
/// The texture with the offscreen contents.
|
||||
texture: GlesTexture,
|
||||
/// Id of the renderer context that the texture comes from.
|
||||
renderer_context_id: ContextId<GlesTexture>,
|
||||
/// Scale of the texture.
|
||||
scale: Scale<f64>,
|
||||
/// Damage tracker for drawing to the texture.
|
||||
damage: OutputDamageTracker,
|
||||
/// Render element states from the last render into the offscreen.
|
||||
states: RenderElementStates,
|
||||
}
|
||||
|
||||
impl Default for Elements {
|
||||
fn default() -> Self {
|
||||
Self::Unchanged(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectBuffer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: Id::new(),
|
||||
size: Size::default(),
|
||||
scale: Scale::from(1.),
|
||||
elements: Elements::default(),
|
||||
offscreen: None,
|
||||
commit_counter: CommitCounter::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> CommitCounter {
|
||||
self.commit_counter
|
||||
}
|
||||
|
||||
pub fn logical_size(&self) -> Size<f64, Logical> {
|
||||
self.size.to_f64().to_logical(self.scale, Transform::Normal)
|
||||
}
|
||||
|
||||
pub fn scale(&self) -> Scale<f64> {
|
||||
self.scale
|
||||
}
|
||||
|
||||
pub fn render_element_states(&self) -> Option<&RenderElementStates> {
|
||||
self.offscreen.as_ref().map(|o| &o.states)
|
||||
}
|
||||
|
||||
pub fn update_size(&mut self, size: Size<i32, Physical>, scale: Scale<f64>) {
|
||||
self.size = size.to_logical(1).to_buffer(1, Transform::Normal);
|
||||
self.scale = scale;
|
||||
}
|
||||
|
||||
pub fn elements(&mut self) -> &mut Vec<OutputRenderElements<GlesRenderer>> {
|
||||
// Assume we're going to insert new elements, switch to New.
|
||||
match mem::take(&mut self.elements) {
|
||||
Elements::Unchanged(elements) | Elements::New(elements) => {
|
||||
self.elements = Elements::New(elements);
|
||||
}
|
||||
}
|
||||
let Elements::New(elements) = &mut self.elements else {
|
||||
unreachable!();
|
||||
};
|
||||
elements
|
||||
}
|
||||
|
||||
pub fn prepare(&mut self, renderer: &mut GlesRenderer) -> bool {
|
||||
if let Err(err) = self.prepare_offscreen(renderer) {
|
||||
warn!("error preparing offscreen: {err:?}");
|
||||
return false;
|
||||
};
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn prepare_offscreen(&mut self, renderer: &mut GlesRenderer) -> anyhow::Result<()> {
|
||||
let _span = tracy_client::span!("EffectBuffer::prepare_offscreen");
|
||||
|
||||
// Check if we need to create or recreate the texture.
|
||||
let size_string;
|
||||
let mut reason = "";
|
||||
if let Some(Offscreen {
|
||||
texture,
|
||||
renderer_context_id,
|
||||
..
|
||||
}) = &mut self.offscreen
|
||||
{
|
||||
let old_size = texture.size();
|
||||
if old_size != self.size {
|
||||
size_string = format!(
|
||||
"size changed from {} × {} to {} × {}",
|
||||
old_size.w, old_size.h, self.size.w, self.size.h
|
||||
);
|
||||
reason = &size_string;
|
||||
|
||||
self.offscreen = None;
|
||||
} else if !texture.is_unique_reference() {
|
||||
reason = "not unique";
|
||||
|
||||
self.offscreen = None;
|
||||
} else if *renderer_context_id != renderer.context_id() {
|
||||
reason = "renderer id changed";
|
||||
|
||||
self.offscreen = None;
|
||||
}
|
||||
} else {
|
||||
reason = "first render";
|
||||
}
|
||||
|
||||
let offscreen = if let Some(offscreen) = &mut self.offscreen {
|
||||
offscreen
|
||||
} else {
|
||||
debug!("creating new offscreen texture: {reason}");
|
||||
let span = tracy_client::span!("creating effect offscreen texture");
|
||||
span.emit_text(reason);
|
||||
|
||||
let texture: GlesTexture = renderer
|
||||
.create_buffer(Fourcc::Abgr8888, self.size)
|
||||
.context("error creating texture")?;
|
||||
|
||||
let buffer_size = self.size.to_logical(1, Transform::Normal).to_physical(1);
|
||||
let damage = OutputDamageTracker::new(buffer_size, self.scale, Transform::Normal);
|
||||
|
||||
self.offscreen.insert(Offscreen {
|
||||
texture,
|
||||
renderer_context_id: renderer.context_id(),
|
||||
scale: self.scale,
|
||||
damage,
|
||||
states: RenderElementStates::default(),
|
||||
})
|
||||
};
|
||||
|
||||
// Recreate the damage tracker if the scale changes. We already recreate it for buffer size
|
||||
// changes, and transform is always Normal.
|
||||
if offscreen.scale != self.scale {
|
||||
offscreen.scale = self.scale;
|
||||
|
||||
trace!("recreating damage tracker due to scale change");
|
||||
let buffer_size = self.size.to_logical(1, Transform::Normal).to_physical(1);
|
||||
offscreen.damage = OutputDamageTracker::new(buffer_size, self.scale, Transform::Normal);
|
||||
|
||||
self.commit_counter.increment();
|
||||
}
|
||||
|
||||
// Render the elements if any.
|
||||
let mut elements = match mem::take(&mut self.elements) {
|
||||
Elements::New(elements) => elements,
|
||||
x @ Elements::Unchanged(_) => {
|
||||
// No redrawing necessary.
|
||||
self.elements = x;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let res = {
|
||||
let mut target = renderer
|
||||
.bind(&mut offscreen.texture)
|
||||
.context("error binding texture")?;
|
||||
offscreen
|
||||
.damage
|
||||
.render_output(renderer, &mut target, 1, &elements, Color32F::TRANSPARENT)
|
||||
.context("error rendering")?
|
||||
};
|
||||
|
||||
offscreen.states = res.states;
|
||||
|
||||
if res.damage.is_some() {
|
||||
self.commit_counter.increment();
|
||||
}
|
||||
|
||||
// Clear and put the storage back.
|
||||
elements.clear();
|
||||
self.elements = Elements::Unchanged(elements);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> anyhow::Result<GlesTexture> {
|
||||
let offscreen = self.offscreen.as_mut().context("offscreen is missing")?;
|
||||
Ok(offscreen.texture.clone())
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,14 @@ use solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use self::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::render_helpers::renderer::AsGlesRenderer;
|
||||
use crate::render_helpers::xray::Xray;
|
||||
|
||||
pub mod background_effect;
|
||||
pub mod border;
|
||||
pub mod clipped_surface;
|
||||
pub mod damage;
|
||||
pub mod debug;
|
||||
pub mod effect_buffer;
|
||||
pub mod gradient_fade_texture;
|
||||
pub mod memory;
|
||||
pub mod offscreen;
|
||||
@@ -42,6 +45,7 @@ pub mod snapshot;
|
||||
pub mod solid_color;
|
||||
pub mod surface;
|
||||
pub mod texture;
|
||||
pub mod xray;
|
||||
|
||||
/// A rendering context.
|
||||
///
|
||||
@@ -49,6 +53,7 @@ pub mod texture;
|
||||
pub struct RenderCtx<'a, R> {
|
||||
pub renderer: &'a mut R,
|
||||
pub target: RenderTarget,
|
||||
pub xray: Option<&'a Xray>,
|
||||
}
|
||||
|
||||
impl<'a, R> RenderCtx<'a, R> {
|
||||
@@ -58,6 +63,7 @@ impl<'a, R> RenderCtx<'a, R> {
|
||||
RenderCtx {
|
||||
renderer: self.renderer,
|
||||
target: self.target,
|
||||
xray: self.xray,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +73,7 @@ impl<'a, R: AsGlesRenderer> RenderCtx<'a, R> {
|
||||
RenderCtx {
|
||||
renderer: self.renderer.as_gles_renderer(),
|
||||
target: self.target,
|
||||
xray: self.xray,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +82,7 @@ impl<'a, R: AsGlesRenderer> RenderCtx<'a, R> {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RenderTarget {
|
||||
/// Rendering to display on screen.
|
||||
Output,
|
||||
Output = 0,
|
||||
/// Rendering for a screencast.
|
||||
Screencast,
|
||||
/// Rendering for any other screen capture.
|
||||
@@ -104,6 +111,8 @@ pub trait ToRenderElement {
|
||||
}
|
||||
|
||||
impl RenderTarget {
|
||||
pub const COUNT: usize = 3;
|
||||
|
||||
pub fn should_block_out(self, block_out_from: Option<BlockOutFrom>) -> bool {
|
||||
match block_out_from {
|
||||
None => false,
|
||||
|
||||
@@ -27,6 +27,7 @@ uniform vec4 corner_radius;
|
||||
uniform mat3 input_to_geo;
|
||||
|
||||
float niri_rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius);
|
||||
vec4 postprocess(vec4 color);
|
||||
|
||||
void main() {
|
||||
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0);
|
||||
@@ -37,6 +38,8 @@ void main() {
|
||||
color = vec4(color.rgb, 1.0);
|
||||
#endif
|
||||
|
||||
color = postprocess(color);
|
||||
|
||||
if (coords_geo.x < 0.0 || 1.0 < coords_geo.x || coords_geo.y < 0.0 || 1.0 < coords_geo.y) {
|
||||
// Clip outside geometry.
|
||||
color = vec4(0.0);
|
||||
|
||||
@@ -13,6 +13,7 @@ pub struct Shaders {
|
||||
pub border: Option<ShaderProgram>,
|
||||
pub shadow: Option<ShaderProgram>,
|
||||
pub clipped_surface: Option<GlesTexProgram>,
|
||||
pub postprocess_and_clip: Option<GlesTexProgram>,
|
||||
pub resize: Option<ShaderProgram>,
|
||||
pub gradient_fade: Option<GlesTexProgram>,
|
||||
pub custom_resize: RefCell<Option<ShaderProgram>>,
|
||||
@@ -86,7 +87,8 @@ impl Shaders {
|
||||
.compile_custom_texture_shader(
|
||||
concat!(
|
||||
include_str!("clipped_surface.frag"),
|
||||
include_str!("rounding_alpha.frag")
|
||||
include_str!("rounding_alpha.frag"),
|
||||
"\nvec4 postprocess(vec4 color) { return color; }",
|
||||
),
|
||||
&[
|
||||
UniformName::new("niri_scale", UniformType::_1f),
|
||||
@@ -100,6 +102,26 @@ impl Shaders {
|
||||
})
|
||||
.ok();
|
||||
|
||||
let postprocess_and_clip = renderer
|
||||
.compile_custom_texture_shader(
|
||||
concat!(
|
||||
include_str!("clipped_surface.frag"),
|
||||
include_str!("rounding_alpha.frag"),
|
||||
include_str!("postprocess.frag"),
|
||||
),
|
||||
&[
|
||||
UniformName::new("niri_scale", UniformType::_1f),
|
||||
UniformName::new("geo_size", UniformType::_2f),
|
||||
UniformName::new("corner_radius", UniformType::_4f),
|
||||
UniformName::new("input_to_geo", UniformType::Matrix3x3),
|
||||
UniformName::new("bg_color", UniformType::_4f),
|
||||
],
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("error compiling postprocess_and_clip shader: {err:?}");
|
||||
})
|
||||
.ok();
|
||||
|
||||
let resize = compile_resize_program(renderer, include_str!("resize.frag"))
|
||||
.map_err(|err| {
|
||||
warn!("error compiling resize shader: {err:?}");
|
||||
@@ -120,6 +142,7 @@ impl Shaders {
|
||||
border,
|
||||
shadow,
|
||||
clipped_surface,
|
||||
postprocess_and_clip,
|
||||
resize,
|
||||
gradient_fade,
|
||||
custom_resize: RefCell::new(None),
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
uniform vec4 bg_color;
|
||||
|
||||
vec4 postprocess(vec4 color) {
|
||||
// Mix bg_color behind the texture (both premultiplied alpha).
|
||||
color = color + bg_color * (1.0 - color.a);
|
||||
|
||||
return color;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use super::{render_to_encompassing_texture, ToRenderElement};
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::render_helpers::{RenderCtx, RenderTarget};
|
||||
|
||||
/// Snapshot of a render.
|
||||
#[derive(Debug)]
|
||||
@@ -17,6 +17,12 @@ pub struct RenderSnapshot<C, B> {
|
||||
/// Relative to the geometry.
|
||||
pub contents: Vec<C>,
|
||||
|
||||
/// Contents that are not blocked out, but the background is blocked out.
|
||||
///
|
||||
/// If `None` then the background doesn't have any blocked-out surfaces, and normal `contents`
|
||||
/// can be used instead.
|
||||
pub contents_with_blocked_out_bg: Option<Vec<C>>,
|
||||
|
||||
/// Blocked-out contents.
|
||||
///
|
||||
/// Relative to the geometry.
|
||||
@@ -31,6 +37,9 @@ pub struct RenderSnapshot<C, B> {
|
||||
/// Contents rendered into a texture (lazily).
|
||||
pub texture: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
|
||||
|
||||
/// Contents with blocked-out bg rendered into a texture (lazily).
|
||||
pub texture_with_blocked_out_bg: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
|
||||
|
||||
/// Blocked-out contents rendered into a texture (lazily).
|
||||
pub blocked_out_texture: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
|
||||
}
|
||||
@@ -73,6 +82,33 @@ where
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if ctx.target != RenderTarget::Output && self.contents_with_blocked_out_bg.is_some()
|
||||
{
|
||||
let contents = self.contents_with_blocked_out_bg.as_ref().unwrap();
|
||||
self.texture_with_blocked_out_bg.get_or_init(|| {
|
||||
let _span = tracy_client::span!("RenderSnapshot::texture");
|
||||
|
||||
let elements: Vec<_> = contents
|
||||
.iter()
|
||||
.map(|baked| {
|
||||
baked.to_render_element(Point::from((0., 0.)), scale, 1., Kind::Unspecified)
|
||||
})
|
||||
.collect();
|
||||
|
||||
match render_to_encompassing_texture(
|
||||
ctx.renderer,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
Fourcc::Abgr8888,
|
||||
&elements,
|
||||
) {
|
||||
Ok((texture, _sync_point, geo)) => Some((texture, geo)),
|
||||
Err(err) => {
|
||||
warn!("error rendering contents with blocked-out bg to texture: {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.texture.get_or_init(|| {
|
||||
let _span = tracy_client::span!("RenderSnapshot::texture");
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
use std::array;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use glam::{Mat3, Vec2};
|
||||
use niri_config::CornerRadius;
|
||||
use smithay::backend::renderer::element::{Element, Id, RenderElement};
|
||||
use smithay::backend::renderer::gles::{
|
||||
GlesError, GlesFrame, GlesRenderer, GlesTexProgram, Uniform,
|
||||
};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions};
|
||||
use smithay::backend::renderer::Color32F;
|
||||
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Transform};
|
||||
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
use crate::render_helpers::background_effect::RenderParams;
|
||||
use crate::render_helpers::effect_buffer::EffectBuffer;
|
||||
use crate::render_helpers::renderer::AsGlesFrame as _;
|
||||
use crate::render_helpers::shaders::{mat3_uniform, Shaders};
|
||||
use crate::render_helpers::{RenderCtx, RenderTarget};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Xray {
|
||||
// The buffers are per-render-target to avoid constant rerendering when screencasting.
|
||||
pub background: [Rc<RefCell<EffectBuffer>>; RenderTarget::COUNT],
|
||||
pub backdrop: [Rc<RefCell<EffectBuffer>>; RenderTarget::COUNT],
|
||||
pub backdrop_color: Color32F,
|
||||
pub workspaces: Vec<(Rectangle<f64, Logical>, Color32F)>,
|
||||
}
|
||||
|
||||
/// Position for drawing xray background.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct XrayPos {
|
||||
/// Position of geometry relative to the backdrop in zoomed coordinates.
|
||||
///
|
||||
/// Should be upscaled by `zoom` to get position in backdrop coordinates.
|
||||
pub pos_in_backdrop: Point<f64, Logical>,
|
||||
|
||||
/// Zoom factor between backdrop coordinates and geometry.
|
||||
pub zoom: f64,
|
||||
}
|
||||
|
||||
impl XrayPos {
|
||||
pub fn new(pos_in_backdrop: Point<f64, Logical>, zoom: f64) -> Self {
|
||||
Self {
|
||||
pos_in_backdrop: pos_in_backdrop.downscale(zoom),
|
||||
zoom,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: Point<f64, Logical>) -> Self {
|
||||
self.pos_in_backdrop += offset;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for XrayPos {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pos_in_backdrop: Point::new(0., 0.),
|
||||
zoom: 1.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XrayElement {
|
||||
buffer: Rc<RefCell<EffectBuffer>>,
|
||||
id: Id,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
input_to_clip_geo: Mat3,
|
||||
clip_geo_size: Vec2,
|
||||
corner_radius: CornerRadius,
|
||||
scale: f32,
|
||||
bg_color: Color32F,
|
||||
program: Option<GlesTexProgram>,
|
||||
}
|
||||
|
||||
impl Xray {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
background: array::from_fn(|_| Rc::new(RefCell::new(EffectBuffer::new()))),
|
||||
backdrop: array::from_fn(|_| Rc::new(RefCell::new(EffectBuffer::new()))),
|
||||
backdrop_color: Color32F::TRANSPARENT,
|
||||
workspaces: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
ctx: RenderCtx<GlesRenderer>,
|
||||
params: RenderParams,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(XrayElement),
|
||||
) {
|
||||
let program = Shaders::get(ctx.renderer).postprocess_and_clip.clone();
|
||||
|
||||
let zoom = xray_pos.zoom;
|
||||
let pos_in_backdrop = xray_pos.pos_in_backdrop.upscale(zoom);
|
||||
|
||||
let (clip_geo, corner_radius) = params
|
||||
.clip
|
||||
.unwrap_or((params.geometry, CornerRadius::default()));
|
||||
|
||||
let clip_offset = clip_geo.loc - params.geometry.loc;
|
||||
let clip_pos_in_backdrop = pos_in_backdrop + clip_offset.upscale(zoom);
|
||||
|
||||
let geo_in_backdrop = Rectangle::new(pos_in_backdrop, params.geometry.size.upscale(zoom));
|
||||
|
||||
let mut backdrop = self.backdrop[ctx.target as usize].borrow_mut();
|
||||
let backdrop_geo = Rectangle::from_size(backdrop.logical_size());
|
||||
let intersection_with_backdrop = backdrop_geo.intersection(geo_in_backdrop);
|
||||
|
||||
let mut skip_backdrop = intersection_with_backdrop.is_none();
|
||||
|
||||
let mut background = self.background[ctx.target as usize].borrow_mut();
|
||||
let prev = background.commit();
|
||||
if background.prepare(ctx.renderer) {
|
||||
if background.commit() != prev {
|
||||
trace!("background damaged");
|
||||
}
|
||||
|
||||
let clip_geo_size = Vec2::new(clip_geo.size.w as f32, clip_geo.size.h as f32);
|
||||
let buf_size = background.logical_size();
|
||||
|
||||
for (ws_geo, bg_color) in &self.workspaces {
|
||||
// If the background color is opaque, check if the workspace fully covers the
|
||||
// element. In this case, we will skip the backdrop element since it's fully
|
||||
// covered.
|
||||
//
|
||||
// FIXME: also implement some way to check if the background elements are fully
|
||||
// covered in opaque regions, and not just the niri background color is opaque
|
||||
let crop = if bg_color.is_opaque() && ws_geo.contains_rect(geo_in_backdrop) {
|
||||
skip_backdrop = true;
|
||||
// No need to intersect, we know it's fully covered.
|
||||
Some(geo_in_backdrop)
|
||||
} else {
|
||||
ws_geo.intersection(geo_in_backdrop)
|
||||
};
|
||||
|
||||
let Some(crop) = crop else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If crop contains the intersection with backdrop, then the workspace fully
|
||||
// covers the backdrop, so we can skip the backdrop.
|
||||
//
|
||||
// This can happen when the overview is closed (so workspaces align left/right with
|
||||
// the backdrop) and the window is peeking out off screen to the side. In this
|
||||
// case, this off-screen part is on top of nothing, neither workspace nor backdrop,
|
||||
// but since the window doesn't fully cover the workspace, the check above doesn't
|
||||
// skip the backdrop.
|
||||
if bg_color.is_opaque()
|
||||
&& intersection_with_backdrop
|
||||
.is_some_and(|backdrop| crop.contains_rect(backdrop))
|
||||
{
|
||||
skip_backdrop = true;
|
||||
}
|
||||
|
||||
// This can be different from zoom for surfaces that do not scale with
|
||||
// workspaces, e.g. layer-shell top and overlay layer.
|
||||
let ws_zoom = ws_geo.size / buf_size;
|
||||
|
||||
let src = Rectangle::new(crop.loc - ws_geo.loc, crop.size).downscale(ws_zoom);
|
||||
let src = src.to_buffer(background.scale(), Transform::Normal, &buf_size);
|
||||
|
||||
let buf_size = Vec2::new(buf_size.w as f32, buf_size.h as f32);
|
||||
let pos_against_buf = (clip_pos_in_backdrop - ws_geo.loc).downscale(ws_zoom);
|
||||
let pos_against_buf = Vec2::new(pos_against_buf.x as f32, pos_against_buf.y as f32);
|
||||
let ws_zoom_vec = Vec2::new(ws_zoom.x as f32, ws_zoom.y as f32);
|
||||
let input_to_clip_geo = Mat3::from_scale(ws_zoom_vec / zoom as f32)
|
||||
* Mat3::from_scale(buf_size / clip_geo_size)
|
||||
* Mat3::from_translation(-pos_against_buf / buf_size);
|
||||
|
||||
let mut geometry =
|
||||
Rectangle::new(crop.loc - geo_in_backdrop.loc, crop.size).downscale(zoom);
|
||||
geometry.loc += params.geometry.loc;
|
||||
|
||||
let elem = XrayElement {
|
||||
buffer: self.background[ctx.target as usize].clone(),
|
||||
id: background.id().clone(),
|
||||
geometry,
|
||||
src,
|
||||
input_to_clip_geo,
|
||||
clip_geo_size,
|
||||
corner_radius,
|
||||
scale: params.scale as f32,
|
||||
bg_color: *bg_color,
|
||||
program: program.clone(),
|
||||
};
|
||||
push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
// If the backdrop is fully covered by opaque background, we can skip it.
|
||||
if skip_backdrop {
|
||||
return;
|
||||
}
|
||||
|
||||
let prev = backdrop.commit();
|
||||
if backdrop.prepare(ctx.renderer) {
|
||||
if backdrop.commit() != prev {
|
||||
trace!("backdrop damaged");
|
||||
}
|
||||
|
||||
let buf_size = backdrop.logical_size();
|
||||
let src = geo_in_backdrop.to_buffer(backdrop.scale(), Transform::Normal, &buf_size);
|
||||
|
||||
let mut clip_geo_in_backdrop = Rectangle::new(clip_offset, clip_geo.size).upscale(zoom);
|
||||
clip_geo_in_backdrop.loc += geo_in_backdrop.loc;
|
||||
|
||||
let clip_pos_in_backdrop = Vec2::new(
|
||||
clip_geo_in_backdrop.loc.x as f32,
|
||||
clip_geo_in_backdrop.loc.y as f32,
|
||||
);
|
||||
let clip_geo_size = Vec2::new(
|
||||
clip_geo_in_backdrop.size.w as f32,
|
||||
clip_geo_in_backdrop.size.h as f32,
|
||||
);
|
||||
|
||||
let buf_size = Vec2::new(buf_size.w as f32, buf_size.h as f32);
|
||||
let input_to_clip_geo = Mat3::from_scale(buf_size / clip_geo_size)
|
||||
* Mat3::from_translation(-clip_pos_in_backdrop / buf_size);
|
||||
|
||||
let elem = XrayElement {
|
||||
buffer: self.backdrop[ctx.target as usize].clone(),
|
||||
id: backdrop.id().clone(),
|
||||
geometry: params.geometry,
|
||||
src,
|
||||
input_to_clip_geo,
|
||||
clip_geo_size,
|
||||
corner_radius: corner_radius.scaled_by(zoom as f32),
|
||||
scale: params.scale as f32,
|
||||
bg_color: self.backdrop_color,
|
||||
program: program.clone(),
|
||||
};
|
||||
push(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XrayElement {
|
||||
fn compute_uniforms(&self) -> [Uniform<'static>; 5] {
|
||||
[
|
||||
Uniform::new("niri_scale", self.scale),
|
||||
Uniform::new("geo_size", <[f32; 2]>::from(self.clip_geo_size)),
|
||||
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
|
||||
mat3_uniform("input_to_geo", self.input_to_clip_geo),
|
||||
Uniform::new("bg_color", self.bg_color.components()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for XrayElement {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.buffer.borrow().commit()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
self.src
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.geometry.to_physical_precise_round(scale)
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, _scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
// FIXME: if bg_color alpha is 1 then compute opaque regions here taking corners into
|
||||
// account
|
||||
OpaqueRegions::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for XrayElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
let texture = match buffer.render() {
|
||||
Ok(x) => x,
|
||||
Err(err) => {
|
||||
warn!("error rendering effect buffer: {err:?}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let uniforms = self.program.is_some().then(|| self.compute_uniforms());
|
||||
let uniforms = uniforms.as_ref().map_or(&[][..], |x| &x[..]);
|
||||
|
||||
frame.render_texture_from_to(
|
||||
&texture,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
// FIXME: opaque regions need to be filtered like damage.
|
||||
&[],
|
||||
Transform::Normal,
|
||||
1.,
|
||||
self.program.as_ref(),
|
||||
uniforms,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'render> RenderElement<TtyRenderer<'render>> for XrayElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut TtyFrame<'_, '_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(&self, gles_frame, src, dst, damage, opaque_regions)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -578,6 +578,7 @@ impl Niri {
|
||||
let ctx = RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Screencast,
|
||||
xray: None,
|
||||
};
|
||||
self.render(ctx, output, false, &mut |elem| elements.push(elem.into()));
|
||||
|
||||
|
||||
+29
-2
@@ -28,6 +28,7 @@ use crate::layout::{
|
||||
LayoutElementRenderSnapshot, SizingMode,
|
||||
};
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::background_effect::BackgroundEffectElement;
|
||||
use crate::render_helpers::border::BorderRenderElement;
|
||||
use crate::render_helpers::offscreen::OffscreenData;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
@@ -36,7 +37,8 @@ use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderEleme
|
||||
use crate::render_helpers::surface::{
|
||||
push_elements_from_surface_tree, render_snapshot_from_surface_tree,
|
||||
};
|
||||
use crate::render_helpers::{BakedBuffer, RenderCtx, RenderTarget};
|
||||
use crate::render_helpers::xray::XrayPos;
|
||||
use crate::render_helpers::{background_effect, BakedBuffer, RenderCtx, RenderTarget};
|
||||
use crate::utils::id::IdCounter;
|
||||
use crate::utils::transaction::Transaction;
|
||||
use crate::utils::{
|
||||
@@ -252,7 +254,6 @@ impl Mapped {
|
||||
pub fn new(window: Window, rules: ResolvedWindowRules, hook: HookId) -> Self {
|
||||
let surface = window.wl_surface().expect("no X11 support");
|
||||
let credentials = get_credentials_for_surface(&surface);
|
||||
|
||||
let mut rv = Self {
|
||||
window,
|
||||
id: MappedId::next(),
|
||||
@@ -407,10 +408,12 @@ impl Mapped {
|
||||
|
||||
RenderSnapshot {
|
||||
contents,
|
||||
contents_with_blocked_out_bg: None,
|
||||
blocked_out_contents,
|
||||
block_out_from: self.rules().block_out_from,
|
||||
size,
|
||||
texture: Default::default(),
|
||||
texture_with_blocked_out_bg: Default::default(),
|
||||
blocked_out_texture: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -523,6 +526,7 @@ impl Mapped {
|
||||
RenderCtx {
|
||||
renderer,
|
||||
target: RenderTarget::Screencast,
|
||||
xray: None,
|
||||
},
|
||||
location,
|
||||
scale,
|
||||
@@ -671,6 +675,29 @@ impl LayoutElement for Mapped {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_background_effect(
|
||||
&self,
|
||||
ctx: RenderCtx<GlesRenderer>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
scale: f64,
|
||||
clip_to_geometry: bool,
|
||||
radius: CornerRadius,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
) {
|
||||
background_effect::render_for_tile(
|
||||
ctx,
|
||||
geometry,
|
||||
scale,
|
||||
clip_to_geometry,
|
||||
self.toplevel().wl_surface(),
|
||||
radius,
|
||||
self.rules.background_effect,
|
||||
xray_pos,
|
||||
push,
|
||||
);
|
||||
}
|
||||
|
||||
fn request_size(
|
||||
&mut self,
|
||||
size: Size<i32, Logical>,
|
||||
|
||||
+9
-2
@@ -3,8 +3,8 @@ use std::cmp::{max, min};
|
||||
use niri_config::utils::MergeWith as _;
|
||||
use niri_config::window_rule::{Match, WindowRule};
|
||||
use niri_config::{
|
||||
BlockOutFrom, BorderRule, CornerRadius, FloatingPosition, PresetSize, ShadowRule,
|
||||
TabIndicatorRule,
|
||||
BackgroundEffect, BlockOutFrom, BorderRule, CornerRadius, FloatingPosition, PresetSize,
|
||||
ShadowRule, TabIndicatorRule,
|
||||
};
|
||||
use niri_ipc::ColumnDisplay;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
@@ -119,6 +119,9 @@ pub struct ResolvedWindowRules {
|
||||
|
||||
/// Override whether to set the Tiled xdg-toplevel state on the window.
|
||||
pub tiled_state: Option<bool>,
|
||||
|
||||
/// Background effect configuration.
|
||||
pub background_effect: BackgroundEffect,
|
||||
}
|
||||
|
||||
impl<'a> WindowRef<'a> {
|
||||
@@ -296,6 +299,10 @@ impl ResolvedWindowRules {
|
||||
if let Some(x) = rule.tiled_state {
|
||||
resolved.tiled_state = Some(x);
|
||||
}
|
||||
|
||||
resolved
|
||||
.background_effect
|
||||
.merge_with(&rule.background_effect);
|
||||
}
|
||||
|
||||
resolved.open_on_output = open_on_output.map(|x| x.to_owned());
|
||||
|
||||
Reference in New Issue
Block a user