forked from Mirror/niri
feat(blur): implement for windows & layer surfaces
Co-authored-by: visualglitch91 <visualglitch91@proton.me> Co-authored-by: nferhat <nferhat20@gmail.com>
This commit is contained in:
@@ -338,6 +338,36 @@ impl MergeWith<BorderRule> for FocusRing {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Blur {
|
||||
pub on: bool,
|
||||
pub passes: u32,
|
||||
pub radius: FloatOrInt<0, 1024>,
|
||||
pub noise: FloatOrInt<0, 1024>,
|
||||
}
|
||||
|
||||
impl Default for Blur {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
on: false,
|
||||
passes: 0,
|
||||
radius: FloatOrInt(0.0),
|
||||
noise: FloatOrInt(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<BlurRule> for Blur {
|
||||
fn merge_with(&mut self, part: &BlurRule) {
|
||||
self.on |= part.on;
|
||||
if part.off {
|
||||
self.on = false;
|
||||
}
|
||||
|
||||
merge_clone!((self, part), passes, radius, noise);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Shadow {
|
||||
pub on: bool,
|
||||
@@ -637,6 +667,20 @@ pub struct BorderRule {
|
||||
pub urgent_gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct BlurRule {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub passes: Option<u32>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub radius: Option<FloatOrInt<0, 1024>>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub noise: Option<FloatOrInt<0, 1024>>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct ShadowRule {
|
||||
#[knuffel(child)]
|
||||
@@ -688,6 +732,14 @@ impl MergeWith<Self> for BorderRule {
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<Self> for BlurRule {
|
||||
fn merge_with(&mut self, part: &Self) {
|
||||
merge_on_off!((self, part));
|
||||
|
||||
merge_clone_opt!((self, part), passes, radius, noise);
|
||||
}
|
||||
}
|
||||
|
||||
impl MergeWith<Self> for ShadowRule {
|
||||
fn merge_with(&mut self, part: &Self) {
|
||||
merge_on_off!((self, part));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::appearance::{BlockOutFrom, CornerRadius, ShadowRule};
|
||||
use crate::utils::RegexEq;
|
||||
use crate::BlurRule;
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct LayerRule {
|
||||
@@ -14,6 +15,8 @@ pub struct LayerRule {
|
||||
pub block_out_from: Option<BlockOutFrom>,
|
||||
#[knuffel(child, default)]
|
||||
pub shadow: ShadowRule,
|
||||
#[knuffel(child, default)]
|
||||
pub blur: BlurRule,
|
||||
#[knuffel(child)]
|
||||
pub geometry_corner_radius: Option<CornerRadius>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
|
||||
@@ -5,12 +5,15 @@ use crate::appearance::{
|
||||
Border, FocusRing, InsertHint, Shadow, TabIndicator, DEFAULT_BACKGROUND_COLOR,
|
||||
};
|
||||
use crate::utils::{expect_only_children, Flag, MergeWith};
|
||||
use crate::{BorderRule, Color, FloatOrInt, InsertHintPart, ShadowRule, TabIndicatorPart};
|
||||
use crate::{
|
||||
Blur, BlurRule, BorderRule, Color, FloatOrInt, InsertHintPart, ShadowRule, TabIndicatorPart,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Layout {
|
||||
pub focus_ring: FocusRing,
|
||||
pub border: Border,
|
||||
pub blur: Blur,
|
||||
pub shadow: Shadow,
|
||||
pub tab_indicator: TabIndicator,
|
||||
pub insert_hint: InsertHint,
|
||||
@@ -30,6 +33,7 @@ impl Default for Layout {
|
||||
Self {
|
||||
focus_ring: FocusRing::default(),
|
||||
border: Border::default(),
|
||||
blur: Blur::default(),
|
||||
shadow: Shadow::default(),
|
||||
tab_indicator: TabIndicator::default(),
|
||||
insert_hint: InsertHint::default(),
|
||||
@@ -60,6 +64,7 @@ impl MergeWith<LayoutPart> for Layout {
|
||||
(self, part),
|
||||
focus_ring,
|
||||
border,
|
||||
blur,
|
||||
shadow,
|
||||
tab_indicator,
|
||||
insert_hint,
|
||||
@@ -98,6 +103,8 @@ pub struct LayoutPart {
|
||||
#[knuffel(child)]
|
||||
pub border: Option<BorderRule>,
|
||||
#[knuffel(child)]
|
||||
pub blur: Option<BlurRule>,
|
||||
#[knuffel(child)]
|
||||
pub shadow: Option<ShadowRule>,
|
||||
#[knuffel(child)]
|
||||
pub tab_indicator: Option<TabIndicatorPart>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::appearance::{BlockOutFrom, BorderRule, CornerRadius, ShadowRule, TabIndicatorRule};
|
||||
use crate::layout::DefaultPresetSize;
|
||||
use crate::utils::RegexEq;
|
||||
use crate::FloatOrInt;
|
||||
use crate::{BlurRule, FloatOrInt};
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||
pub struct WindowRule {
|
||||
@@ -45,6 +45,8 @@ pub struct WindowRule {
|
||||
#[knuffel(child, default)]
|
||||
pub border: BorderRule,
|
||||
#[knuffel(child, default)]
|
||||
pub blur: BlurRule,
|
||||
#[knuffel(child, default)]
|
||||
pub shadow: ShadowRule,
|
||||
#[knuffel(child, default)]
|
||||
pub tab_indicator: TabIndicatorRule,
|
||||
|
||||
@@ -120,7 +120,7 @@ impl TestCase for Tile {
|
||||
Rectangle::new(Point::from((-location.x, -location.y)), size.to_logical(1.)),
|
||||
);
|
||||
self.tile
|
||||
.render(renderer, location, true, RenderTarget::Output)
|
||||
.render(renderer, location, true, RenderTarget::Output, None)
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -222,6 +222,10 @@ impl LayoutElement for TestWindow {
|
||||
|
||||
fn set_floating(&mut self, _floating: bool) {}
|
||||
|
||||
fn is_floating(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||
|
||||
fn is_ignoring_opacity_window_rule(&self) -> bool {
|
||||
|
||||
@@ -65,7 +65,9 @@ use super::{IpcOutputMap, RenderResult};
|
||||
use crate::backend::OutputId;
|
||||
use crate::frame_clock::FrameClock;
|
||||
use crate::niri::{Niri, RedrawState, State};
|
||||
use crate::render_helpers::blur::EffectsFramebuffers;
|
||||
use crate::render_helpers::debug::draw_damage;
|
||||
use crate::render_helpers::render_data::RendererData;
|
||||
use crate::render_helpers::renderer::AsGlesRenderer;
|
||||
use crate::render_helpers::{resources, shaders, RenderTarget};
|
||||
use crate::utils::{get_monotonic_time, is_laptop_panel, logical_output, PanelOrientation};
|
||||
@@ -800,6 +802,7 @@ impl Tty {
|
||||
let gles_renderer = renderer.as_gles_renderer();
|
||||
resources::init(gles_renderer);
|
||||
shaders::init(gles_renderer);
|
||||
RendererData::init(gles_renderer);
|
||||
|
||||
let config = self.config.borrow();
|
||||
if let Some(src) = config.animations.window_resize.custom_shader.as_deref() {
|
||||
@@ -1321,7 +1324,8 @@ impl Tty {
|
||||
}
|
||||
|
||||
let render_node = device.render_node.unwrap_or(self.primary_render_node);
|
||||
let renderer = self.gpu_manager.single_renderer(&render_node)?;
|
||||
let mut renderer = self.gpu_manager.single_renderer(&render_node)?;
|
||||
EffectsFramebuffers::init_for_output(output.clone(), &mut renderer);
|
||||
let egl_context = renderer.as_ref().egl_context();
|
||||
let render_formats = egl_context.dmabuf_render_formats();
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ use smithay::wayland::presentation::Refresh;
|
||||
|
||||
use super::{IpcOutputMap, OutputId, RenderResult};
|
||||
use crate::niri::{Niri, RedrawState, State};
|
||||
use crate::render_helpers::blur::EffectsFramebuffers;
|
||||
use crate::render_helpers::debug::draw_damage;
|
||||
use crate::render_helpers::render_data::RendererData;
|
||||
use crate::render_helpers::{resources, shaders, RenderTarget};
|
||||
use crate::utils::{get_monotonic_time, logical_output};
|
||||
|
||||
@@ -122,6 +124,15 @@ impl Winit {
|
||||
}
|
||||
|
||||
state.niri.output_resized(&winit.output);
|
||||
|
||||
if let Err(e) = EffectsFramebuffers::update_for_output(
|
||||
winit.output.clone(),
|
||||
winit.backend.renderer(),
|
||||
) {
|
||||
warn!("failed to update fx buffers on output resize: {e:?}");
|
||||
} else {
|
||||
EffectsFramebuffers::set_dirty(&winit.output);
|
||||
}
|
||||
}
|
||||
WinitEvent::Input(event) => state.process_input_event(event),
|
||||
WinitEvent::Focus(_) => (),
|
||||
@@ -147,6 +158,8 @@ impl Winit {
|
||||
|
||||
resources::init(renderer);
|
||||
shaders::init(renderer);
|
||||
RendererData::init(renderer);
|
||||
EffectsFramebuffers::init_for_output(self.output.clone(), renderer);
|
||||
|
||||
let config = self.config.borrow();
|
||||
if let Some(src) = config.animations.window_resize.custom_shader.as_deref() {
|
||||
|
||||
@@ -12,6 +12,7 @@ use smithay::wayland::shell::xdg::PopupSurface;
|
||||
|
||||
use crate::layer::{MappedLayer, ResolvedLayerRules};
|
||||
use crate::niri::State;
|
||||
use crate::render_helpers::blur::EffectsFramebuffers;
|
||||
use crate::utils::{is_mapped, output_size, send_scale_transform};
|
||||
|
||||
impl WlrLayerShellHandler for State {
|
||||
@@ -23,7 +24,7 @@ impl WlrLayerShellHandler for State {
|
||||
&mut self,
|
||||
surface: WlrLayerSurface,
|
||||
wl_output: Option<WlOutput>,
|
||||
_layer: Layer,
|
||||
wlr_layer: Layer,
|
||||
namespace: String,
|
||||
) {
|
||||
let output = if let Some(wl_output) = &wl_output {
|
||||
@@ -37,6 +38,10 @@ impl WlrLayerShellHandler for State {
|
||||
return;
|
||||
};
|
||||
|
||||
if matches!(wlr_layer, Layer::Background | Layer::Bottom) {
|
||||
EffectsFramebuffers::set_dirty(&output);
|
||||
}
|
||||
|
||||
let wl_surface = surface.wl_surface().clone();
|
||||
let is_new = self.niri.unmapped_layer_surfaces.insert(wl_surface);
|
||||
assert!(is_new);
|
||||
@@ -59,6 +64,10 @@ impl WlrLayerShellHandler for State {
|
||||
.cloned();
|
||||
layer.map(|layer| (o.clone(), map, layer))
|
||||
}) {
|
||||
if matches!(layer.layer(), Layer::Background | Layer::Bottom) {
|
||||
EffectsFramebuffers::set_dirty(&output);
|
||||
}
|
||||
|
||||
map.unmap_layer(&layer);
|
||||
self.niri.mapped_layer_surfaces.remove(&layer);
|
||||
Some(output)
|
||||
@@ -113,6 +122,10 @@ impl State {
|
||||
.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap();
|
||||
|
||||
if matches!(layer.layer(), Layer::Bottom | Layer::Background) {
|
||||
EffectsFramebuffers::set_dirty(&output);
|
||||
}
|
||||
|
||||
if is_mapped(surface) {
|
||||
let was_unmapped = self.niri.unmapped_layer_surfaces.remove(surface);
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
use niri_config::utils::MergeWith as _;
|
||||
use niri_config::{Config, LayerRule};
|
||||
use niri_config::{Blur, Config, LayerRule};
|
||||
use smithay::backend::renderer::element::surface::{
|
||||
render_elements_from_surface_tree, 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::shell::wlr_layer::{ExclusiveZone, Layer};
|
||||
|
||||
use super::ResolvedLayerRules;
|
||||
use crate::animation::Clock;
|
||||
use crate::layout::shadow::Shadow;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::blur::element::BlurRenderElement;
|
||||
use crate::render_helpers::blur::EffectsFramebufffersUserData;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
@@ -32,6 +34,13 @@ pub struct MappedLayer {
|
||||
/// The shadow around the surface.
|
||||
shadow: Shadow,
|
||||
|
||||
/// Configuration for this layer's blur.
|
||||
blur_config: Blur,
|
||||
|
||||
/// Size (used for blur).
|
||||
// TODO: move to standalone blur struct
|
||||
size: Size<f64, Logical>,
|
||||
|
||||
/// The view size for the layer surface's output.
|
||||
view_size: Size<f64, Logical>,
|
||||
|
||||
@@ -47,6 +56,7 @@ niri_render_elements! {
|
||||
Wayland = WaylandSurfaceRenderElement<R>,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
Shadow = ShadowRenderElement,
|
||||
Blur = BlurRenderElement,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +74,8 @@ impl MappedLayer {
|
||||
shadow_config.on = false;
|
||||
shadow_config.merge_with(&rules.shadow);
|
||||
|
||||
let blur_config = config.layout.blur.merged_with(&rules.blur);
|
||||
|
||||
Self {
|
||||
surface,
|
||||
rules,
|
||||
@@ -72,6 +84,8 @@ impl MappedLayer {
|
||||
scale,
|
||||
shadow: Shadow::new(shadow_config),
|
||||
clock,
|
||||
blur_config,
|
||||
size: Size::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +95,8 @@ impl MappedLayer {
|
||||
shadow_config.on = false;
|
||||
shadow_config.merge_with(&self.rules.shadow);
|
||||
self.shadow.update_config(shadow_config);
|
||||
|
||||
self.blur_config = config.layout.blur.merged_with(&self.rules.blur);
|
||||
}
|
||||
|
||||
pub fn update_shaders(&mut self) {
|
||||
@@ -98,6 +114,8 @@ impl MappedLayer {
|
||||
.to_physical_precise_round(self.scale)
|
||||
.to_logical(self.scale);
|
||||
|
||||
self.size = size;
|
||||
|
||||
self.block_out_buffer.resize(size);
|
||||
|
||||
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
|
||||
@@ -161,6 +179,7 @@ impl MappedLayer {
|
||||
renderer: &mut R,
|
||||
location: Point<f64, Logical>,
|
||||
target: RenderTarget,
|
||||
fx_buffers: Option<EffectsFramebufffersUserData>,
|
||||
) -> SplitElements<LayerSurfaceRenderElement<R>> {
|
||||
let mut rv = SplitElements::default();
|
||||
|
||||
@@ -209,10 +228,35 @@ impl MappedLayer {
|
||||
);
|
||||
}
|
||||
|
||||
let blur_elem = (self.blur_config.on
|
||||
&& matches!(self.surface.layer(), Layer::Top | Layer::Overlay))
|
||||
.then(|| {
|
||||
let fx_buffers = fx_buffers?;
|
||||
|
||||
Some(
|
||||
BlurRenderElement::new_true(
|
||||
fx_buffers,
|
||||
Rectangle::new(location, self.size).to_i32_round(),
|
||||
location.to_physical_precise_round(self.scale),
|
||||
self.rules
|
||||
.geometry_corner_radius
|
||||
.unwrap_or_default()
|
||||
.top_left,
|
||||
self.scale,
|
||||
self.blur_config,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
.into_iter();
|
||||
|
||||
let location = location.to_physical_precise_round(scale).to_logical(scale);
|
||||
rv.normal
|
||||
.extend(self.shadow.render(renderer, location).map(Into::into));
|
||||
|
||||
rv.normal.extend(blur_elem);
|
||||
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::{BlockOutFrom, BlurRule, CornerRadius, ShadowRule};
|
||||
use smithay::desktop::LayerSurface;
|
||||
|
||||
pub mod mapped;
|
||||
@@ -18,6 +18,9 @@ pub struct ResolvedLayerRules {
|
||||
/// Shadow overrides.
|
||||
pub shadow: ShadowRule,
|
||||
|
||||
/// Blur overrides
|
||||
pub blur: BlurRule,
|
||||
|
||||
/// Corner radius to assume this layer surface has.
|
||||
pub geometry_corner_radius: Option<CornerRadius>,
|
||||
|
||||
@@ -33,6 +36,13 @@ impl ResolvedLayerRules {
|
||||
Self {
|
||||
opacity: None,
|
||||
block_out_from: None,
|
||||
blur: BlurRule {
|
||||
off: false,
|
||||
on: false,
|
||||
passes: None,
|
||||
radius: None,
|
||||
noise: None,
|
||||
},
|
||||
shadow: ShadowRule {
|
||||
off: false,
|
||||
on: false,
|
||||
@@ -90,6 +100,7 @@ impl ResolvedLayerRules {
|
||||
}
|
||||
|
||||
resolved.shadow.merge_with(&rule.shadow);
|
||||
resolved.blur.merge_with(&rule.blur);
|
||||
}
|
||||
|
||||
resolved
|
||||
|
||||
@@ -17,6 +17,7 @@ use super::{
|
||||
};
|
||||
use crate::animation::{Animation, Clock};
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::blur::EffectsFramebufffersUserData;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::RenderTarget;
|
||||
use crate::utils::transaction::TransactionBlocker;
|
||||
@@ -1140,6 +1141,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
||||
view_rect: Rectangle<f64, Logical>,
|
||||
target: RenderTarget,
|
||||
focus_ring: bool,
|
||||
fx_buffers: Option<EffectsFramebufffersUserData>,
|
||||
) -> Vec<FloatingSpaceRenderElement<R>> {
|
||||
let mut rv = Vec::new();
|
||||
|
||||
@@ -1159,7 +1161,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
||||
let focus_ring = focus_ring && Some(tile.focused_window().id()) == active.as_ref();
|
||||
|
||||
rv.extend(
|
||||
tile.render(renderer, tile_pos, focus_ring, target)
|
||||
tile.render(renderer, tile_pos, focus_ring, target, fx_buffers.clone())
|
||||
.map(Into::into),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ 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::blur::EffectsFramebuffers;
|
||||
use crate::render_helpers::offscreen::OffscreenData;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
@@ -214,6 +215,7 @@ pub trait LayoutElement {
|
||||
fn set_activated(&mut self, active: bool);
|
||||
fn set_active_in_column(&mut self, active: bool);
|
||||
fn set_floating(&mut self, floating: bool);
|
||||
fn is_floating(&self) -> bool;
|
||||
fn set_bounds(&self, bounds: Size<i32, Logical>);
|
||||
fn is_ignoring_opacity_window_rule(&self) -> bool;
|
||||
|
||||
@@ -4845,9 +4847,10 @@ 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);
|
||||
let fx_buffers = EffectsFramebuffers::get_user_data(output);
|
||||
let iter = move_
|
||||
.tile
|
||||
.render(renderer, location, true, target)
|
||||
.render(renderer, location, true, target, fx_buffers)
|
||||
.map(move |elem| {
|
||||
RescaleRenderElement::from_element(
|
||||
elem,
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::animation::{Animation, Clock};
|
||||
use crate::input::swipe_tracker::SwipeTracker;
|
||||
use crate::layout::SizingMode;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::blur::EffectsFramebufffersUserData;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::RenderTarget;
|
||||
use crate::utils::transaction::{Transaction, TransactionBlocker};
|
||||
@@ -3240,6 +3241,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
renderer: &mut R,
|
||||
target: RenderTarget,
|
||||
focus_ring: bool,
|
||||
fx_buffers: Option<EffectsFramebufffersUserData>,
|
||||
) -> Vec<ScrollingSpaceRenderElement<R>> {
|
||||
let mut rv = vec![];
|
||||
|
||||
@@ -3277,7 +3279,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
first = false;
|
||||
|
||||
rv.extend(
|
||||
tile.render(renderer, tile_pos, focus_ring, target)
|
||||
tile.render(renderer, tile_pos, focus_ring, target, fx_buffers.clone())
|
||||
.map(Into::into),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -240,6 +240,10 @@ impl LayoutElement for TestWindow {
|
||||
|
||||
fn set_floating(&mut self, _floating: bool) {}
|
||||
|
||||
fn is_floating(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn sizing_mode(&self) -> SizingMode {
|
||||
self.0.sizing_mode.get()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use niri_config::utils::MergeWith as _;
|
||||
use niri_config::{Color, CornerRadius, GradientInterpolation};
|
||||
use niri_config::{Blur, Color, CornerRadius, GradientInterpolation};
|
||||
use niri_ipc::WindowLayout;
|
||||
use portable_atomic::AtomicU8;
|
||||
use smithay::backend::renderer::element::{Element, Kind};
|
||||
@@ -21,6 +21,8 @@ use crate::animation::{Animation, Clock};
|
||||
use crate::layout::tab_indicator::{TabIndicator, TabIndicatorRenderElement, TabInfo};
|
||||
use crate::layout::SizingMode;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::blur::element::BlurRenderElement;
|
||||
use crate::render_helpers::blur::EffectsFramebufffersUserData;
|
||||
use crate::render_helpers::border::BorderRenderElement;
|
||||
use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage};
|
||||
use crate::render_helpers::damage::ExtraDamage;
|
||||
@@ -403,6 +405,10 @@ pub struct Tile<W: LayoutElement> {
|
||||
/// window still having to adjust, which causes a jerking visual without this compensation.
|
||||
window_size_override: WindowSizeOverride,
|
||||
|
||||
/// This tile's blur settings.
|
||||
// TODO: create a proper struct for this, like e.g. Shadow
|
||||
blur_config: Blur,
|
||||
|
||||
/// Clock for driving animations.
|
||||
pub(super) clock: Clock,
|
||||
|
||||
@@ -419,6 +425,7 @@ niri_render_elements! {
|
||||
Resize = ResizeRenderElement,
|
||||
Border = BorderRenderElement,
|
||||
Shadow = ShadowRenderElement,
|
||||
Blur = BlurRenderElement,
|
||||
ClippedSurface = ClippedSurfaceRenderElement<R>,
|
||||
Offscreen = OffscreenRenderElement,
|
||||
ExtraDamage = ExtraDamage,
|
||||
@@ -478,12 +485,14 @@ impl<W: LayoutElement> Tile<W> {
|
||||
let shadow_config = options.layout.shadow.merged_with(&rules.shadow);
|
||||
let sizing_mode = window.sizing_mode();
|
||||
let tab_indicator_config = options.layout.tab_indicator;
|
||||
let blur_config = options.layout.blur.merged_with(&rules.blur);
|
||||
|
||||
Self {
|
||||
window: WindowInner::Single(Some(window)),
|
||||
border: FocusRing::new(border_config.into()),
|
||||
focus_ring: FocusRing::new(focus_ring_config),
|
||||
shadow: Shadow::new(shadow_config),
|
||||
blur_config,
|
||||
sizing_mode,
|
||||
fullscreen_backdrop: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]),
|
||||
restore_to_floating: false,
|
||||
@@ -547,6 +556,8 @@ impl<W: LayoutElement> Tile<W> {
|
||||
|
||||
self.tab_indicator
|
||||
.update_config(self.options.layout.tab_indicator);
|
||||
|
||||
self.blur_config = self.options.layout.blur.merged_with(&rules.blur);
|
||||
}
|
||||
|
||||
pub fn update_shaders(&mut self) {
|
||||
@@ -1524,6 +1535,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
location: Point<f64, Logical>,
|
||||
focus_ring: bool,
|
||||
target: RenderTarget,
|
||||
fx_buffers: Option<EffectsFramebufffersUserData>,
|
||||
) -> impl Iterator<Item = TileRenderElement<R>> + 'a {
|
||||
let _span = tracy_client::span!("Tile::render_inner");
|
||||
|
||||
@@ -1841,9 +1853,47 @@ impl<W: LayoutElement> Tile<W> {
|
||||
.then(|| self.focus_ring.render(renderer, location).map(Into::into));
|
||||
let rv = rv.chain(elem.into_iter().flatten());
|
||||
|
||||
let blur_elem = self
|
||||
.blur_config
|
||||
.on
|
||||
.then(|| {
|
||||
let fx_buffers = fx_buffers?;
|
||||
|
||||
if self.focused_window().is_floating() {
|
||||
Some(
|
||||
BlurRenderElement::new_true(
|
||||
fx_buffers,
|
||||
area.to_i32_round(),
|
||||
window_render_loc.to_physical(self.scale).to_i32_round(),
|
||||
radius.top_left,
|
||||
self.scale,
|
||||
self.blur_config,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Some(
|
||||
BlurRenderElement::new_optimized(
|
||||
renderer,
|
||||
&fx_buffers.borrow_mut(),
|
||||
area.to_i32_round(),
|
||||
window_render_loc.to_physical(self.scale).to_i32_round(),
|
||||
radius.top_left,
|
||||
self.scale,
|
||||
self.blur_config,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.into_iter();
|
||||
|
||||
let elem = (expanded_progress < 1.)
|
||||
.then(|| self.shadow.render(renderer, location).map(Into::into));
|
||||
rv.chain(elem.into_iter().flatten())
|
||||
let rv = rv.chain(elem.into_iter().flatten());
|
||||
|
||||
rv.chain(blur_elem)
|
||||
}
|
||||
|
||||
pub fn render<'a, R: NiriRenderer + 'a>(
|
||||
@@ -1852,6 +1902,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
location: Point<f64, Logical>,
|
||||
focus_ring: bool,
|
||||
target: RenderTarget,
|
||||
fx_buffers: Option<EffectsFramebufffersUserData>,
|
||||
) -> impl Iterator<Item = TileRenderElement<R>> + 'a {
|
||||
let _span = tracy_client::span!("Tile::render");
|
||||
|
||||
@@ -1870,7 +1921,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
|
||||
if let Some(open) = &self.open_animation {
|
||||
let renderer = renderer.as_gles_renderer();
|
||||
let elements = self.render_inner(renderer, Point::from((0., 0.)), focus_ring, target);
|
||||
let elements = self.render_inner(
|
||||
renderer,
|
||||
Point::from((0., 0.)),
|
||||
focus_ring,
|
||||
target,
|
||||
fx_buffers.clone(),
|
||||
);
|
||||
let elements = elements.collect::<Vec<TileRenderElement<_>>>();
|
||||
match open.render(
|
||||
renderer,
|
||||
@@ -1890,7 +1947,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
} else if let Some(alpha) = &self.alpha_animation {
|
||||
let renderer = renderer.as_gles_renderer();
|
||||
let elements = self.render_inner(renderer, Point::from((0., 0.)), focus_ring, target);
|
||||
let elements = self.render_inner(
|
||||
renderer,
|
||||
Point::from((0., 0.)),
|
||||
focus_ring,
|
||||
target,
|
||||
fx_buffers.clone(),
|
||||
);
|
||||
let elements = elements.collect::<Vec<TileRenderElement<_>>>();
|
||||
match alpha.offscreen.render(renderer, scale, &elements) {
|
||||
Ok((elem, _sync, data)) => {
|
||||
@@ -1907,7 +1970,8 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
|
||||
if open_anim_elem.is_none() && alpha_anim_elem.is_none() {
|
||||
window_elems = Some(self.render_inner(renderer, location, focus_ring, target));
|
||||
window_elems =
|
||||
Some(self.render_inner(renderer, location, focus_ring, target, fx_buffers));
|
||||
}
|
||||
|
||||
open_anim_elem
|
||||
@@ -1927,7 +1991,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
fn render_snapshot(&self, renderer: &mut GlesRenderer) -> TileRenderSnapshot {
|
||||
let _span = tracy_client::span!("Tile::render_snapshot");
|
||||
|
||||
let contents = self.render(renderer, Point::from((0., 0.)), false, RenderTarget::Output);
|
||||
let contents = self.render(
|
||||
renderer,
|
||||
Point::from((0., 0.)),
|
||||
false,
|
||||
RenderTarget::Output,
|
||||
None,
|
||||
);
|
||||
|
||||
// A bit of a hack to render blocked out as for screencast, but I think it's fine here.
|
||||
let blocked_out_contents = self.render(
|
||||
@@ -1935,6 +2005,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
Point::from((0., 0.)),
|
||||
false,
|
||||
RenderTarget::Screencast,
|
||||
None,
|
||||
);
|
||||
|
||||
RenderSnapshot {
|
||||
|
||||
@@ -30,6 +30,7 @@ use super::{
|
||||
};
|
||||
use crate::animation::Clock;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::blur::EffectsFramebuffers;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::shadow::ShadowRenderElement;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
@@ -1727,18 +1728,29 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
impl Iterator<Item = WorkspaceRenderElement<R>>,
|
||||
impl Iterator<Item = WorkspaceRenderElement<R>>,
|
||||
) {
|
||||
let fx_buffers = self
|
||||
.current_output()
|
||||
.and_then(EffectsFramebuffers::get_user_data);
|
||||
|
||||
let scrolling_focus_ring = focus_ring && !self.floating_is_active();
|
||||
let scrolling = self
|
||||
.scrolling
|
||||
.render_elements(renderer, target, scrolling_focus_ring);
|
||||
let scrolling = self.scrolling.render_elements(
|
||||
renderer,
|
||||
target,
|
||||
scrolling_focus_ring,
|
||||
fx_buffers.clone(),
|
||||
);
|
||||
let scrolling = scrolling.into_iter().map(WorkspaceRenderElement::from);
|
||||
|
||||
let floating_focus_ring = focus_ring && self.floating_is_active();
|
||||
let floating = self.is_floating_visible().then(|| {
|
||||
let view_rect = Rectangle::from_size(self.view_size);
|
||||
let floating =
|
||||
self.floating
|
||||
.render_elements(renderer, view_rect, target, floating_focus_ring);
|
||||
let floating = self.floating.render_elements(
|
||||
renderer,
|
||||
view_rect,
|
||||
target,
|
||||
floating_focus_ring,
|
||||
fx_buffers.clone(),
|
||||
);
|
||||
floating.into_iter().map(WorkspaceRenderElement::from)
|
||||
});
|
||||
let floating = floating.into_iter().flatten();
|
||||
|
||||
34
src/niri.rs
34
src/niri.rs
@@ -153,6 +153,7 @@ use crate::protocols::virtual_pointer::VirtualPointerManagerState;
|
||||
use crate::pw_utils::{Cast, PipeWire};
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
use crate::pw_utils::{CastSizeChange, PwToNiri};
|
||||
use crate::render_helpers::blur::{EffectsFramebuffers, EffectsFramebufffersUserData};
|
||||
use crate::render_helpers::debug::draw_opaque_regions;
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
@@ -1676,6 +1677,11 @@ impl State {
|
||||
*CHILD_DISPLAY.write().unwrap() = display_name;
|
||||
}
|
||||
|
||||
self.niri
|
||||
.global_space
|
||||
.outputs()
|
||||
.for_each(EffectsFramebuffers::set_dirty);
|
||||
|
||||
// Can't really update xdg-decoration settings since we have to hide the globals for CSD
|
||||
// due to the SDL2 bug... I don't imagine clients are prepared for the xdg-decoration
|
||||
// global suddenly appearing? Either way, right now it's live-reloaded in a sense that new
|
||||
@@ -4449,9 +4455,18 @@ impl Niri {
|
||||
|
||||
// Get layer-shell elements.
|
||||
let layer_map = layer_map_for_output(output);
|
||||
let fx_buffers = EffectsFramebuffers::get_user_data(output);
|
||||
let mut extend_from_layer =
|
||||
|elements: &mut SplitElements<LayerSurfaceRenderElement<R>>, layer, for_backdrop| {
|
||||
self.render_layer(renderer, target, &layer_map, layer, elements, for_backdrop);
|
||||
self.render_layer(
|
||||
renderer,
|
||||
target,
|
||||
&layer_map,
|
||||
layer,
|
||||
elements,
|
||||
for_backdrop,
|
||||
fx_buffers.clone(),
|
||||
);
|
||||
};
|
||||
|
||||
// The overlay layer elements go next.
|
||||
@@ -4566,6 +4581,20 @@ impl Niri {
|
||||
draw_opaque_regions(&mut elements, output_scale);
|
||||
}
|
||||
|
||||
if let Some(mut fx_buffers) = EffectsFramebuffers::get(output) {
|
||||
let blur_config = self.config.borrow().layout.blur;
|
||||
if blur_config.on && blur_config.passes > 0 {
|
||||
if let Err(e) = fx_buffers.update_optimized_blur_buffer(
|
||||
renderer.as_gles_renderer(),
|
||||
layer_map,
|
||||
output_scale,
|
||||
blur_config,
|
||||
) {
|
||||
error!("failed to update optimized blur buffer: {e:?}");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
elements
|
||||
}
|
||||
|
||||
@@ -4578,6 +4607,7 @@ impl Niri {
|
||||
layer: Layer,
|
||||
elements: &mut SplitElements<LayerSurfaceRenderElement<R>>,
|
||||
for_backdrop: bool,
|
||||
fx_buffers: Option<EffectsFramebufffersUserData>,
|
||||
) {
|
||||
// LayerMap returns layers in reverse stacking order.
|
||||
let iter = layer_map.layers_on(layer).rev().filter_map(|surface| {
|
||||
@@ -4591,7 +4621,7 @@ impl Niri {
|
||||
Some((mapped, geo))
|
||||
});
|
||||
for (mapped, geo) in iter {
|
||||
elements.extend(mapped.render(renderer, geo.loc.to_f64(), target));
|
||||
elements.extend(mapped.render(renderer, geo.loc.to_f64(), target, fx_buffers.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
844
src/render_helpers/blur.rs
Normal file
844
src/render_helpers/blur.rs
Normal file
@@ -0,0 +1,844 @@
|
||||
// Ported from https://github.com/nferhat/fht-compositor/blob/main/src/renderer/blur/mod.rs
|
||||
|
||||
pub mod element;
|
||||
pub mod optimized_blur_texture_element;
|
||||
pub(super) mod shader;
|
||||
|
||||
use anyhow::Context;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use glam::{Mat3, Vec2};
|
||||
use niri_config::Blur;
|
||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
||||
use smithay::backend::renderer::element::AsRenderElements;
|
||||
use smithay::backend::renderer::gles::format::fourcc_to_gl_formats;
|
||||
use smithay::backend::renderer::gles::{ffi, Capability, GlesError, GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::{Bind, Blit, Frame, Offscreen, Renderer, Texture, TextureFilter};
|
||||
use smithay::desktop::LayerMap;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::gbm::Format;
|
||||
use smithay::utils::{Buffer, Physical, Point, Rectangle, Scale, Size, Transform};
|
||||
use smithay::wayland::shell::wlr_layer::Layer;
|
||||
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use shader::BlurShaders;
|
||||
|
||||
use super::render_data::RendererData;
|
||||
use super::render_elements;
|
||||
use super::shaders::Shaders;
|
||||
|
||||
use std::sync::MutexGuard;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
enum CurrentBuffer {
|
||||
/// We are currently sampling from normal buffer, and rendering in the swapped/alternative.
|
||||
#[default]
|
||||
Normal,
|
||||
/// We are currently sampling from swapped buffer, and rendering in the normal.
|
||||
Swapped,
|
||||
}
|
||||
|
||||
impl CurrentBuffer {
|
||||
pub fn swap(&mut self) {
|
||||
*self = match self {
|
||||
// sampled from normal, render to swapped
|
||||
Self::Normal => Self::Swapped,
|
||||
// sampled fro swapped, render to normal next
|
||||
Self::Swapped => Self::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Effect framebuffers associated with each output.
|
||||
#[derive(Debug)]
|
||||
pub struct EffectsFramebuffers {
|
||||
/// Contains the main buffer blurred contents
|
||||
pub optimized_blur: GlesTexture,
|
||||
/// Whether the optimizer blur buffer is dirty
|
||||
pub 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
|
||||
effects: GlesTexture,
|
||||
effects_swapped: GlesTexture,
|
||||
/// The buffer we are currently rendering/sampling from.
|
||||
///
|
||||
/// In order todo the up/downscaling, we render into different buffers. On each pass, we render
|
||||
/// into a different buffer with downscaling/upscaling (depending on which pass we are at)
|
||||
///
|
||||
/// One exception is that if we are on the first pass, we are on [`CurrentBuffer::Initial`], we
|
||||
/// are sampling from [`Self::blit_buffer`] from initial screen contents.
|
||||
current_buffer: CurrentBuffer,
|
||||
/// Size of the output that this object runs on.
|
||||
output_size: Size<i32, Physical>,
|
||||
}
|
||||
|
||||
pub type EffectsFramebufffersUserData = Rc<RefCell<EffectsFramebuffers>>;
|
||||
|
||||
fn get_rerender_at() -> Option<Instant> {
|
||||
Some(Instant::now() + Duration::from_millis(150))
|
||||
}
|
||||
|
||||
impl EffectsFramebuffers {
|
||||
/// Get the associated [`EffectsFramebuffers`] with this output.
|
||||
pub fn get<'a>(output: &'a Output) -> Option<RefMut<'a, Self>> {
|
||||
output
|
||||
.user_data()
|
||||
.get::<EffectsFramebufffersUserData>()
|
||||
.map(|e| e.borrow_mut())
|
||||
}
|
||||
|
||||
pub fn get_user_data(output: &Output) -> Option<EffectsFramebufffersUserData> {
|
||||
output.user_data().get().cloned()
|
||||
}
|
||||
|
||||
pub fn set_dirty(output: &Output) {
|
||||
let Some(mut fx_buffers) = Self::get(output) else {
|
||||
warn!("attempting to set fx buffer to dirty on output that has none: {output:?}");
|
||||
return;
|
||||
};
|
||||
|
||||
if fx_buffers.optimized_blur_rerender_at.is_none() {
|
||||
fx_buffers.optimized_blur_rerender_at = get_rerender_at();
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the [`EffectsFramebuffers`] for an [`Output`].
|
||||
///
|
||||
/// The framebuffers handles live inside the Output's user data, use [`Self::get`] to access
|
||||
/// them.
|
||||
pub fn init_for_output(output: Output, renderer: &mut impl NiriRenderer) {
|
||||
let renderer = renderer.as_gles_renderer();
|
||||
let output_size = output.current_mode().unwrap().size;
|
||||
|
||||
fn create_buffer(
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
) -> Result<GlesTexture, GlesError> {
|
||||
renderer.create_buffer(
|
||||
Format::Abgr8888,
|
||||
size.to_logical(1).to_buffer(1, Transform::Normal),
|
||||
)
|
||||
}
|
||||
|
||||
let this = EffectsFramebuffers {
|
||||
optimized_blur: create_buffer(renderer, output_size).unwrap(),
|
||||
optimized_blur_rerender_at: get_rerender_at(),
|
||||
effects: create_buffer(renderer, output_size).unwrap(),
|
||||
effects_swapped: create_buffer(renderer, output_size).unwrap(),
|
||||
current_buffer: CurrentBuffer::Normal,
|
||||
output_size: output.current_mode().unwrap().size,
|
||||
};
|
||||
|
||||
let user_data = output.user_data();
|
||||
assert!(
|
||||
user_data.insert_if_missing(|| Rc::new(RefCell::new(this))),
|
||||
"EffectsFrambuffers::init_for_output should only be called once!"
|
||||
);
|
||||
}
|
||||
|
||||
/// Update the [`EffectsFramebuffers`] for an [`Output`].
|
||||
///
|
||||
/// You should call this if the output's scale/size changes
|
||||
pub fn update_for_output(
|
||||
output: Output,
|
||||
renderer: &mut impl NiriRenderer,
|
||||
) -> Result<(), GlesError> {
|
||||
let renderer = renderer.as_gles_renderer();
|
||||
let Some(mut fx_buffers) = Self::get(&output) else {
|
||||
warn!("attempting to update fx buffer on output that has none: {output:?}");
|
||||
return Ok(()); // TODO: error?
|
||||
};
|
||||
let output_size = output.current_mode().unwrap().size;
|
||||
|
||||
fn create_buffer(
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
) -> Result<GlesTexture, GlesError> {
|
||||
renderer.create_buffer(
|
||||
Format::Abgr8888,
|
||||
size.to_logical(1).to_buffer(1, Transform::Normal),
|
||||
)
|
||||
}
|
||||
|
||||
*fx_buffers = EffectsFramebuffers {
|
||||
optimized_blur: create_buffer(renderer, output_size)?,
|
||||
optimized_blur_rerender_at: get_rerender_at(),
|
||||
effects: create_buffer(renderer, output_size)?,
|
||||
effects_swapped: create_buffer(renderer, output_size)?,
|
||||
current_buffer: CurrentBuffer::Normal,
|
||||
output_size: output.current_mode().unwrap().size,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render the optimized blur buffer again
|
||||
pub fn update_optimized_blur_buffer(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
layer_map: MutexGuard<'_, LayerMap>,
|
||||
scale: Scale<f64>,
|
||||
config: Blur,
|
||||
) -> anyhow::Result<()> {
|
||||
if self.optimized_blur_rerender_at.is_none()
|
||||
|| matches!(self.optimized_blur_rerender_at, Some(t) if t > Instant::now())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.optimized_blur_rerender_at = None;
|
||||
|
||||
// first render layer shell elements
|
||||
// NOTE: We use Blur::DISABLED since we should not include blur with Background/Bottom
|
||||
// layer shells
|
||||
|
||||
let mut elements = vec![];
|
||||
for layer in layer_map
|
||||
.layers_on(Layer::Background)
|
||||
.chain(layer_map.layers_on(Layer::Bottom))
|
||||
.rev()
|
||||
{
|
||||
let layer_geo = layer_map.layer_geometry(layer).unwrap();
|
||||
let location = layer_geo.loc.to_physical_precise_round(scale);
|
||||
elements.extend(
|
||||
layer.render_elements::<WaylandSurfaceRenderElement<_>>(
|
||||
renderer, location, scale, 1.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let mut fb = renderer.bind(&mut self.effects).unwrap();
|
||||
|
||||
let _ = render_elements(
|
||||
renderer,
|
||||
&mut fb,
|
||||
self.output_size,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
elements.iter(),
|
||||
)
|
||||
.expect("failed to render for optimized blur buffer");
|
||||
drop(fb);
|
||||
|
||||
self.current_buffer = CurrentBuffer::Normal;
|
||||
|
||||
let shaders = Shaders::get(renderer).blur.clone();
|
||||
|
||||
// NOTE: If we only do one pass its kinda ugly, there must be at least
|
||||
// n=2 passes in order to have good sampling
|
||||
let half_pixel = [
|
||||
0.5 / (self.output_size.w as f32 / 2.0),
|
||||
0.5 / (self.output_size.h as f32 / 2.0),
|
||||
];
|
||||
|
||||
for _ in 0..config.passes {
|
||||
let (sample_buffer, render_buffer) = self.buffers();
|
||||
render_blur_pass_with_frame(
|
||||
renderer,
|
||||
sample_buffer,
|
||||
render_buffer,
|
||||
&shaders.down,
|
||||
half_pixel,
|
||||
config,
|
||||
)?;
|
||||
self.current_buffer.swap();
|
||||
}
|
||||
|
||||
let half_pixel = [
|
||||
0.5 / (self.output_size.w as f32 * 2.0),
|
||||
0.5 / (self.output_size.h as f32 * 2.0),
|
||||
];
|
||||
// FIXME: Why we need inclusive here but down is exclusive?
|
||||
for _ in 0..config.passes {
|
||||
let (sample_buffer, render_buffer) = self.buffers();
|
||||
render_blur_pass_with_frame(
|
||||
renderer,
|
||||
sample_buffer,
|
||||
render_buffer,
|
||||
&shaders.up,
|
||||
half_pixel,
|
||||
config,
|
||||
)?;
|
||||
self.current_buffer.swap();
|
||||
}
|
||||
|
||||
// Now blit from the last render buffer into optimized_blur
|
||||
// We are already bound so its just a blit
|
||||
let tex_fb = renderer.bind(&mut self.effects).unwrap();
|
||||
let mut optimized_blur_fb = renderer.bind(&mut self.optimized_blur).unwrap();
|
||||
|
||||
let _ = renderer.blit(
|
||||
&tex_fb,
|
||||
&mut optimized_blur_fb,
|
||||
Rectangle::from_size(self.output_size),
|
||||
Rectangle::from_size(self.output_size),
|
||||
TextureFilter::Linear,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the sample and render buffers.
|
||||
pub fn buffers(&mut self) -> (&GlesTexture, &mut GlesTexture) {
|
||||
match self.current_buffer {
|
||||
CurrentBuffer::Normal => (&self.effects, &mut self.effects_swapped),
|
||||
CurrentBuffer::Swapped => (&self.effects_swapped, &mut self.effects),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) unsafe fn get_main_buffer_blur(
|
||||
gl: &ffi::Gles2,
|
||||
fx_buffers: &mut EffectsFramebuffers,
|
||||
shaders: &BlurShaders,
|
||||
blur_config: Blur,
|
||||
projection_matrix: Mat3,
|
||||
scale: i32,
|
||||
vbos: &[u32; 2],
|
||||
debug: bool,
|
||||
supports_instancing: bool,
|
||||
// dst is the region that we want blur on
|
||||
dst: Rectangle<i32, Physical>,
|
||||
is_tty: bool,
|
||||
) -> Result<GlesTexture, GlesError> {
|
||||
let tex_size = fx_buffers
|
||||
.effects
|
||||
.size()
|
||||
.to_logical(1, Transform::Normal)
|
||||
.to_physical(scale);
|
||||
|
||||
let dst_expanded = {
|
||||
let mut dst = dst;
|
||||
let size =
|
||||
(2f32.powi(blur_config.passes as i32 + 1) * blur_config.radius.0 as f32).ceil() as i32;
|
||||
dst.loc -= Point::from((size, size)).upscale(8);
|
||||
dst.size += Size::from((size, size)).upscale(16);
|
||||
dst
|
||||
};
|
||||
|
||||
let mut prev_fbo = 0;
|
||||
gl.GetIntegerv(ffi::FRAMEBUFFER_BINDING, &mut prev_fbo as *mut _);
|
||||
|
||||
let (sample_buffer, _) = fx_buffers.buffers();
|
||||
|
||||
// First get a fbo for the texture we are about to read into
|
||||
let mut sample_fbo = 0u32;
|
||||
{
|
||||
gl.GenFramebuffers(1, &mut sample_fbo as *mut _);
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, sample_fbo);
|
||||
gl.FramebufferTexture2D(
|
||||
ffi::FRAMEBUFFER,
|
||||
ffi::COLOR_ATTACHMENT0,
|
||||
ffi::TEXTURE_2D,
|
||||
sample_buffer.tex_id(),
|
||||
0,
|
||||
);
|
||||
gl.Clear(ffi::COLOR_BUFFER_BIT);
|
||||
let status = gl.CheckFramebufferStatus(ffi::FRAMEBUFFER);
|
||||
if status != ffi::FRAMEBUFFER_COMPLETE {
|
||||
gl.DeleteFramebuffers(1, &mut sample_fbo as *mut _);
|
||||
return Err(GlesError::FramebufferBindingError);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// NOTE: We are assured that the size of the effects texture is the same
|
||||
// as the bound fbo size, so blitting uses dst immediately
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, sample_fbo);
|
||||
|
||||
if is_tty {
|
||||
let src_x0 = dst_expanded.loc.x;
|
||||
let src_y0 = dst_expanded.loc.y;
|
||||
let src_x1 = dst_expanded.loc.x + dst_expanded.size.w;
|
||||
let src_y1 = dst_expanded.loc.y + dst_expanded.size.h;
|
||||
let dst_x0 = src_x0;
|
||||
let dst_y0 = src_y0;
|
||||
let dst_x1 = src_x1;
|
||||
let dst_y1 = src_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,
|
||||
);
|
||||
} else {
|
||||
let fb_height = tex_size.h;
|
||||
|
||||
let dst_y0 = dst_expanded.loc.y + dst_expanded.size.h;
|
||||
let dst_y1 = dst_expanded.loc.y;
|
||||
|
||||
let src_x0 = dst_expanded.loc.x;
|
||||
let src_x1 = dst_expanded.loc.x + dst_expanded.size.w;
|
||||
let src_y0 = fb_height - dst_y0;
|
||||
let src_y1 = fb_height - dst_y1;
|
||||
|
||||
gl.BlitFramebuffer(
|
||||
src_x0,
|
||||
src_y0,
|
||||
src_x1,
|
||||
src_y1,
|
||||
src_x0,
|
||||
dst_y0,
|
||||
src_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);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let passes = blur_config.passes;
|
||||
let half_pixel = [
|
||||
0.5 / (tex_size.w as f32 / 2.0),
|
||||
0.5 / (tex_size.h as f32 / 2.0),
|
||||
];
|
||||
for i in 0..passes {
|
||||
let (sample_buffer, render_buffer) = fx_buffers.buffers();
|
||||
let damage = dst_expanded.downscale(1 << (i + 1));
|
||||
render_blur_pass_with_gl(
|
||||
gl,
|
||||
vbos,
|
||||
debug,
|
||||
supports_instancing,
|
||||
projection_matrix,
|
||||
sample_buffer,
|
||||
render_buffer,
|
||||
scale,
|
||||
&shaders.down,
|
||||
half_pixel,
|
||||
blur_config,
|
||||
damage,
|
||||
)?;
|
||||
fx_buffers.current_buffer.swap();
|
||||
}
|
||||
|
||||
let half_pixel = [
|
||||
0.5 / (tex_size.w as f32 * 2.0),
|
||||
0.5 / (tex_size.h as f32 * 2.0),
|
||||
];
|
||||
for i in 0..passes {
|
||||
let (sample_buffer, render_buffer) = fx_buffers.buffers();
|
||||
let damage = dst_expanded.downscale(1 << (passes - 1 - i));
|
||||
render_blur_pass_with_gl(
|
||||
gl,
|
||||
vbos,
|
||||
debug,
|
||||
supports_instancing,
|
||||
projection_matrix,
|
||||
sample_buffer,
|
||||
render_buffer,
|
||||
scale,
|
||||
&shaders.up,
|
||||
half_pixel,
|
||||
blur_config,
|
||||
damage,
|
||||
)?;
|
||||
fx_buffers.current_buffer.swap();
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
{
|
||||
gl.DeleteFramebuffers(1, &mut sample_fbo as *mut _);
|
||||
gl.BindFramebuffer(ffi::FRAMEBUFFER, prev_fbo as u32);
|
||||
}
|
||||
|
||||
Ok(fx_buffers.effects.clone())
|
||||
}
|
||||
|
||||
// Renders a blur pass using a GlesFrame with syncing and fencing provided by smithay. Used for
|
||||
// updating optimized blur buffer since we are not yet rendering.
|
||||
fn render_blur_pass_with_frame(
|
||||
renderer: &mut GlesRenderer,
|
||||
sample_buffer: &GlesTexture,
|
||||
render_buffer: &mut GlesTexture,
|
||||
blur_program: &shader::BlurShader,
|
||||
half_pixel: [f32; 2],
|
||||
config: Blur,
|
||||
) -> anyhow::Result<()> {
|
||||
// We use a texture render element with a custom GlesTexProgram in order todo the blurring
|
||||
// At least this is what swayfx/scenefx do, but they just use gl calls directly.
|
||||
let size = sample_buffer.size().to_logical(1, Transform::Flipped);
|
||||
|
||||
let vbos = RendererData::get(renderer).vbos;
|
||||
let is_shared = renderer.egl_context().is_shared();
|
||||
|
||||
let mut fb = renderer.bind(render_buffer)?;
|
||||
// Using GlesFrame since I want to use a custom program
|
||||
let mut frame = renderer
|
||||
.render(&mut fb, size.to_physical(1), Transform::Normal)
|
||||
.context("failed to create frame")?;
|
||||
|
||||
let supports_instaning = frame.capabilities().contains(&Capability::Instancing);
|
||||
let debug = !frame.debug_flags().is_empty();
|
||||
let projection = Mat3::from_cols_array(frame.projection());
|
||||
|
||||
let tex_size = sample_buffer.size();
|
||||
let src = Rectangle::from_size(sample_buffer.size()).to_f64();
|
||||
let dst = Rectangle::from_size(size).to_physical(1);
|
||||
|
||||
frame.with_context(|gl| unsafe {
|
||||
// We are doing basically what Frame::render_texture_from_to does, but our own shader struct
|
||||
// instead. This allows me to get into the gl plumbing.
|
||||
|
||||
// NOTE: We are rendering at the origin of the texture, no need to translate
|
||||
let mut mat = Mat3::IDENTITY;
|
||||
let src_size = sample_buffer.size().to_f64();
|
||||
|
||||
if tex_size.is_empty() || src_size.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut tex_mat = build_texture_mat(src, dst, tex_size, Transform::Normal);
|
||||
if sample_buffer.is_y_inverted() {
|
||||
tex_mat *= Mat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0]);
|
||||
}
|
||||
|
||||
// NOTE: We know that this texture is always opaque so skip on some logic checks and
|
||||
// directly render. The following code is from GlesRenderer::render_texture
|
||||
gl.Disable(ffi::BLEND);
|
||||
|
||||
// Since we are just rendering onto the offsreen buffer, the vertices to draw are only 4
|
||||
let damage = [
|
||||
dst.loc.x as f32,
|
||||
dst.loc.y as f32,
|
||||
dst.size.w as f32,
|
||||
dst.size.h as f32,
|
||||
];
|
||||
|
||||
let mut vertices = Vec::with_capacity(4);
|
||||
let damage_len = if supports_instaning {
|
||||
vertices.extend(damage);
|
||||
vertices.len() / 4
|
||||
} else {
|
||||
for _ in 0..6 {
|
||||
// Add the 4 f32s per damage rectangle for each of the 6 vertices.
|
||||
vertices.extend_from_slice(&damage);
|
||||
}
|
||||
|
||||
1
|
||||
};
|
||||
|
||||
mat *= projection;
|
||||
|
||||
// SAFETY: internal texture should always have a format
|
||||
// We also use Abgr8888 which is known and confirmed
|
||||
let (internal_format, _, _) =
|
||||
fourcc_to_gl_formats(sample_buffer.format().unwrap()).unwrap();
|
||||
let variant = blur_program.variant_for_format(Some(internal_format), false);
|
||||
|
||||
let program = if debug {
|
||||
&variant.debug
|
||||
} else {
|
||||
&variant.normal
|
||||
};
|
||||
|
||||
gl.ActiveTexture(ffi::TEXTURE0);
|
||||
gl.BindTexture(ffi::TEXTURE_2D, sample_buffer.tex_id());
|
||||
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
|
||||
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MAG_FILTER, ffi::LINEAR as i32);
|
||||
gl.UseProgram(program.program);
|
||||
|
||||
gl.Uniform1i(program.uniform_tex, 0);
|
||||
gl.UniformMatrix3fv(
|
||||
program.uniform_matrix,
|
||||
1,
|
||||
ffi::FALSE,
|
||||
mat.as_ref() as *const f32,
|
||||
);
|
||||
gl.UniformMatrix3fv(
|
||||
program.uniform_tex_matrix,
|
||||
1,
|
||||
ffi::FALSE,
|
||||
tex_mat.as_ref() as *const f32,
|
||||
);
|
||||
gl.Uniform1f(program.uniform_alpha, 1.0);
|
||||
gl.Uniform1f(program.uniform_radius, config.radius.0 as f32);
|
||||
gl.Uniform2f(program.uniform_half_pixel, half_pixel[0], half_pixel[1]);
|
||||
|
||||
gl.EnableVertexAttribArray(program.attrib_vert as u32);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
|
||||
gl.VertexAttribPointer(
|
||||
program.attrib_vert as u32,
|
||||
2,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
// vert_position
|
||||
gl.EnableVertexAttribArray(program.attrib_vert_position as u32);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
|
||||
gl.VertexAttribPointer(
|
||||
program.attrib_vert_position as u32,
|
||||
4,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
vertices.as_ptr() as *const _,
|
||||
);
|
||||
|
||||
if supports_instaning {
|
||||
gl.VertexAttribDivisor(program.attrib_vert as u32, 0);
|
||||
gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1);
|
||||
gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len as i32);
|
||||
} else {
|
||||
let count = damage_len * 6;
|
||||
gl.DrawArrays(ffi::TRIANGLES, 0, count as i32);
|
||||
}
|
||||
|
||||
gl.BindTexture(ffi::TEXTURE_2D, 0);
|
||||
gl.DisableVertexAttribArray(program.attrib_vert as u32);
|
||||
gl.DisableVertexAttribArray(program.attrib_vert_position as u32);
|
||||
|
||||
gl.Enable(ffi::BLEND);
|
||||
gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// FIXME: Check for Fencing support
|
||||
if is_shared {
|
||||
gl.Finish();
|
||||
}
|
||||
|
||||
Result::<_, GlesError>::Ok(())
|
||||
})??;
|
||||
|
||||
let _sync_point = frame.finish()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Renders a blur pass using gl code bypassing smithay's Frame mechanisms
|
||||
//
|
||||
// When rendering blur in real-time (for windows, for example) there should not be a wait for
|
||||
// fencing/finishing since this will be done when sending the fb to the output. Using a Frame
|
||||
// forces us to do that.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn render_blur_pass_with_gl(
|
||||
gl: &ffi::Gles2,
|
||||
vbos: &[u32; 2],
|
||||
debug: bool,
|
||||
supports_instancing: bool,
|
||||
projection_matrix: Mat3,
|
||||
// The buffers used for blurring
|
||||
sample_buffer: &GlesTexture,
|
||||
render_buffer: &mut GlesTexture,
|
||||
scale: i32,
|
||||
// The current blur program + config
|
||||
blur_program: &shader::BlurShader,
|
||||
half_pixel: [f32; 2],
|
||||
config: Blur,
|
||||
// dst is the region that should have blur
|
||||
// it gets up/downscaled with passes
|
||||
_damage: Rectangle<i32, Physical>,
|
||||
) -> Result<(), GlesError> {
|
||||
let tex_size = sample_buffer.size();
|
||||
let src = Rectangle::from_size(tex_size.to_f64());
|
||||
let dest = src
|
||||
.to_logical(1.0, Transform::Normal, &src.size)
|
||||
.to_physical(scale as f64)
|
||||
.to_i32_round();
|
||||
|
||||
let damage = dest;
|
||||
|
||||
// FIXME: Should we call gl.Finish() when done rendering this pass? If yes, should we check
|
||||
// if the gl context is shared or not? What about fencing, we don't have access to that
|
||||
|
||||
// PERF: Instead of taking the whole src/dst as damage, adapt the code to run with only the
|
||||
// damaged window? This would cause us to make a custom WaylandSurfaceRenderElement to blur out
|
||||
// stuff. Complicated.
|
||||
|
||||
// First bind to our render buffer
|
||||
let mut render_buffer_fbo = 0;
|
||||
{
|
||||
gl.GenFramebuffers(1, &mut render_buffer_fbo as *mut _);
|
||||
gl.BindFramebuffer(ffi::FRAMEBUFFER, render_buffer_fbo);
|
||||
gl.FramebufferTexture2D(
|
||||
ffi::FRAMEBUFFER,
|
||||
ffi::COLOR_ATTACHMENT0,
|
||||
ffi::TEXTURE_2D,
|
||||
render_buffer.tex_id(),
|
||||
0,
|
||||
);
|
||||
|
||||
let status = gl.CheckFramebufferStatus(ffi::FRAMEBUFFER);
|
||||
if status != ffi::FRAMEBUFFER_COMPLETE {
|
||||
return Err(GlesError::FramebufferBindingError);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mat = projection_matrix;
|
||||
// NOTE: We are assured that tex_size != 0, and src.size != too (by damage tracker)
|
||||
let tex_mat = build_texture_mat(src, dest, tex_size, Transform::Normal);
|
||||
|
||||
gl.Disable(ffi::BLEND);
|
||||
|
||||
// FIXME: Use actual damage for this? Would require making a custom window render element
|
||||
// that includes blur and whatnot to get the damage for the window only
|
||||
let damage = [
|
||||
damage.loc.x as f32,
|
||||
damage.loc.y as f32,
|
||||
damage.size.w as f32,
|
||||
damage.size.h as f32,
|
||||
];
|
||||
|
||||
let mut vertices = Vec::with_capacity(4);
|
||||
let damage_len = if supports_instancing {
|
||||
vertices.extend(damage);
|
||||
vertices.len() / 4
|
||||
} else {
|
||||
for _ in 0..6 {
|
||||
// Add the 4 f32s per damage rectangle for each of the 6 vertices.
|
||||
vertices.extend_from_slice(&damage);
|
||||
}
|
||||
|
||||
1
|
||||
};
|
||||
|
||||
// SAFETY: internal texture should always have a format
|
||||
// We also use Abgr8888 which is known and confirmed
|
||||
let (internal_format, _, _) =
|
||||
fourcc_to_gl_formats(sample_buffer.format().unwrap()).unwrap();
|
||||
let variant = blur_program.variant_for_format(Some(internal_format), false);
|
||||
|
||||
let program = if debug {
|
||||
&variant.debug
|
||||
} else {
|
||||
&variant.normal
|
||||
};
|
||||
|
||||
gl.ActiveTexture(ffi::TEXTURE0);
|
||||
gl.BindTexture(ffi::TEXTURE_2D, sample_buffer.tex_id());
|
||||
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
|
||||
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MAG_FILTER, ffi::LINEAR as i32);
|
||||
gl.UseProgram(program.program);
|
||||
|
||||
gl.Uniform1i(program.uniform_tex, 0);
|
||||
gl.UniformMatrix3fv(
|
||||
program.uniform_matrix,
|
||||
1,
|
||||
ffi::FALSE,
|
||||
mat.as_ref() as *const f32,
|
||||
);
|
||||
gl.UniformMatrix3fv(
|
||||
program.uniform_tex_matrix,
|
||||
1,
|
||||
ffi::FALSE,
|
||||
tex_mat.as_ref() as *const f32,
|
||||
);
|
||||
gl.Uniform1f(program.uniform_alpha, 1.0);
|
||||
gl.Uniform1f(program.uniform_radius, config.radius.0 as f32);
|
||||
gl.Uniform2f(program.uniform_half_pixel, half_pixel[0], half_pixel[1]);
|
||||
|
||||
gl.EnableVertexAttribArray(program.attrib_vert as u32);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
|
||||
gl.VertexAttribPointer(
|
||||
program.attrib_vert as u32,
|
||||
2,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
// vert_position
|
||||
gl.EnableVertexAttribArray(program.attrib_vert_position as u32);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
|
||||
gl.VertexAttribPointer(
|
||||
program.attrib_vert_position as u32,
|
||||
4,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
vertices.as_ptr() as *const _,
|
||||
);
|
||||
|
||||
if supports_instancing {
|
||||
gl.VertexAttribDivisor(program.attrib_vert as u32, 0);
|
||||
gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1);
|
||||
gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len as i32);
|
||||
} else {
|
||||
let count = damage_len * 6;
|
||||
gl.DrawArrays(ffi::TRIANGLES, 0, count as i32);
|
||||
}
|
||||
|
||||
gl.BindTexture(ffi::TEXTURE_2D, 0);
|
||||
gl.DisableVertexAttribArray(program.attrib_vert as u32);
|
||||
gl.DisableVertexAttribArray(program.attrib_vert_position as u32);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
{
|
||||
gl.Enable(ffi::BLEND);
|
||||
gl.DeleteFramebuffers(1, &render_buffer_fbo as *const _);
|
||||
gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA);
|
||||
gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Copied from smithay, adapted to use glam structs
|
||||
fn build_texture_mat(
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dest: Rectangle<i32, Physical>,
|
||||
texture: Size<i32, Buffer>,
|
||||
transform: Transform,
|
||||
) -> Mat3 {
|
||||
let dst_src_size = transform.transform_size(src.size);
|
||||
let scale = dst_src_size.to_f64() / dest.size.to_f64();
|
||||
|
||||
let mut tex_mat = Mat3::IDENTITY;
|
||||
// first bring the damage into src scale
|
||||
tex_mat = Mat3::from_scale(Vec2::new(scale.x as f32, scale.y as f32)) * tex_mat;
|
||||
|
||||
// then compensate for the texture transform
|
||||
let transform_mat = Mat3::from_cols_array(transform.matrix().as_ref());
|
||||
let translation = match transform {
|
||||
Transform::Normal => Mat3::IDENTITY,
|
||||
Transform::_90 => Mat3::from_translation(Vec2::new(0f32, dst_src_size.w as f32)),
|
||||
Transform::_180 => {
|
||||
Mat3::from_translation(Vec2::new(dst_src_size.w as f32, dst_src_size.h as f32))
|
||||
}
|
||||
Transform::_270 => Mat3::from_translation(Vec2::new(dst_src_size.h as f32, 0f32)),
|
||||
Transform::Flipped => Mat3::from_translation(Vec2::new(dst_src_size.w as f32, 0f32)),
|
||||
Transform::Flipped90 => Mat3::IDENTITY,
|
||||
Transform::Flipped180 => Mat3::from_translation(Vec2::new(0f32, dst_src_size.h as f32)),
|
||||
Transform::Flipped270 => {
|
||||
Mat3::from_translation(Vec2::new(dst_src_size.h as f32, dst_src_size.w as f32))
|
||||
}
|
||||
};
|
||||
tex_mat = transform_mat * tex_mat;
|
||||
tex_mat = translation * tex_mat;
|
||||
|
||||
// now we can add the src crop loc, the size already done implicit by the src size
|
||||
tex_mat = Mat3::from_translation(Vec2::new(src.loc.x as f32, src.loc.y as f32)) * tex_mat;
|
||||
|
||||
// at last we have to normalize the values for UV space
|
||||
tex_mat = Mat3::from_scale(Vec2::new(
|
||||
(1.0f64 / texture.w as f64) as f32,
|
||||
(1.0f64 / texture.h as f64) as f32,
|
||||
)) * tex_mat;
|
||||
|
||||
tex_mat
|
||||
}
|
||||
462
src/render_helpers/blur/element.rs
Normal file
462
src/render_helpers/blur/element.rs
Normal file
@@ -0,0 +1,462 @@
|
||||
// Originally ported from https://github.com/nferhat/fht-compositor/blob/main/src/renderer/blur/element.rs
|
||||
|
||||
use niri_config::Blur;
|
||||
|
||||
use smithay::backend::renderer::element::texture::TextureRenderElement;
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
|
||||
use smithay::backend::renderer::Renderer;
|
||||
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::render_data::RendererData;
|
||||
use crate::render_helpers::renderer::{AsGlesFrame, NiriRenderer};
|
||||
use crate::render_helpers::shaders::Shaders;
|
||||
|
||||
use super::optimized_blur_texture_element::OptimizedBlurTextureElement;
|
||||
use super::{CurrentBuffer, EffectsFramebuffers};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BlurRenderElement {
|
||||
/// Use optimized blur, aka X-ray blur.
|
||||
///
|
||||
/// This technique relies on [`EffectsFramebuffers::optimized_blur`] to be populated. It will
|
||||
/// render this texture no matter what is below the blur render element.
|
||||
Optimized {
|
||||
tex: OptimizedBlurTextureElement,
|
||||
corner_radius: f32,
|
||||
noise: f32,
|
||||
scale: f64,
|
||||
output_size: Size<i32, Physical>,
|
||||
},
|
||||
/// Use true blur.
|
||||
///
|
||||
/// When using this technique, the compositor will blur the current framebuffer ccontents that
|
||||
/// are below the [`BlurElement`] in order to display them. This adds an additional render step
|
||||
/// but provides true results with the blurred contents.
|
||||
TrueBlur {
|
||||
// we are just a funny texture element that generates the texture on RenderElement::draw
|
||||
id: Id,
|
||||
scale: f64,
|
||||
transform: Transform,
|
||||
src: Rectangle<f64, Logical>,
|
||||
size: Size<i32, Logical>,
|
||||
corner_radius: f32,
|
||||
loc: Point<i32, Physical>,
|
||||
config: Blur,
|
||||
// FIXME: Use DamageBag and expand it as needed?
|
||||
commit_counter: CommitCounter,
|
||||
fx_buffers: EffectsFramebufffersUserData,
|
||||
},
|
||||
}
|
||||
|
||||
impl BlurRenderElement {
|
||||
/// Create a new [`BlurElement`]. You are supposed to put this **below** the translucent surface
|
||||
/// that you want to blur. `area` is assumed to be relative to the `output` you are rendering
|
||||
/// in.
|
||||
///
|
||||
/// If you don't update the blur optimized buffer
|
||||
/// [`EffectsFramebuffers::update_optimized_blur_buffer`] this element will either
|
||||
/// - Display outdated/wrong contents
|
||||
/// - Not display anything since the buffer will be empty.
|
||||
pub fn new_optimized(
|
||||
renderer: &mut impl NiriRenderer,
|
||||
fx_buffers: &EffectsFramebuffers,
|
||||
sample_area: Rectangle<i32, Logical>,
|
||||
loc: Point<i32, Physical>,
|
||||
corner_radius: f32,
|
||||
scale: f64,
|
||||
config: Blur,
|
||||
) -> Self {
|
||||
let texture = fx_buffers.optimized_blur.clone();
|
||||
|
||||
let scaled = sample_area.to_f64().upscale(scale);
|
||||
|
||||
let texture = TextureRenderElement::from_static_texture(
|
||||
Id::new(),
|
||||
renderer.as_gles_renderer().context_id(),
|
||||
loc.to_f64(),
|
||||
texture,
|
||||
1,
|
||||
Transform::Normal,
|
||||
Some(1.0),
|
||||
Some(scaled),
|
||||
Some(scaled.size.to_i32_ceil()),
|
||||
// NOTE: Since this is "optimized" blur, anything below the window will not be
|
||||
// rendered
|
||||
Some(vec![Rectangle::new(
|
||||
scaled.loc.to_i32_ceil(),
|
||||
scaled.size.to_i32_ceil(),
|
||||
)
|
||||
.to_buffer(1, Transform::Normal, &sample_area.size)]),
|
||||
Kind::Unspecified,
|
||||
);
|
||||
|
||||
Self::Optimized {
|
||||
tex: texture.into(),
|
||||
corner_radius,
|
||||
noise: config.noise.0 as f32,
|
||||
scale,
|
||||
output_size: fx_buffers.output_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_true(
|
||||
fx_buffers: EffectsFramebufffersUserData,
|
||||
sample_area: Rectangle<i32, Logical>,
|
||||
loc: Point<i32, Physical>,
|
||||
corner_radius: f32,
|
||||
scale: f64,
|
||||
config: Blur,
|
||||
) -> Self {
|
||||
Self::TrueBlur {
|
||||
id: Id::new(),
|
||||
scale,
|
||||
src: sample_area.to_f64(),
|
||||
transform: Transform::Normal,
|
||||
size: sample_area.size,
|
||||
corner_radius,
|
||||
loc,
|
||||
config,
|
||||
fx_buffers,
|
||||
commit_counter: CommitCounter::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for BlurRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.id(),
|
||||
BlurRenderElement::TrueBlur { id, .. } => id,
|
||||
}
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.current_commit(),
|
||||
BlurRenderElement::TrueBlur { commit_counter, .. } => *commit_counter,
|
||||
}
|
||||
}
|
||||
|
||||
fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.location(scale),
|
||||
BlurRenderElement::TrueBlur { loc, .. } => *loc,
|
||||
}
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.src(),
|
||||
BlurRenderElement::TrueBlur {
|
||||
src,
|
||||
transform,
|
||||
size,
|
||||
scale,
|
||||
..
|
||||
} => src.to_buffer(*scale, *transform, &size.to_f64()),
|
||||
}
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.transform(),
|
||||
BlurRenderElement::TrueBlur { transform, .. } => *transform,
|
||||
}
|
||||
}
|
||||
|
||||
fn damage_since(
|
||||
&self,
|
||||
scale: Scale<f64>,
|
||||
commit: Option<CommitCounter>,
|
||||
) -> DamageSet<i32, Physical> {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.damage_since(scale, commit),
|
||||
BlurRenderElement::TrueBlur { config, .. } => {
|
||||
let passes = config.passes;
|
||||
let radius = config.radius.0 as f32;
|
||||
|
||||
// Since the blur element samples from around itself, we must expand the damage it
|
||||
// induces to include any potential changes.
|
||||
let mut geometry = Rectangle::from_size(self.geometry(scale).size);
|
||||
let size = (2f32.powi(passes as i32 + 1) * radius).ceil() as i32;
|
||||
geometry.loc -= Point::from((size, size));
|
||||
geometry.size += Size::from((size, size)).upscale(2);
|
||||
|
||||
// FIXME: Damage tracking?
|
||||
DamageSet::from_slice(&[geometry])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.opaque_regions(scale),
|
||||
BlurRenderElement::TrueBlur { .. } => {
|
||||
// Since we are rendering as true blur, we will draw whatever is behind the window
|
||||
OpaqueRegions::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
match self {
|
||||
BlurRenderElement::Optimized { tex, .. } => tex.geometry(scale),
|
||||
BlurRenderElement::TrueBlur { loc, size, .. } => {
|
||||
Rectangle::new(*loc, size.to_physical_precise_round(scale))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn kind(&self) -> Kind {
|
||||
Kind::Unspecified
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_true_blur(
|
||||
fx_buffers: &mut EffectsFramebuffers,
|
||||
gles_frame: &mut GlesFrame,
|
||||
config: &Blur,
|
||||
scale: f64,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
corner_radius: f32,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
alpha: f32,
|
||||
is_tty: bool,
|
||||
) -> Result<(), GlesError> {
|
||||
fx_buffers.current_buffer = CurrentBuffer::Normal;
|
||||
|
||||
let shaders = Shaders::get_from_frame(gles_frame).blur.clone();
|
||||
let vbos = RendererData::get_from_frame(gles_frame).vbos;
|
||||
let supports_instancing = gles_frame
|
||||
.capabilities()
|
||||
.contains(&smithay::backend::renderer::gles::Capability::Instancing);
|
||||
let debug = !gles_frame.debug_flags().is_empty();
|
||||
let projection_matrix = glam::Mat3::from_cols_array(gles_frame.projection());
|
||||
|
||||
// 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,
|
||||
scale as i32,
|
||||
&vbos,
|
||||
debug,
|
||||
supports_instancing,
|
||||
dst,
|
||||
is_tty,
|
||||
)
|
||||
})??;
|
||||
|
||||
let (program, additional_uniforms) = if corner_radius == 0.0 {
|
||||
(None, vec![])
|
||||
} else {
|
||||
let program = Shaders::get_from_frame(gles_frame).blur_finish.clone();
|
||||
(
|
||||
program,
|
||||
vec![
|
||||
Uniform::new(
|
||||
"geo",
|
||||
[
|
||||
dst.loc.x as f32,
|
||||
dst.loc.y as f32,
|
||||
dst.size.w as f32,
|
||||
dst.size.h as f32,
|
||||
],
|
||||
),
|
||||
Uniform::new("alpha", alpha),
|
||||
Uniform::new("noise", config.noise.0 as f32),
|
||||
Uniform::new("corner_radius", corner_radius),
|
||||
Uniform::new(
|
||||
"output_size",
|
||||
[
|
||||
fx_buffers.output_size.w as f32,
|
||||
fx_buffers.output_size.h as f32,
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
};
|
||||
|
||||
gles_frame.render_texture_from_to(
|
||||
&blurred_texture,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
Transform::Normal,
|
||||
alpha,
|
||||
program.as_ref(),
|
||||
&additional_uniforms,
|
||||
)
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for BlurRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
gles_frame: &mut GlesFrame,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
match self {
|
||||
Self::Optimized {
|
||||
tex,
|
||||
corner_radius,
|
||||
noise,
|
||||
scale,
|
||||
output_size,
|
||||
} => {
|
||||
let downscaled_dst = Rectangle::new(
|
||||
dst.loc,
|
||||
Size::from((
|
||||
(dst.size.w as f64 / *scale) as i32,
|
||||
(dst.size.h as f64 / *scale) as i32,
|
||||
)),
|
||||
);
|
||||
|
||||
if *corner_radius == 0.0 {
|
||||
<OptimizedBlurTextureElement as RenderElement<GlesRenderer>>::draw(
|
||||
tex,
|
||||
gles_frame,
|
||||
src,
|
||||
downscaled_dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)
|
||||
} else {
|
||||
let program = Shaders::get_from_frame(gles_frame).blur_finish.clone();
|
||||
let gles_frame: &mut GlesFrame = gles_frame;
|
||||
gles_frame.override_default_tex_program(
|
||||
program.unwrap(),
|
||||
vec![
|
||||
Uniform::new(
|
||||
"geo",
|
||||
[
|
||||
dst.loc.x as f32,
|
||||
dst.loc.y as f32,
|
||||
dst.size.w as f32,
|
||||
dst.size.h as f32,
|
||||
],
|
||||
),
|
||||
Uniform::new("corner_radius", *corner_radius),
|
||||
Uniform::new(
|
||||
"output_size",
|
||||
[output_size.w as f32, output_size.h as f32],
|
||||
),
|
||||
Uniform::new("noise", *noise),
|
||||
Uniform::new("alpha", self.alpha()),
|
||||
],
|
||||
);
|
||||
|
||||
let res =
|
||||
<TextureRenderElement<GlesTexture> as RenderElement<GlesRenderer>>::draw(
|
||||
&tex.0,
|
||||
gles_frame,
|
||||
src,
|
||||
downscaled_dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
);
|
||||
|
||||
gles_frame.clear_tex_program_override();
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
Self::TrueBlur {
|
||||
fx_buffers,
|
||||
scale,
|
||||
corner_radius,
|
||||
config,
|
||||
..
|
||||
} => draw_true_blur(
|
||||
&mut fx_buffers.borrow_mut(),
|
||||
gles_frame,
|
||||
config,
|
||||
*scale,
|
||||
dst,
|
||||
*corner_radius,
|
||||
src,
|
||||
damage,
|
||||
opaque_regions,
|
||||
self.alpha(),
|
||||
false,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, _: &mut GlesRenderer) -> Option<UnderlyingStorage<'_>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'render> RenderElement<TtyRenderer<'render>> for BlurRenderElement {
|
||||
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>> {
|
||||
match self {
|
||||
Self::Optimized { .. } => {
|
||||
<BlurRenderElement as RenderElement<GlesRenderer>>::draw(
|
||||
self,
|
||||
frame.as_gles_frame(),
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)?;
|
||||
}
|
||||
|
||||
Self::TrueBlur {
|
||||
fx_buffers,
|
||||
scale,
|
||||
corner_radius,
|
||||
config,
|
||||
..
|
||||
} => {
|
||||
draw_true_blur(
|
||||
&mut fx_buffers.borrow_mut(),
|
||||
frame.as_gles_frame(),
|
||||
config,
|
||||
*scale,
|
||||
dst,
|
||||
*corner_radius,
|
||||
src,
|
||||
damage,
|
||||
opaque_regions,
|
||||
self.alpha(),
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(
|
||||
&'_ self,
|
||||
_renderer: &mut TtyRenderer<'render>,
|
||||
) -> Option<UnderlyingStorage<'_>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
122
src/render_helpers/blur/optimized_blur_texture_element.rs
Normal file
122
src/render_helpers/blur/optimized_blur_texture_element.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use smithay::backend::renderer::element::texture::TextureRenderElement;
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::utils::CommitCounter;
|
||||
use smithay::backend::renderer::Texture;
|
||||
use smithay::utils::{Buffer, Physical, Point, Rectangle, Scale, Transform};
|
||||
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
use crate::render_helpers::renderer::{AsGlesFrame, AsGlesRenderer};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OptimizedBlurTextureElement<E = GlesTexture>(pub TextureRenderElement<E>)
|
||||
where
|
||||
E: Texture + Clone + 'static;
|
||||
|
||||
impl<E: Texture + Clone + 'static> From<TextureRenderElement<E>>
|
||||
for OptimizedBlurTextureElement<E>
|
||||
{
|
||||
fn from(value: TextureRenderElement<E>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Texture + Clone + 'static> Element for OptimizedBlurTextureElement<E> {
|
||||
fn id(&self) -> &Id {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.0.current_commit()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
self.0.src()
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.0.geometry(scale)
|
||||
}
|
||||
|
||||
fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
|
||||
self.geometry(scale).loc
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
Transform::Normal
|
||||
}
|
||||
|
||||
fn damage_since(
|
||||
&self,
|
||||
scale: Scale<f64>,
|
||||
commit: Option<CommitCounter>,
|
||||
) -> smithay::backend::renderer::utils::DamageSet<i32, Physical> {
|
||||
self.0.damage_since(scale, commit)
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
self.0.alpha()
|
||||
}
|
||||
|
||||
fn kind(&self) -> Kind {
|
||||
self.0.kind()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderElement<GlesRenderer> for OptimizedBlurTextureElement<GlesTexture> {
|
||||
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> {
|
||||
<TextureRenderElement<GlesTexture> as RenderElement<GlesRenderer>>::draw(
|
||||
&self.0,
|
||||
frame,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)
|
||||
}
|
||||
|
||||
fn underlying_storage(
|
||||
&'_ self,
|
||||
renderer: &mut GlesRenderer,
|
||||
) -> Option<smithay::backend::renderer::element::UnderlyingStorage<'_>> {
|
||||
self.0.underlying_storage(renderer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'render> RenderElement<TtyRenderer<'render>> for OptimizedBlurTextureElement<GlesTexture> {
|
||||
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 frame = frame.as_gles_frame();
|
||||
|
||||
<TextureRenderElement<GlesTexture> as RenderElement<GlesRenderer>>::draw(
|
||||
&self.0,
|
||||
frame,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn underlying_storage(
|
||||
&'_ self,
|
||||
renderer: &mut TtyRenderer<'render>,
|
||||
) -> Option<UnderlyingStorage<'_>> {
|
||||
self.0.underlying_storage(renderer.as_gles_renderer())
|
||||
}
|
||||
}
|
||||
181
src/render_helpers/blur/shader.rs
Normal file
181
src/render_helpers/blur/shader.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
// Originally ported from https://github.com/nferhat/fht-compositor/blob/main/src/renderer/blur/shaders.rs
|
||||
// The notes below are from the original code.
|
||||
|
||||
//! Since I need more control over how stuff is done when blurring, I use my own shader and
|
||||
//! ffi::Gles2 directly. Gives me the most control but needs some re-writing of existing smithay
|
||||
//! code.
|
||||
//!
|
||||
//! NOTE: Since we are assured that this shader lives as long as the compositor does, there's no
|
||||
//! need to add a destruction callback sender like smithay does.
|
||||
|
||||
use std::fmt::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
use smithay::backend::renderer::gles::{ffi, link_program, GlesError, GlesRenderer};
|
||||
|
||||
const BLUR_DOWN_SRC: &str = include_str!("../shaders/blur_down.frag");
|
||||
const BLUR_UP_SRC: &str = include_str!("../shaders/blur_up.frag");
|
||||
const VERTEX_SRC: &str = include_str!("../shaders/texture.vert");
|
||||
|
||||
/// The set of blur shaders used to render the blur.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlurShaders {
|
||||
pub down: BlurShader,
|
||||
pub up: BlurShader,
|
||||
}
|
||||
|
||||
impl BlurShaders {
|
||||
pub fn compile(renderer: &mut GlesRenderer) -> Result<Self, GlesError> {
|
||||
renderer.with_context(|gl| unsafe {
|
||||
let down = BlurShader::compile(gl, BLUR_DOWN_SRC)?;
|
||||
let up = BlurShader::compile(gl, BLUR_UP_SRC)?;
|
||||
Result::<Self, GlesError>::Ok(Self { down, up })
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlurShader(Arc<[BlurShaderVariant; 2]>);
|
||||
impl BlurShader {
|
||||
pub(super) fn variant_for_format(
|
||||
&self,
|
||||
format: Option<ffi::types::GLenum>,
|
||||
has_alpha: bool,
|
||||
) -> &BlurShaderVariant {
|
||||
match format {
|
||||
Some(ffi::BGRA_EXT) | Some(ffi::RGBA) | Some(ffi::RGBA8) | Some(ffi::RGB10_A2)
|
||||
| Some(ffi::RGBA16F) => {
|
||||
if has_alpha {
|
||||
&self.0[0]
|
||||
} else {
|
||||
&self.0[1]
|
||||
}
|
||||
}
|
||||
// SAFETY: Since we create the blur textures ourselves (through
|
||||
// Offscreen::create_buffer) they should always be local to the renderer
|
||||
None => panic!("Blur textures should not be external!"),
|
||||
_ => panic!("Unknown texture type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) unsafe fn compile(gl: &ffi::Gles2, src: &str) -> Result<Self, GlesError> {
|
||||
let create_variant = |defines: &[&str]| -> Result<BlurShaderVariant, GlesError> {
|
||||
let shader = src.replace(
|
||||
"//_DEFINES_",
|
||||
&defines.iter().fold(String::new(), |mut shader, define| {
|
||||
let _ = writeln!(&mut shader, "#define {}", define);
|
||||
shader
|
||||
}),
|
||||
);
|
||||
let debug_shader = src.replace(
|
||||
"//_DEFINES_",
|
||||
&defines.iter().chain(&["DEBUG_FLAGS"]).fold(
|
||||
String::new(),
|
||||
|mut shader, define| {
|
||||
let _ = writeln!(shader, "#define {}", define);
|
||||
shader
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
let program = unsafe { link_program(gl, VERTEX_SRC, &shader)? };
|
||||
let debug_program = unsafe { link_program(gl, VERTEX_SRC, debug_shader.as_ref())? };
|
||||
|
||||
let vert = c"vert";
|
||||
let vert_position = c"vert_position";
|
||||
let tex = c"tex";
|
||||
let matrix = c"matrix";
|
||||
let tex_matrix = c"tex_matrix";
|
||||
let alpha = c"alpha";
|
||||
let radius = c"radius";
|
||||
let half_pixel = c"half_pixel";
|
||||
|
||||
Ok(BlurShaderVariant {
|
||||
normal: BlurShaderProgram {
|
||||
program,
|
||||
uniform_tex: gl
|
||||
.GetUniformLocation(program, tex.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_matrix: gl
|
||||
.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_tex_matrix: gl.GetUniformLocation(
|
||||
program,
|
||||
tex_matrix.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_alpha: gl
|
||||
.GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_radius: gl
|
||||
.GetUniformLocation(program, radius.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_half_pixel: gl.GetUniformLocation(
|
||||
program,
|
||||
half_pixel.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
attrib_vert: gl
|
||||
.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert_position: gl.GetAttribLocation(
|
||||
program,
|
||||
vert_position.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
},
|
||||
debug: BlurShaderProgram {
|
||||
program: debug_program,
|
||||
uniform_tex: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
tex.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_matrix: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
matrix.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_tex_matrix: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
tex_matrix.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_alpha: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
alpha.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_radius: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
radius.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_half_pixel: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
half_pixel.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
attrib_vert: gl.GetAttribLocation(
|
||||
debug_program,
|
||||
vert.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
attrib_vert_position: gl.GetAttribLocation(
|
||||
debug_program,
|
||||
vert_position.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
},
|
||||
})
|
||||
};
|
||||
|
||||
Ok(BlurShader(Arc::new([
|
||||
create_variant(&[])?,
|
||||
create_variant(&["NO_ALPHA"])?,
|
||||
])))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlurShaderVariant {
|
||||
pub(super) normal: BlurShaderProgram,
|
||||
pub(super) debug: BlurShaderProgram,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct BlurShaderProgram {
|
||||
pub(super) program: ffi::types::GLuint,
|
||||
pub(super) uniform_tex: ffi::types::GLint,
|
||||
pub(super) uniform_tex_matrix: ffi::types::GLint,
|
||||
pub(super) uniform_matrix: ffi::types::GLint,
|
||||
pub(super) uniform_alpha: ffi::types::GLint,
|
||||
pub(super) uniform_radius: ffi::types::GLint,
|
||||
pub(super) uniform_half_pixel: ffi::types::GLint,
|
||||
pub(super) attrib_vert: ffi::types::GLint,
|
||||
pub(super) attrib_vert_position: ffi::types::GLint,
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use self::texture::{TextureBuffer, TextureRenderElement};
|
||||
|
||||
pub mod blur;
|
||||
pub mod border;
|
||||
pub mod clipped_surface;
|
||||
pub mod damage;
|
||||
@@ -26,6 +27,7 @@ pub mod gradient_fade_texture;
|
||||
pub mod memory;
|
||||
pub mod offscreen;
|
||||
pub mod primary_gpu_texture;
|
||||
pub mod render_data;
|
||||
pub mod render_elements;
|
||||
pub mod renderer;
|
||||
pub mod resize;
|
||||
|
||||
114
src/render_helpers/render_data.rs
Normal file
114
src/render_helpers/render_data.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use smithay::backend::renderer::gles::{ffi, Capability, GlesFrame, GlesRenderer};
|
||||
|
||||
/// Extra renderer data used for custom drawing with gles FFI.
|
||||
///
|
||||
/// [`GlesRenderer`] creates these, but keeps them private, so we create our own.
|
||||
pub struct RendererData {
|
||||
pub vbos: [u32; 2],
|
||||
}
|
||||
|
||||
impl RendererData {
|
||||
pub fn init(renderer: &mut GlesRenderer) {
|
||||
let capabilities = renderer.capabilities();
|
||||
let vertices: &[ffi::types::GLfloat] = if capabilities.contains(&Capability::Instancing) {
|
||||
&INSTANCED_VERTS
|
||||
} else {
|
||||
&TRIANGLE_VERTS
|
||||
};
|
||||
|
||||
let this = renderer
|
||||
.with_context(|gl| unsafe {
|
||||
let mut vbos = [0; 2];
|
||||
gl.GenBuffers(vbos.len() as i32, vbos.as_mut_ptr());
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
|
||||
gl.BufferData(
|
||||
ffi::ARRAY_BUFFER,
|
||||
std::mem::size_of_val(vertices) as isize,
|
||||
vertices.as_ptr() as *const _,
|
||||
ffi::STATIC_DRAW,
|
||||
);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[1]);
|
||||
gl.BufferData(
|
||||
ffi::ARRAY_BUFFER,
|
||||
(std::mem::size_of::<ffi::types::GLfloat>() * OUTPUT_VERTS.len()) as isize,
|
||||
OUTPUT_VERTS.as_ptr() as *const _,
|
||||
ffi::STATIC_DRAW,
|
||||
);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
|
||||
Self { vbos }
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
renderer
|
||||
.egl_context()
|
||||
.user_data()
|
||||
.insert_if_missing(|| this),
|
||||
"RendererData should only be initialized once"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn get(renderer: &mut GlesRenderer) -> &Self {
|
||||
renderer.egl_context().user_data().get().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_from_frame<'a>(frame: &'a mut GlesFrame<'_, '_>) -> &'a Self {
|
||||
frame.egl_context().user_data().get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertices for instanced rendering.
|
||||
static INSTANCED_VERTS: [ffi::types::GLfloat; 8] = [
|
||||
1.0, 0.0, // top right
|
||||
0.0, 0.0, // top left
|
||||
1.0, 1.0, // bottom right
|
||||
0.0, 1.0, // bottom left
|
||||
];
|
||||
|
||||
/// Vertices for rendering individual triangles.
|
||||
const MAX_RECTS_PER_DRAW: usize = 10;
|
||||
const TRIANGLE_VERTS: [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] = triangle_verts();
|
||||
const fn triangle_verts() -> [ffi::types::GLfloat; 12 * MAX_RECTS_PER_DRAW] {
|
||||
let mut verts = [0.; 12 * MAX_RECTS_PER_DRAW];
|
||||
let mut i = 0;
|
||||
loop {
|
||||
// Top Left.
|
||||
verts[i * 12] = 0.0;
|
||||
verts[i * 12 + 1] = 0.0;
|
||||
|
||||
// Bottom left.
|
||||
verts[i * 12 + 2] = 0.0;
|
||||
verts[i * 12 + 3] = 1.0;
|
||||
|
||||
// Bottom right.
|
||||
verts[i * 12 + 4] = 1.0;
|
||||
verts[i * 12 + 5] = 1.0;
|
||||
|
||||
// Top left.
|
||||
verts[i * 12 + 6] = 0.0;
|
||||
verts[i * 12 + 7] = 0.0;
|
||||
|
||||
// Bottom right.
|
||||
verts[i * 12 + 8] = 1.0;
|
||||
verts[i * 12 + 9] = 1.0;
|
||||
|
||||
// Top right.
|
||||
verts[i * 12 + 10] = 1.0;
|
||||
verts[i * 12 + 11] = 0.0;
|
||||
|
||||
i += 1;
|
||||
if i == MAX_RECTS_PER_DRAW {
|
||||
break;
|
||||
}
|
||||
}
|
||||
verts
|
||||
}
|
||||
|
||||
/// Vertices for output rendering.
|
||||
static OUTPUT_VERTS: [ffi::types::GLfloat; 8] = [
|
||||
-1.0, 1.0, // top right
|
||||
-1.0, -1.0, // top left
|
||||
1.0, 1.0, // bottom right
|
||||
1.0, -1.0, // bottom left
|
||||
];
|
||||
26
src/render_helpers/shaders/blur_down.frag
Normal file
26
src/render_helpers/shaders/blur_down.frag
Normal file
@@ -0,0 +1,26 @@
|
||||
// Ported from https://github.com/nferhat/fht-compositor/blob/main/src/renderer/shaders/blur-down.frag
|
||||
|
||||
precision highp float;
|
||||
|
||||
#if defined(EXTERNAL)
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
uniform samplerExternalOES tex;
|
||||
#else
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
varying vec2 niri_v_coords;
|
||||
uniform float radius;
|
||||
uniform vec2 half_pixel;
|
||||
|
||||
void main() {
|
||||
vec2 uv = niri_v_coords * 2.0;
|
||||
|
||||
vec4 sum = texture2D(tex, uv) * 4.0;
|
||||
sum += texture2D(tex, uv - half_pixel * radius);
|
||||
sum += texture2D(tex, uv + half_pixel * radius);
|
||||
sum += texture2D(tex, uv + vec2(half_pixel.x, -half_pixel.y) * radius);
|
||||
sum += texture2D(tex, uv - vec2(half_pixel.x, -half_pixel.y) * radius);
|
||||
|
||||
gl_FragColor = sum / 8.0;
|
||||
}
|
||||
98
src/render_helpers/shaders/blur_finish.frag
Normal file
98
src/render_helpers/shaders/blur_finish.frag
Normal file
@@ -0,0 +1,98 @@
|
||||
// Ported from https://github.com/nferhat/fht-compositor/blob/main/src/renderer/shaders/blur-finish.frag
|
||||
//
|
||||
// Implementation from pinnacle-comp/pinnacle (GPL-3.0)
|
||||
// Thank you very much!
|
||||
#version 100
|
||||
|
||||
//_DEFINES_
|
||||
|
||||
#if defined(EXTERNAL)
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
#endif
|
||||
|
||||
precision highp float;
|
||||
#if defined(EXTERNAL)
|
||||
uniform samplerExternalOES tex;
|
||||
#else
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
uniform float alpha;
|
||||
varying vec2 v_coords;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
uniform float tint;
|
||||
#endif
|
||||
|
||||
uniform vec4 geo;
|
||||
uniform vec2 output_size;
|
||||
uniform float corner_radius;
|
||||
uniform float noise;
|
||||
|
||||
float rounding_alpha(vec2 coords, vec2 size, float radius) {
|
||||
vec2 center;
|
||||
|
||||
if (coords.x < radius && coords.y < radius) {
|
||||
center = vec2(radius, radius);
|
||||
} else if (coords.x > size.x - radius && coords.y < radius) {
|
||||
center = vec2(size.x - radius, radius);
|
||||
} else if (coords.x > size.x - radius && coords.y > size.y - radius) {
|
||||
center = vec2(size.x - radius, size.y - radius);
|
||||
} else if (coords.x < radius && coords.y > size.y - radius) {
|
||||
center = vec2(radius, size.y - radius);
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float dist = distance(coords, center);
|
||||
float half_px = 0.5 ;
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
// Noise function copied from hyprland.
|
||||
// I like the effect it gave, can be tweaked further
|
||||
float hash(vec2 p) {
|
||||
vec3 p3 = fract(vec3(p.xyx) * 727.727); // wysi :wink: :wink:
|
||||
p3 += dot(p3, p3.xyz + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 texCoords;
|
||||
|
||||
// Sample the texture.
|
||||
vec4 color = texture2D(tex, v_coords);
|
||||
|
||||
#if defined(NO_ALPHA)
|
||||
color = vec4(color.rgb, 1.0);
|
||||
#endif
|
||||
// This shader exists to make blur rounding correct.
|
||||
//
|
||||
// Since we are scr-ing a texture that is the size of the output, the v_coords are always
|
||||
// relative to the output. This corresponds to gl_FragCoord.
|
||||
vec2 size = geo.zw;
|
||||
vec2 loc;
|
||||
loc.x = gl_FragCoord.x - geo.x;
|
||||
// FIXME: y inverted
|
||||
loc.y = output_size.y - (gl_FragCoord.y + geo.y);
|
||||
|
||||
// Add noise fx
|
||||
// This can be used to achieve a glass look
|
||||
float noiseHash = hash(loc / size);
|
||||
float noiseAmount = (mod(noiseHash, 1.0) - 0.5);
|
||||
color.rgb += noiseAmount * noise;
|
||||
|
||||
// Apply corner rounding inside geometry.
|
||||
color *= rounding_alpha(loc, size, corner_radius);
|
||||
|
||||
// Apply final alpha and tint.
|
||||
color *= alpha;
|
||||
#if defined(DEBUG_FLAGS)
|
||||
if (tint == 1.0)
|
||||
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
|
||||
#endif
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
|
||||
// vim: ft=glsl
|
||||
29
src/render_helpers/shaders/blur_up.frag
Normal file
29
src/render_helpers/shaders/blur_up.frag
Normal file
@@ -0,0 +1,29 @@
|
||||
// Ported from https://github.com/nferhat/fht-compositor/blob/main/src/renderer/shaders/blur-up.frag
|
||||
|
||||
precision highp float;
|
||||
|
||||
#if defined(EXTERNAL)
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
uniform samplerExternalOES tex;
|
||||
#else
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
varying vec2 niri_v_coords;
|
||||
uniform vec2 half_pixel;
|
||||
uniform float radius;
|
||||
|
||||
void main() {
|
||||
vec2 uv = niri_v_coords / 2.0;
|
||||
|
||||
vec4 sum = texture2D(tex, uv + vec2(-half_pixel.x * 2.0, 0.0) * radius);
|
||||
sum += texture2D(tex, uv + vec2(-half_pixel.x, half_pixel.y) * radius) * 2.0;
|
||||
sum += texture2D(tex, uv + vec2(0.0, half_pixel.y * 2.0) * radius);
|
||||
sum += texture2D(tex, uv + vec2(half_pixel.x, half_pixel.y) * radius) * 2.0;
|
||||
sum += texture2D(tex, uv + vec2(half_pixel.x * 2.0, 0.0) * radius);
|
||||
sum += texture2D(tex, uv + vec2(half_pixel.x, -half_pixel.y) * radius) * 2.0;
|
||||
sum += texture2D(tex, uv + vec2(0.0, -half_pixel.y * 2.0) * radius);
|
||||
sum += texture2D(tex, uv + vec2(-half_pixel.x, -half_pixel.y) * radius) * 2.0;
|
||||
|
||||
gl_FragColor = sum / 12.0;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ use smithay::backend::renderer::gles::{
|
||||
UniformValue,
|
||||
};
|
||||
|
||||
use super::blur::shader::BlurShaders;
|
||||
|
||||
use super::renderer::NiriRenderer;
|
||||
use super::shader_element::ShaderProgram;
|
||||
|
||||
@@ -18,6 +20,8 @@ pub struct Shaders {
|
||||
pub custom_resize: RefCell<Option<ShaderProgram>>,
|
||||
pub custom_close: RefCell<Option<ShaderProgram>>,
|
||||
pub custom_open: RefCell<Option<ShaderProgram>>,
|
||||
pub blur_finish: Option<GlesTexProgram>,
|
||||
pub blur: BlurShaders,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -97,6 +101,22 @@ impl Shaders {
|
||||
})
|
||||
.ok();
|
||||
|
||||
let blur_finish = renderer
|
||||
.compile_custom_texture_shader(
|
||||
include_str!("blur_finish.frag"),
|
||||
&[
|
||||
UniformName::new("output_size", UniformType::_2f),
|
||||
UniformName::new("corner_radius", UniformType::_1f),
|
||||
UniformName::new("alpha", UniformType::_1f),
|
||||
UniformName::new("noise", UniformType::_1f),
|
||||
UniformName::new("geo", UniformType::_4f),
|
||||
],
|
||||
)
|
||||
.map_err(|e| warn!("error compiling clipped surface shader: {e:?}"))
|
||||
.ok();
|
||||
|
||||
let blur = BlurShaders::compile(renderer).expect("blur shaders should always compile");
|
||||
|
||||
let gradient_fade = renderer
|
||||
.compile_custom_texture_shader(
|
||||
include_str!("gradient_fade.frag"),
|
||||
@@ -116,6 +136,8 @@ impl Shaders {
|
||||
custom_resize: RefCell::new(None),
|
||||
custom_close: RefCell::new(None),
|
||||
custom_open: RefCell::new(None),
|
||||
blur_finish,
|
||||
blur,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -941,6 +941,10 @@ impl LayoutElement for Mapped {
|
||||
self.need_to_recompute_rules |= changed;
|
||||
}
|
||||
|
||||
fn is_floating(&self) -> bool {
|
||||
self.is_floating
|
||||
}
|
||||
|
||||
fn set_bounds(&self, bounds: Size<i32, Logical>) {
|
||||
self.toplevel().with_pending_state(|state| {
|
||||
state.bounds = Some(bounds);
|
||||
|
||||
@@ -3,7 +3,7 @@ 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,
|
||||
BlockOutFrom, BlurRule, BorderRule, CornerRadius, FloatingPosition, PresetSize, ShadowRule,
|
||||
TabIndicatorRule,
|
||||
};
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
@@ -84,6 +84,8 @@ pub struct ResolvedWindowRules {
|
||||
pub border: BorderRule,
|
||||
/// Shadow overrides.
|
||||
pub shadow: ShadowRule,
|
||||
/// Blur overrides.
|
||||
pub blur: BlurRule,
|
||||
/// Tab indicator overrides.
|
||||
pub tab_indicator: TabIndicatorRule,
|
||||
|
||||
@@ -260,6 +262,7 @@ impl ResolvedWindowRules {
|
||||
resolved.border.merge_with(&rule.border);
|
||||
resolved.shadow.merge_with(&rule.shadow);
|
||||
resolved.tab_indicator.merge_with(&rule.tab_indicator);
|
||||
resolved.blur.merge_with(&rule.blur);
|
||||
|
||||
if let Some(x) = rule.draw_border_with_background {
|
||||
resolved.draw_border_with_background = Some(x);
|
||||
|
||||
@@ -20,6 +20,8 @@ in
|
||||
"*.md"
|
||||
"*.nix"
|
||||
"*.rs"
|
||||
"*.vert"
|
||||
"*.frag"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@ datas = "datas"
|
||||
arange = "arange"
|
||||
localed = "localed"
|
||||
WRONLY = "WRONLY"
|
||||
utput = "utput"
|
||||
|
||||
Reference in New Issue
Block a user