forked from Mirror/niri
perf(blur): limit true blur frame rate
This commit is contained in:
@@ -26,8 +26,9 @@ Tiled windows will always draw "optimized" or "x-ray" blur, which is rendered fr
|
||||
`overlay` layer surfaces will draw "true" blur by default instead, which is rendered using all available screen
|
||||
contents.
|
||||
|
||||
Note that true blur is rather expensive in terms of GPU load however, so an option exists to also have these surfaces
|
||||
draw `x-ray` blur instead.
|
||||
Note that true blur is rather expensive in terms of GPU load however (even though its frame rate is limited as long as
|
||||
the window isn't being resized / the layer surface doesn't update), so an option exists to also have these surfaces draw
|
||||
`x-ray` blur instead.
|
||||
|
||||
To set global defaults for blur:
|
||||
|
||||
|
||||
@@ -302,6 +302,7 @@ impl MappedLayer {
|
||||
Some(
|
||||
self.blur
|
||||
.render(
|
||||
renderer.as_gles_renderer(),
|
||||
fx_buffers,
|
||||
blur_sample_area,
|
||||
self.rules.geometry_corner_radius.unwrap_or_default(),
|
||||
|
||||
@@ -1931,6 +1931,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
.and_then(|fx_buffers| {
|
||||
self.blur
|
||||
.render(
|
||||
renderer.as_gles_renderer(),
|
||||
fx_buffers,
|
||||
blur_sample_area.to_i32_round(),
|
||||
radius,
|
||||
|
||||
@@ -55,9 +55,9 @@ impl CurrentBuffer {
|
||||
#[derive(Debug)]
|
||||
pub struct EffectsFramebuffers {
|
||||
/// Contains the main buffer blurred contents
|
||||
pub optimized_blur: GlesTexture,
|
||||
optimized_blur: GlesTexture,
|
||||
/// Whether the optimizer blur buffer is dirty
|
||||
pub optimized_blur_rerender_at: Option<Instant>,
|
||||
optimized_blur_rerender_at: Option<Instant>,
|
||||
// /// Contains the original pixels before blurring to draw with in case of artifacts.
|
||||
// blur_saved_pixels: GlesTexture,
|
||||
// The blur algorithms (dual-kawase) swaps between these two whenever scaling the image
|
||||
@@ -317,6 +317,7 @@ pub(super) unsafe fn get_main_buffer_blur(
|
||||
supports_instancing: bool,
|
||||
// dst is the region that we want blur on
|
||||
dst: Rectangle<i32, Physical>,
|
||||
texture_cache: &GlesTexture,
|
||||
alpha_tex: Option<&GlesTexture>,
|
||||
) -> Result<GlesTexture, GlesError> {
|
||||
let tex_size = fx_buffers
|
||||
@@ -463,6 +464,78 @@ pub(super) unsafe fn get_main_buffer_blur(
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over cached texture
|
||||
{
|
||||
let mut tex_cache_fbo = 0;
|
||||
|
||||
gl.GenFramebuffers(1, &mut tex_cache_fbo as *mut _);
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, tex_cache_fbo);
|
||||
gl.FramebufferTexture2D(
|
||||
ffi::DRAW_FRAMEBUFFER,
|
||||
ffi::COLOR_ATTACHMENT0,
|
||||
ffi::TEXTURE_2D,
|
||||
texture_cache.tex_id(),
|
||||
0,
|
||||
);
|
||||
gl.Clear(ffi::COLOR_BUFFER_BIT);
|
||||
let status = gl.CheckFramebufferStatus(ffi::DRAW_FRAMEBUFFER);
|
||||
if status != ffi::FRAMEBUFFER_COMPLETE {
|
||||
gl.DeleteFramebuffers(1, &mut tex_cache_fbo as *mut _);
|
||||
return Err(GlesError::FramebufferBindingError);
|
||||
}
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, tex_cache_fbo);
|
||||
|
||||
let mut render_buffer_fbo = 0;
|
||||
|
||||
gl.GenFramebuffers(1, &mut render_buffer_fbo as *mut _);
|
||||
gl.BindFramebuffer(ffi::READ_FRAMEBUFFER, render_buffer_fbo);
|
||||
gl.FramebufferTexture2D(
|
||||
ffi::READ_FRAMEBUFFER,
|
||||
ffi::COLOR_ATTACHMENT0,
|
||||
ffi::TEXTURE_2D,
|
||||
fx_buffers.effects.tex_id(),
|
||||
0,
|
||||
);
|
||||
gl.Clear(ffi::COLOR_BUFFER_BIT);
|
||||
let status = gl.CheckFramebufferStatus(ffi::READ_FRAMEBUFFER);
|
||||
if status != ffi::FRAMEBUFFER_COMPLETE {
|
||||
gl.DeleteFramebuffers(1, &mut render_buffer_fbo as *mut _);
|
||||
return Err(GlesError::FramebufferBindingError);
|
||||
}
|
||||
gl.BindFramebuffer(ffi::READ_FRAMEBUFFER, render_buffer_fbo);
|
||||
|
||||
let dst_x0 = dst_expanded.loc.x;
|
||||
let dst_y0 = dst_expanded.loc.y;
|
||||
let dst_x1 = dst_expanded.loc.x + dst_expanded.size.w;
|
||||
let dst_y1 = dst_expanded.loc.y + dst_expanded.size.h;
|
||||
|
||||
let src_x0 = dst_x0;
|
||||
let src_y0 = dst_y0;
|
||||
let src_x1 = dst_x1;
|
||||
let src_y1 = dst_y1;
|
||||
|
||||
gl.BlitFramebuffer(
|
||||
src_x0,
|
||||
src_y0,
|
||||
src_x1,
|
||||
src_y1,
|
||||
dst_x0,
|
||||
dst_y0,
|
||||
dst_x1,
|
||||
dst_y1,
|
||||
ffi::COLOR_BUFFER_BIT,
|
||||
ffi::LINEAR,
|
||||
);
|
||||
|
||||
if gl.GetError() == ffi::INVALID_OPERATION {
|
||||
error!("TrueBlur needs GLES3.0 for blitting");
|
||||
return Err(GlesError::BlitError);
|
||||
}
|
||||
|
||||
gl.DeleteFramebuffers(1, &mut tex_cache_fbo as *mut _);
|
||||
gl.DeleteFramebuffers(1, &mut render_buffer_fbo as *mut _);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
{
|
||||
gl.DeleteFramebuffers(1, &mut sample_fbo as *mut _);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::time::Instant;
|
||||
|
||||
use glam::{Mat3, Vec2};
|
||||
use niri_config::CornerRadius;
|
||||
@@ -12,11 +14,12 @@ use smithay::backend::renderer::gles::{
|
||||
ffi, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform,
|
||||
};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions};
|
||||
use smithay::backend::renderer::Texture;
|
||||
use smithay::backend::renderer::{Offscreen, Texture};
|
||||
use smithay::reexports::gbm::Format;
|
||||
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
use crate::render_helpers::blur::EffectsFramebufffersUserData;
|
||||
use crate::render_helpers::blur::{get_rerender_at, EffectsFramebufffersUserData};
|
||||
use crate::render_helpers::render_data::RendererData;
|
||||
use crate::render_helpers::renderer::AsGlesFrame;
|
||||
use crate::render_helpers::shaders::{mat3_uniform, Shaders};
|
||||
@@ -26,11 +29,16 @@ use super::{CurrentBuffer, EffectsFramebuffers};
|
||||
#[derive(Debug, Clone)]
|
||||
enum BlurVariant {
|
||||
Optimized {
|
||||
/// Reference to the globally cached optimized blur texture.
|
||||
texture: GlesTexture,
|
||||
},
|
||||
True {
|
||||
/// Individual cache of true blur texture.
|
||||
texture: GlesTexture,
|
||||
fx_buffers: EffectsFramebufffersUserData,
|
||||
config: niri_config::Blur,
|
||||
/// Timer to limit redraw rate of true blur. Currently set at 150ms fixed (~6.6 fps).
|
||||
rerender_at: Rc<RefCell<Option<Instant>>>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,6 +117,7 @@ impl Blur {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render(
|
||||
&self,
|
||||
renderer: &mut GlesRenderer,
|
||||
fx_buffers: EffectsFramebufffersUserData,
|
||||
sample_area: Rectangle<i32, Logical>,
|
||||
corner_radius: CornerRadius,
|
||||
@@ -129,6 +138,15 @@ impl Blur {
|
||||
true_blur = false;
|
||||
}
|
||||
|
||||
let mut tex_buffer = || {
|
||||
renderer
|
||||
.create_buffer(Format::Argb8888, fx_buffers.borrow().effects.size())
|
||||
.inspect_err(|e| {
|
||||
warn!("failed to allocate buffer for cached true blur texture: {e:?}")
|
||||
})
|
||||
.ok()
|
||||
};
|
||||
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
|
||||
let Some(inner) = inner.as_mut() else {
|
||||
@@ -144,6 +162,8 @@ impl Blur {
|
||||
BlurVariant::True {
|
||||
fx_buffers: fx_buffers.clone(),
|
||||
config: self.config,
|
||||
texture: tex_buffer()?,
|
||||
rerender_at: Default::default(),
|
||||
}
|
||||
} else {
|
||||
BlurVariant::Optimized {
|
||||
@@ -163,6 +183,8 @@ impl Blur {
|
||||
BlurVariant::True {
|
||||
fx_buffers: fx_buffers.clone(),
|
||||
config: self.config,
|
||||
texture: tex_buffer()?,
|
||||
rerender_at: Default::default(),
|
||||
}
|
||||
} else {
|
||||
BlurVariant::Optimized {
|
||||
@@ -175,24 +197,34 @@ impl Blur {
|
||||
|
||||
let fx_buffers = fx_buffers.borrow();
|
||||
|
||||
let variant_needs_rerender = match &inner.variant {
|
||||
BlurVariant::Optimized { texture } => {
|
||||
texture.size().w != fx_buffers.output_size().w
|
||||
|| texture.size().h != fx_buffers.output_size().h
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// if nothing about our geometry changed, we don't need to re-render blur
|
||||
if inner.sample_area == sample_area
|
||||
&& inner.geometry == geometry
|
||||
&& inner.scale == scale
|
||||
&& inner.corner_radius == corner_radius
|
||||
&& inner.render_loc == render_loc
|
||||
{
|
||||
if !matches!(&inner.variant, BlurVariant::Optimized { texture }
|
||||
if texture.size().w == fx_buffers.output_size().w
|
||||
&& texture.size().h == fx_buffers.output_size().h)
|
||||
{
|
||||
// If we are true blur, or if our output size changed, we need to re-render.
|
||||
// PERF: is there a better solution for true blur?
|
||||
if variant_needs_rerender {
|
||||
// If we are true blur, or if our output size changed, we need to always be
|
||||
// damaged. True blur has a separate internal timer to regulate frame rate.
|
||||
inner.damage_all();
|
||||
}
|
||||
|
||||
return Some(inner.clone());
|
||||
}
|
||||
|
||||
if let BlurVariant::True { rerender_at, .. } = &inner.variant {
|
||||
rerender_at.set(None);
|
||||
};
|
||||
|
||||
inner.render_loc = render_loc;
|
||||
inner.sample_area = sample_area;
|
||||
inner.alpha_tex = self.alpha_tex.borrow().clone();
|
||||
@@ -421,7 +453,12 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
|
||||
Some(&program),
|
||||
&self.uniforms,
|
||||
),
|
||||
BlurVariant::True { fx_buffers, config } => {
|
||||
BlurVariant::True {
|
||||
fx_buffers,
|
||||
config,
|
||||
texture,
|
||||
rerender_at,
|
||||
} => {
|
||||
let mut fx_buffers = fx_buffers.borrow_mut();
|
||||
|
||||
fx_buffers.current_buffer = CurrentBuffer::Normal;
|
||||
@@ -436,24 +473,33 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
|
||||
|
||||
// Update the blur buffers.
|
||||
// We use gl ffi directly to circumvent some stuff done by smithay
|
||||
let blurred_texture = gles_frame.with_context(|gl| unsafe {
|
||||
super::get_main_buffer_blur(
|
||||
gl,
|
||||
&mut fx_buffers,
|
||||
&shaders,
|
||||
*config,
|
||||
projection_matrix,
|
||||
self.scale as i32,
|
||||
&vbos,
|
||||
debug,
|
||||
supports_instancing,
|
||||
downscaled_dst,
|
||||
self.alpha_tex.as_ref(),
|
||||
)
|
||||
})??;
|
||||
if rerender_at
|
||||
.borrow()
|
||||
.map(|r| r < Instant::now())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
gles_frame.with_context(|gl| unsafe {
|
||||
super::get_main_buffer_blur(
|
||||
gl,
|
||||
&mut fx_buffers,
|
||||
&shaders,
|
||||
*config,
|
||||
projection_matrix,
|
||||
self.scale as i32,
|
||||
&vbos,
|
||||
debug,
|
||||
supports_instancing,
|
||||
downscaled_dst,
|
||||
texture,
|
||||
self.alpha_tex.as_ref(),
|
||||
)
|
||||
})??;
|
||||
|
||||
rerender_at.set(get_rerender_at());
|
||||
};
|
||||
|
||||
gles_frame.render_texture_from_to(
|
||||
&blurred_texture,
|
||||
texture,
|
||||
src,
|
||||
downscaled_dst,
|
||||
damage,
|
||||
|
||||
Reference in New Issue
Block a user