mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement non-xray background effects
This commit is contained in:
@@ -51,3 +51,14 @@ Xray is automatically enabled by default if any other background effect (like bl
|
||||
This is because it's much more efficient: with xray active, niri only needs to blur the background once, and then can reuse this blurred version with no extra work (since the wallpaper changes very rarely).
|
||||
|
||||
If you have an animated wallpaper, xray will still have to recompute blur every frame, but that happens once and shared among all windows, rather than recomputed separately for each window.
|
||||
|
||||
#### Non-xray effects (experimental)
|
||||
|
||||
You can disable xray with `xray false` background effect window rule.
|
||||
This gives you the normal kind of blur where everything below a window is blurred.
|
||||
Keep in mind that non-xray blur and other non-xray effects are more expensive as niri has to recompute them any time you move the window, or the contents underneath change.
|
||||
|
||||
Non-xray effects are currently experimental because they have some known limitations.
|
||||
|
||||
- They disappear during window open/close animations and while dragging a tiled window.
|
||||
Fixing this requries a refactor to the niri rendering code to defer offscreen rendering, and possibly other refactors.
|
||||
|
||||
@@ -187,6 +187,7 @@ impl MappedLayer {
|
||||
pub fn render_normal<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
ns: Option<usize>,
|
||||
location: Point<f64, Logical>,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(LayerSurfaceRenderElement<R>),
|
||||
@@ -238,6 +239,7 @@ impl MappedLayer {
|
||||
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
|
||||
background_effect::render_for_tile(
|
||||
ctx.as_gles(),
|
||||
ns,
|
||||
geometry,
|
||||
self.scale,
|
||||
false,
|
||||
|
||||
+40
-18
@@ -4271,33 +4271,41 @@ impl Niri {
|
||||
// We use macros instead of closures to avoid borrowing issues (renderer and push() go
|
||||
// into different functions).
|
||||
macro_rules! push_popups_from_layer {
|
||||
($layer:expr, $backdrop:expr, $push:expr) => {{
|
||||
self.render_layer_popups(ctx.r(), &layer_map, $layer, $backdrop, $push);
|
||||
($layer:expr, $ns:expr, $backdrop:expr, $push:expr) => {{
|
||||
self.render_layer_popups(ctx.r(), $ns, &layer_map, $layer, $backdrop, $push);
|
||||
}};
|
||||
($layer:expr, true) => {{
|
||||
push_popups_from_layer!($layer, true, &mut |elem| push(elem.into()));
|
||||
push_popups_from_layer!($layer, None, true, &mut |elem| push(elem.into()));
|
||||
}};
|
||||
($layer:expr, $push:expr) => {{
|
||||
push_popups_from_layer!($layer, false, $push);
|
||||
($layer:expr, $ns:expr, $push:expr) => {{
|
||||
push_popups_from_layer!($layer, $ns, false, $push);
|
||||
}};
|
||||
($layer:expr) => {{
|
||||
push_popups_from_layer!($layer, false, &mut |elem| push(elem.into()));
|
||||
push_popups_from_layer!($layer, None, false, &mut |elem| push(elem.into()));
|
||||
}};
|
||||
}
|
||||
macro_rules! push_normal_from_layer {
|
||||
($layer:expr, $xray_pos:expr, $backdrop:expr, $push:expr) => {{
|
||||
self.render_layer_normal(ctx.r(), &layer_map, $layer, $xray_pos, $backdrop, $push);
|
||||
($layer:expr, $ns:expr, $xray_pos:expr, $backdrop:expr, $push:expr) => {{
|
||||
self.render_layer_normal(
|
||||
ctx.r(),
|
||||
$ns,
|
||||
&layer_map,
|
||||
$layer,
|
||||
$xray_pos,
|
||||
$backdrop,
|
||||
$push,
|
||||
);
|
||||
}};
|
||||
($layer:expr, true) => {{
|
||||
push_normal_from_layer!($layer, XrayPos::default(), true, &mut |elem| {
|
||||
push_normal_from_layer!($layer, None, XrayPos::default(), true, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}};
|
||||
($layer:expr, $xray_pos:expr, $push:expr) => {{
|
||||
push_normal_from_layer!($layer, $xray_pos, false, $push);
|
||||
($layer:expr, $ns:expr, $xray_pos:expr, $push:expr) => {{
|
||||
push_normal_from_layer!($layer, $ns, $xray_pos, false, $push);
|
||||
}};
|
||||
($layer:expr) => {{
|
||||
push_normal_from_layer!($layer, XrayPos::default(), false, &mut |elem| {
|
||||
push_normal_from_layer!($layer, None, XrayPos::default(), false, &mut |elem| {
|
||||
push(elem.into())
|
||||
});
|
||||
}};
|
||||
@@ -4349,17 +4357,27 @@ impl Niri {
|
||||
}};
|
||||
}
|
||||
|
||||
for (_ws, geo) in mon.workspaces_with_render_geo() {
|
||||
push_popups_from_layer!(Layer::Bottom, process!(geo));
|
||||
push_popups_from_layer!(Layer::Background, process!(geo));
|
||||
for (ws, geo) in mon.workspaces_with_render_geo() {
|
||||
let ns = Some(ws.id().get() as usize);
|
||||
push_popups_from_layer!(Layer::Bottom, ns, process!(geo));
|
||||
push_popups_from_layer!(Layer::Background, ns, process!(geo));
|
||||
}
|
||||
|
||||
mon.render_workspaces(ctx.r(), focus_ring, &mut |elem| push(elem.into()));
|
||||
|
||||
for (ws, geo) in mon.workspaces_with_render_geo() {
|
||||
// The render element namespace. This will be set to the workspace index for
|
||||
// elements duplicated across workspaces (i.e. background and bottom layers) in
|
||||
// order to have their non-xray framebuffer effects separated from each other.
|
||||
//
|
||||
// This doesn't have to correspond exactly to workspace id or idx, the only
|
||||
// requirement is that there's only one framebuffer effect element with a given id +
|
||||
// namespace on the frame at once. Id + namespace is used as the cache key in the
|
||||
// damage tracker.
|
||||
let ns = Some(ws.id().get() as usize);
|
||||
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));
|
||||
push_normal_from_layer!(Layer::Bottom, ns, xray_pos, process!(geo));
|
||||
push_normal_from_layer!(Layer::Background, ns, xray_pos, process!(geo));
|
||||
|
||||
process!(geo)(ws.render_background());
|
||||
}
|
||||
@@ -4402,6 +4420,7 @@ impl Niri {
|
||||
elements.clear();
|
||||
self.render_layer_normal(
|
||||
ctx.r(),
|
||||
None,
|
||||
&layer_map,
|
||||
Layer::Background,
|
||||
XrayPos::default(),
|
||||
@@ -4418,6 +4437,7 @@ impl Niri {
|
||||
elements.clear();
|
||||
self.render_layer_normal(
|
||||
ctx.r(),
|
||||
None,
|
||||
&layer_map,
|
||||
Layer::Background,
|
||||
XrayPos::default(),
|
||||
@@ -4481,6 +4501,7 @@ impl Niri {
|
||||
fn render_layer_normal<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
ns: Option<usize>,
|
||||
layer_map: &LayerMap,
|
||||
layer: Layer,
|
||||
xray_pos: XrayPos,
|
||||
@@ -4490,13 +4511,14 @@ impl Niri {
|
||||
for (mapped, geo) in self.layers_in_render_order(layer_map, layer, for_backdrop) {
|
||||
let loc = geo.loc.to_f64();
|
||||
let xray_pos = xray_pos.offset(loc);
|
||||
mapped.render_normal(ctx.r(), loc, xray_pos, push);
|
||||
mapped.render_normal(ctx.r(), ns, loc, xray_pos, push);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_layer_popups<R: NiriRenderer>(
|
||||
&self,
|
||||
mut ctx: RenderCtx<R>,
|
||||
_ns: Option<usize>,
|
||||
layer_map: &LayerMap,
|
||||
layer: Layer,
|
||||
for_backdrop: bool,
|
||||
|
||||
@@ -8,7 +8,9 @@ use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
use crate::handlers::background_effect::get_cached_blur_region;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::blur::BlurOptions;
|
||||
use crate::render_helpers::damage::ExtraDamage;
|
||||
use crate::render_helpers::framebuffer_effect::{FramebufferEffect, FramebufferEffectElement};
|
||||
use crate::render_helpers::xray::{XrayElement, XrayPos};
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::utils::region::TransformedRegion;
|
||||
@@ -16,6 +18,7 @@ use crate::utils::surface_geo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BackgroundEffect {
|
||||
nonxray: FramebufferEffect,
|
||||
/// Damage when options change.
|
||||
damage: ExtraDamage,
|
||||
/// Corner radius for clipping.
|
||||
@@ -72,6 +75,7 @@ impl RenderParams {
|
||||
|
||||
niri_render_elements! {
|
||||
BackgroundEffectElement => {
|
||||
FramebufferEffect = FramebufferEffectElement,
|
||||
Xray = XrayElement,
|
||||
ExtraDamage = ExtraDamage,
|
||||
}
|
||||
@@ -80,6 +84,7 @@ niri_render_elements! {
|
||||
impl BackgroundEffect {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
nonxray: FramebufferEffect::new(),
|
||||
damage: ExtraDamage::new(),
|
||||
corner_radius: CornerRadius::default(),
|
||||
blur_config: niri_config::Blur::default(),
|
||||
@@ -90,6 +95,7 @@ impl BackgroundEffect {
|
||||
/// Damage the background effect, for example when a blur subregion changes.
|
||||
pub fn damage(&mut self) {
|
||||
self.damage.damage_all();
|
||||
self.nonxray.damage();
|
||||
}
|
||||
|
||||
pub fn update_config(&mut self, config: niri_config::Blur) {
|
||||
@@ -99,6 +105,7 @@ impl BackgroundEffect {
|
||||
|
||||
self.blur_config = config;
|
||||
self.damage.damage_all();
|
||||
self.nonxray.damage();
|
||||
}
|
||||
|
||||
pub fn update_render_elements(
|
||||
@@ -134,6 +141,7 @@ impl BackgroundEffect {
|
||||
self.options = options;
|
||||
self.corner_radius = corner_radius;
|
||||
self.damage.damage_all();
|
||||
self.nonxray.damage();
|
||||
}
|
||||
|
||||
pub fn is_visible(&self) -> bool {
|
||||
@@ -143,6 +151,7 @@ impl BackgroundEffect {
|
||||
pub fn render(
|
||||
&self,
|
||||
ctx: RenderCtx<GlesRenderer>,
|
||||
ns: Option<usize>,
|
||||
mut params: RenderParams,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
@@ -161,6 +170,7 @@ impl BackgroundEffect {
|
||||
// Use noise/saturation from options, falling back to blur defaults if blurred, and
|
||||
// to no effect if not blurred.
|
||||
let blur = self.options.blur && !self.blur_config.off;
|
||||
let blur_options = blur.then_some(BlurOptions::from(self.blur_config));
|
||||
let noise = if blur { self.blur_config.noise } else { 0. };
|
||||
let noise = self.options.noise.unwrap_or(noise) as f32;
|
||||
let saturation = if blur {
|
||||
@@ -187,6 +197,10 @@ impl BackgroundEffect {
|
||||
);
|
||||
} else {
|
||||
// Render non-xray effect.
|
||||
let elem = self
|
||||
.nonxray
|
||||
.render(ns, params, blur_options, noise, saturation);
|
||||
push(elem.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,6 +283,7 @@ pub fn damage_surface(states: &SurfaceData) {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_for_tile(
|
||||
ctx: RenderCtx<GlesRenderer>,
|
||||
ns: Option<usize>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
scale: f64,
|
||||
clip_to_geometry: bool,
|
||||
@@ -312,6 +327,6 @@ pub fn render_for_tile(
|
||||
};
|
||||
|
||||
let xray_pos = xray_pos.offset(params.geometry.loc - geometry.loc);
|
||||
background_effect.render(ctx, params, xray_pos, push);
|
||||
background_effect.render(ctx, ns, params, xray_pos, push);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use glam::{Mat3, Vec2};
|
||||
use niri_config::CornerRadius;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::{Element, Id, RenderElement};
|
||||
use smithay::backend::renderer::gles::{
|
||||
ffi, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform,
|
||||
};
|
||||
use smithay::backend::renderer::utils::CommitCounter;
|
||||
use smithay::backend::renderer::{Frame as _, FrameContext, Offscreen, Texture as _};
|
||||
use smithay::gpu_span_location;
|
||||
use smithay::utils::user_data::UserDataMap;
|
||||
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
|
||||
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
use crate::render_helpers::background_effect::RenderParams;
|
||||
use crate::render_helpers::blur::{Blur, BlurOptions};
|
||||
use crate::render_helpers::renderer::AsGlesFrame as _;
|
||||
use crate::render_helpers::shaders::{mat3_uniform, Shaders};
|
||||
use crate::utils::region::TransformedRegion;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FramebufferEffect {
|
||||
id: Id,
|
||||
commit: CommitCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FramebufferEffectElement {
|
||||
id: Id,
|
||||
commit: CommitCounter,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
clip_geo: Rectangle<f64, Logical>,
|
||||
corner_radius: CornerRadius,
|
||||
subregion: Option<TransformedRegion>,
|
||||
scale: f32,
|
||||
blur_options: Option<BlurOptions>,
|
||||
noise: f32,
|
||||
saturation: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
framebuffer: Option<GlesTexture>,
|
||||
blur: Option<Blur>,
|
||||
intermediate: Option<GlesTexture>,
|
||||
/// Reusable storage for subregion-filtered damage rects.
|
||||
subregion_damage: Vec<Rectangle<i32, Physical>>,
|
||||
}
|
||||
|
||||
impl FramebufferEffect {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: Id::new(),
|
||||
commit: CommitCounter::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn damage(&mut self) {
|
||||
self.commit.increment();
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
ns: Option<usize>,
|
||||
params: RenderParams,
|
||||
blur_options: Option<BlurOptions>,
|
||||
noise: f32,
|
||||
saturation: f32,
|
||||
) -> FramebufferEffectElement {
|
||||
let (clip_geo, corner_radius) = params
|
||||
.clip
|
||||
.unwrap_or((params.geometry, CornerRadius::default()));
|
||||
|
||||
let mut id = self.id.clone();
|
||||
if let Some(ns) = ns {
|
||||
id = id.namespaced(ns);
|
||||
}
|
||||
|
||||
FramebufferEffectElement {
|
||||
id,
|
||||
commit: self.commit,
|
||||
geometry: params.geometry,
|
||||
clip_geo,
|
||||
corner_radius,
|
||||
subregion: params.subregion,
|
||||
scale: params.scale as f32,
|
||||
blur_options,
|
||||
noise,
|
||||
saturation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FramebufferEffectElement {
|
||||
fn compute_uniforms(
|
||||
&self,
|
||||
crop: Rectangle<f64, Logical>,
|
||||
transform: Transform,
|
||||
) -> [Uniform<'static>; 7] {
|
||||
let offset = crop.loc - (self.clip_geo.loc - self.geometry.loc);
|
||||
let offset = Vec2::new(offset.x as f32, offset.y as f32);
|
||||
let crop_size = Vec2::new(crop.size.w as f32, crop.size.h as f32);
|
||||
let clip_size = Vec2::new(self.clip_geo.size.w as f32, self.clip_geo.size.h as f32);
|
||||
|
||||
// Our v_coords are [0, 1] inside crop. We want them to be [0, 1] inside clip_geo.
|
||||
let input_to_clip_geo =
|
||||
Mat3::from_scale(crop_size / clip_size) * Mat3::from_translation(offset / crop_size);
|
||||
|
||||
// Revert the effect of the texture transform.
|
||||
let transform_mat = Mat3::from_translation(Vec2::new(0.5, 0.5))
|
||||
* Mat3::from_cols_array(transform.matrix().as_ref())
|
||||
* Mat3::from_translation(Vec2::new(-0.5, -0.5));
|
||||
let input_to_clip_geo = input_to_clip_geo * transform_mat;
|
||||
|
||||
let clip_geo_size = (self.clip_geo.size.w as f32, self.clip_geo.size.h as f32);
|
||||
|
||||
[
|
||||
Uniform::new("niri_scale", self.scale),
|
||||
Uniform::new("geo_size", clip_geo_size),
|
||||
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
|
||||
mat3_uniform("input_to_geo", input_to_clip_geo),
|
||||
Uniform::new("noise", self.noise),
|
||||
Uniform::new("saturation", self.saturation),
|
||||
Uniform::new("bg_color", [0f32, 0., 0., 0.]),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for FramebufferEffectElement {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.commit
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
// We don't use src for drawing but we can use it to figure out how we were cropped.
|
||||
let size = self.geometry.size.to_buffer(1., Transform::Normal);
|
||||
Rectangle::from_size(size)
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.geometry.to_physical_precise_round(scale)
|
||||
}
|
||||
|
||||
fn is_framebuffer_effect(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for FramebufferEffectElement {
|
||||
fn capture_framebuffer(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
cache: &UserDataMap,
|
||||
) -> Result<(), GlesError> {
|
||||
let _span = tracy_client::span!("FramebufferEffectElement::capture_framebuffer");
|
||||
let location = gpu_span_location!("FramebufferEffectElement::capture_framebuffer");
|
||||
frame.with_gpu_span(location, |frame| {
|
||||
let output_rect = Rectangle::from_size(frame.output_size());
|
||||
let transform = frame.transformation();
|
||||
|
||||
let mut guard = frame.renderer();
|
||||
|
||||
let inner = cache
|
||||
.get_or_insert::<RefCell<Inner>, _>(|| RefCell::new(Inner::new(guard.as_mut())));
|
||||
let mut inner = inner.borrow_mut();
|
||||
let inner = &mut *inner;
|
||||
|
||||
inner.intermediate = None;
|
||||
|
||||
// We want clamp-to-edge behavior for out-of-bounds pixels. However, glBlitFramebuffer
|
||||
// seems to skip out-of-bounds pixels, even though my reading of the docs suggests
|
||||
// otherwise (we use GL_LINEAR filter). So, clamp dst to the framebuffer bounds
|
||||
// ourselves.
|
||||
let clamped_dst = match dst.intersection(output_rect) {
|
||||
Some(clamped) => clamped,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let clamp_scale = clamped_dst.size.to_f64() / dst.size.to_f64();
|
||||
|
||||
let dst = transform.transform_rect_in(clamped_dst, &output_rect.size);
|
||||
|
||||
// Compute size from our geometry and scale.
|
||||
//
|
||||
// The "correct" size is always dst.size since that's the pixel region we're actually
|
||||
// blitting. However, using dst.size causes two undesirable things when zooming out for
|
||||
// the overview:
|
||||
// 1. dst.size shrinks every frame, causing a texture realloaction for every fb effect
|
||||
// element every frame.
|
||||
// 2. The underlying blur visually expands. This is technically correct, since the
|
||||
// underlying contents shrink, but it's not what you visually expect: you expect the
|
||||
// blur to also shrink as the windows zoom out, to give the zooming out effect.
|
||||
//
|
||||
// Using size computed from geometry and scale solves both of those problems (even
|
||||
// though there's a bit of a cost in that zoomed-out elements still blur the entire
|
||||
// unzoomed texture size, and even though the blur ends up slightly wrong as there's two
|
||||
// layers of texture resampling, up and back down).
|
||||
//
|
||||
// Here we use src.size rather than geometry directly because src takes into account
|
||||
// cropping.
|
||||
let size = src
|
||||
.size
|
||||
.to_logical(1., Transform::Normal)
|
||||
.upscale(clamp_scale)
|
||||
.to_physical_precise_round(self.scale);
|
||||
let size = transform.transform_size(size);
|
||||
|
||||
let size = size.to_logical(1).to_buffer(1, Transform::Normal);
|
||||
|
||||
// Recreate framebuffer if needed.
|
||||
if inner
|
||||
.framebuffer
|
||||
.as_ref()
|
||||
.is_some_and(|fb| fb.size() != size)
|
||||
{
|
||||
inner.framebuffer = None;
|
||||
}
|
||||
let framebuffer = if let Some(fb) = &inner.framebuffer {
|
||||
fb
|
||||
} else {
|
||||
trace!("creating framebuffer texture sized {} × {}", size.w, size.h);
|
||||
let renderer = guard.as_mut();
|
||||
let texture = renderer.create_buffer(Fourcc::Abgr8888, size)?;
|
||||
inner.framebuffer.insert(texture)
|
||||
};
|
||||
|
||||
// Prepare blur textures.
|
||||
let mut blur = Option::zip(inner.blur.as_mut(), self.blur_options);
|
||||
if let Some((b, options)) = &mut blur {
|
||||
let renderer = guard.as_mut();
|
||||
if let Err(err) = b.prepare_textures(
|
||||
|fourcc, size| renderer.create_buffer(fourcc, size),
|
||||
framebuffer,
|
||||
*options,
|
||||
) {
|
||||
warn!("error preparing blur textures: {err:?}");
|
||||
blur = None;
|
||||
}
|
||||
}
|
||||
|
||||
// We can't use renderer.with_context() as that will reset the GlesFrame binding that we
|
||||
// want to blit from.
|
||||
drop(guard);
|
||||
|
||||
// Blit the framebuffer contents.
|
||||
frame.with_context(|gl| unsafe {
|
||||
while gl.GetError() != ffi::NO_ERROR {}
|
||||
|
||||
let mut current_fbo = 0i32;
|
||||
gl.GetIntegerv(ffi::DRAW_FRAMEBUFFER_BINDING, &mut current_fbo as *mut _);
|
||||
|
||||
// BlitFramebuffer is affected by the scissor test, we don't want that.
|
||||
gl.Disable(ffi::SCISSOR_TEST);
|
||||
|
||||
let mut fbo = 0;
|
||||
gl.GenFramebuffers(1, &mut fbo as *mut _);
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, fbo);
|
||||
|
||||
gl.FramebufferTexture2D(
|
||||
ffi::DRAW_FRAMEBUFFER,
|
||||
ffi::COLOR_ATTACHMENT0,
|
||||
ffi::TEXTURE_2D,
|
||||
framebuffer.tex_id(),
|
||||
0,
|
||||
);
|
||||
|
||||
gl.BlitFramebuffer(
|
||||
dst.loc.x,
|
||||
dst.loc.y,
|
||||
dst.loc.x + dst.size.w,
|
||||
dst.loc.y + dst.size.h,
|
||||
0,
|
||||
0,
|
||||
size.w,
|
||||
size.h,
|
||||
ffi::COLOR_BUFFER_BIT,
|
||||
ffi::LINEAR,
|
||||
);
|
||||
|
||||
// Restore state set by GlesFrame that we just modified.
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, current_fbo as u32);
|
||||
gl.Enable(ffi::SCISSOR_TEST);
|
||||
|
||||
gl.DeleteFramebuffers(1, &mut fbo as *mut _);
|
||||
|
||||
if gl.GetError() != ffi::NO_ERROR {
|
||||
Err(GlesError::BlitError)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})??;
|
||||
|
||||
// If blur is off, use the unblurred texture.
|
||||
if self.blur_options.is_none() {
|
||||
inner.intermediate = Some(framebuffer.clone());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some((blur, options)) = blur {
|
||||
let mut guard = frame.renderer();
|
||||
let renderer = guard.as_mut();
|
||||
match blur.render(renderer, framebuffer, options) {
|
||||
Ok(blurred) => inner.intermediate = Some(blurred),
|
||||
Err(err) => {
|
||||
warn!("error rendering blur: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut GlesFrame<'_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
cache: Option<&UserDataMap>,
|
||||
) -> Result<(), GlesError> {
|
||||
let Some(cache) = cache else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(inner) = cache.get::<RefCell<Inner>>() else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut inner = inner.borrow_mut();
|
||||
let inner = &mut *inner;
|
||||
|
||||
let Some(texture) = &inner.intermediate else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Clamp the same way as in capture_framebuffer().
|
||||
let output_rect = Rectangle::from_size(frame.output_size());
|
||||
let clamped_dst = match dst.intersection(output_rect) {
|
||||
Some(clamped) => clamped,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let clamp_offset = clamped_dst.loc - dst.loc;
|
||||
|
||||
// Filter damage by subregion, reusing the stored Vec to avoid allocation.
|
||||
let filtered = &mut inner.subregion_damage;
|
||||
filtered.clear();
|
||||
|
||||
if let Some(subregion) = &self.subregion {
|
||||
// Convert to subregion coordinates.
|
||||
let mut crop = src.to_logical(1., Transform::Normal, &src.size);
|
||||
crop.loc += self.geometry.loc;
|
||||
subregion.filter_damage(crop, dst, damage, filtered);
|
||||
} else {
|
||||
filtered.extend(damage.iter());
|
||||
};
|
||||
|
||||
// Adjust for clamped dst.
|
||||
if clamped_dst != dst {
|
||||
let r = Rectangle::new(clamp_offset, clamped_dst.size);
|
||||
filtered.retain_mut(|d| {
|
||||
if let Some(mut crop) = d.intersection(r) {
|
||||
crop.loc -= clamp_offset;
|
||||
*d = crop;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if filtered.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let damage = &filtered[..];
|
||||
|
||||
// Adjust src proportionally to the dst clamping.
|
||||
let src_loc = src.loc.to_logical(1., Transform::Normal, &src.size);
|
||||
let dst_to_src = src.size / dst.size.to_f64();
|
||||
let crop = Rectangle::new(
|
||||
src_loc + clamp_offset.to_f64().upscale(dst_to_src).to_logical(1.),
|
||||
clamped_dst.size.to_f64().upscale(dst_to_src).to_logical(1.),
|
||||
);
|
||||
|
||||
let program = Shaders::get_from_frame(frame).postprocess_and_clip.clone();
|
||||
let uniforms = program
|
||||
.is_some()
|
||||
.then(|| self.compute_uniforms(crop, frame.transformation()));
|
||||
let uniforms = uniforms.as_ref().map_or(&[][..], |x| &x[..]);
|
||||
|
||||
frame.render_texture_from_to(
|
||||
texture,
|
||||
Rectangle::from_size(texture.size().to_f64()),
|
||||
clamped_dst,
|
||||
damage,
|
||||
&[],
|
||||
// The intermediate texture has the same transform as the frame.
|
||||
frame.transformation().invert(),
|
||||
1.,
|
||||
program.as_ref(),
|
||||
uniforms,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'render> RenderElement<TtyRenderer<'render>> for FramebufferEffectElement {
|
||||
fn capture_framebuffer(
|
||||
&self,
|
||||
frame: &mut TtyFrame<'_, '_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
cache: &UserDataMap,
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::capture_framebuffer(&self, gles_frame, src, dst, cache)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut TtyFrame<'_, '_, '_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
cache: Option<&UserDataMap>,
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(
|
||||
&self,
|
||||
gles_frame,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
cache,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new(renderer: &mut GlesRenderer) -> Self {
|
||||
Inner {
|
||||
framebuffer: None,
|
||||
blur: Blur::new(renderer),
|
||||
intermediate: None,
|
||||
subregion_damage: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ pub mod clipped_surface;
|
||||
pub mod damage;
|
||||
pub mod debug;
|
||||
pub mod effect_buffer;
|
||||
pub mod framebuffer_effect;
|
||||
pub mod gradient_fade_texture;
|
||||
pub mod memory;
|
||||
pub mod offscreen;
|
||||
|
||||
@@ -697,6 +697,7 @@ impl LayoutElement for Mapped {
|
||||
let should_block_out = ctx.target.should_block_out(self.rules.block_out_from);
|
||||
background_effect::render_for_tile(
|
||||
ctx,
|
||||
None,
|
||||
geometry,
|
||||
scale,
|
||||
clip_to_geometry,
|
||||
|
||||
Reference in New Issue
Block a user