Implement rounded window corners

This commit is contained in:
Ivan Molodetskikh
2024-05-01 19:06:08 +04:00
parent d86df5025c
commit 42cef79c69
27 changed files with 1288 additions and 309 deletions
Generated
+2 -2
View File
@@ -3170,7 +3170,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smithay"
version = "0.3.0"
source = "git+https://github.com/Smithay/smithay.git#7544c4d4b9ef89e2945bb626dd47518fedc5f660"
source = "git+https://github.com/Smithay/smithay.git#219b783609dab41114b8202e8be8e46523c7f4b5"
dependencies = [
"appendlist",
"bitflags 2.5.0",
@@ -3242,7 +3242,7 @@ dependencies = [
[[package]]
name = "smithay-drm-extras"
version = "0.1.0"
source = "git+https://github.com/Smithay/smithay.git#7544c4d4b9ef89e2945bb626dd47518fedc5f660"
source = "git+https://github.com/Smithay/smithay.git#219b783609dab41114b8202e8be8e46523c7f4b5"
dependencies = [
"drm",
"edid-rs",
+189
View File
@@ -722,6 +722,10 @@ pub struct WindowRule {
pub draw_border_with_background: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub opacity: Option<f32>,
#[knuffel(child)]
pub geometry_corner_radius: Option<CornerRadius>,
#[knuffel(child, unwrap(argument))]
pub clip_to_geometry: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub block_out_from: Option<BlockOutFrom>,
}
@@ -748,6 +752,25 @@ impl PartialEq for Match {
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct CornerRadius {
pub top_left: f32,
pub top_right: f32,
pub bottom_right: f32,
pub bottom_left: f32,
}
impl From<CornerRadius> for [f32; 4] {
fn from(value: CornerRadius) -> Self {
[
value.top_left,
value.top_right,
value.bottom_right,
value.bottom_left,
]
}
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockOutFrom {
Screencast,
@@ -1079,6 +1102,53 @@ impl BorderRule {
}
}
impl CornerRadius {
pub fn fit_to(self, width: f32, height: f32) -> Self {
// Like in CSS: https://drafts.csswg.org/css-backgrounds/#corner-overlap
let reduction = f32::min(
f32::min(
width / (self.top_left + self.top_right),
width / (self.bottom_left + self.bottom_right),
),
f32::min(
height / (self.top_left + self.bottom_left),
height / (self.top_right + self.bottom_right),
),
);
let reduction = f32::min(1., reduction);
Self {
top_left: self.top_left * reduction,
top_right: self.top_right * reduction,
bottom_right: self.bottom_right * reduction,
bottom_left: self.bottom_left * reduction,
}
}
pub fn expanded_by(self, width: f32) -> Self {
// Preserve zero radius.
if self == Self::default() {
return self;
}
Self {
top_left: self.top_left + width,
top_right: self.top_right + width,
bottom_right: self.bottom_right + width,
bottom_left: self.bottom_left + width,
}
}
pub fn scaled_by(self, scale: f32) -> Self {
Self {
top_left: self.top_left * scale,
top_right: self.top_right * scale,
bottom_right: self.bottom_right * scale,
bottom_left: self.bottom_left * scale,
}
}
}
impl FromStr for Color {
type Err = miette::Error;
@@ -1606,6 +1676,125 @@ where
}
}
impl<S> knuffel::Decode<S> for CornerRadius
where
S: knuffel::traits::ErrorSpan,
{
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
// Check for unexpected type name.
if let Some(type_name) = &node.type_name {
ctx.emit_error(DecodeError::unexpected(
type_name,
"type name",
"no type name expected for this node",
));
}
let decode_radius = |ctx: &mut knuffel::decode::Context<S>,
val: &knuffel::ast::Value<S>| {
// Check for unexpected type name.
if let Some(typ) = &val.type_name {
ctx.emit_error(DecodeError::TypeName {
span: typ.span().clone(),
found: Some((**typ).clone()),
expected: knuffel::errors::ExpectedType::no_type(),
rust_type: "str",
});
}
// Decode both integers and floats.
let radius = match *val.literal {
knuffel::ast::Literal::Int(ref x) => f32::from(match x.try_into() {
Ok(x) => x,
Err(err) => {
ctx.emit_error(DecodeError::conversion(&val.literal, err));
0i16
}
}),
knuffel::ast::Literal::Decimal(ref x) => match x.try_into() {
Ok(x) => x,
Err(err) => {
ctx.emit_error(DecodeError::conversion(&val.literal, err));
0.
}
},
_ => {
ctx.emit_error(DecodeError::scalar_kind(
knuffel::decode::Kind::Int,
&val.literal,
));
0.
}
};
if radius < 0. {
ctx.emit_error(DecodeError::conversion(&val.literal, "radius must be >= 0"));
}
radius
};
// Get the first argument.
let mut iter_args = node.arguments.iter();
let val = iter_args
.next()
.ok_or_else(|| DecodeError::missing(node, "additional argument is required"))?;
let top_left = decode_radius(ctx, val);
let mut rv = CornerRadius {
top_left,
top_right: top_left,
bottom_right: top_left,
bottom_left: top_left,
};
if let Some(val) = iter_args.next() {
rv.top_right = decode_radius(ctx, val);
let val = iter_args.next().ok_or_else(|| {
DecodeError::missing(node, "either 1 or 4 arguments are required")
})?;
rv.bottom_right = decode_radius(ctx, val);
let val = iter_args.next().ok_or_else(|| {
DecodeError::missing(node, "either 1 or 4 arguments are required")
})?;
rv.bottom_left = decode_radius(ctx, val);
// Check for unexpected following arguments.
if let Some(val) = iter_args.next() {
ctx.emit_error(DecodeError::unexpected(
&val.literal,
"argument",
"unexpected argument",
));
}
}
// Check for unexpected properties and children.
for name in node.properties.keys() {
ctx.emit_error(DecodeError::unexpected(
name,
"property",
format!("unexpected property `{}`", name.escape_default()),
));
}
for child in node.children.as_ref().map(|lst| &lst[..]).unwrap_or(&[]) {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
format!("unexpected node `{}`", child.node_name.escape_default()),
));
}
Ok(rv)
}
}
impl<S> knuffel::Decode<S> for Binds
where
S: knuffel::traits::ErrorSpan,
+20 -13
View File
@@ -3,7 +3,8 @@ use std::sync::atomic::Ordering;
use std::time::Duration;
use niri::animation::ANIMATION_SLOWDOWN;
use niri::render_helpers::gradient::GradientRenderElement;
use niri::render_helpers::border::BorderRenderElement;
use niri_config::CornerRadius;
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Rectangle, Scale, Size};
@@ -60,17 +61,23 @@ impl TestCase for GradientAngle {
let size = (size.w - a * 2, size.h - b * 2);
let area = Rectangle::from_loc_and_size((a, b), size);
GradientRenderElement::new(
renderer,
Scale::from(1.),
area,
area,
[1., 0., 0., 1.],
[0., 1., 0., 1.],
self.angle - FRAC_PI_2,
)
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
BorderRenderElement::shader(renderer)
.map(|shader| {
BorderRenderElement::new(
shader.clone(),
Scale::from(1.),
area,
area,
[1., 0., 0., 1.],
[0., 1., 0., 1.],
self.angle - FRAC_PI_2,
area,
0.,
CornerRadius::default(),
)
})
.into_iter()
.map(|elem| Box::new(elem) as _)
.collect()
}
}
+20 -14
View File
@@ -4,8 +4,8 @@ use std::time::Duration;
use niri::animation::ANIMATION_SLOWDOWN;
use niri::layout::focus_ring::FocusRing;
use niri::render_helpers::gradient::GradientRenderElement;
use niri_config::Color;
use niri::render_helpers::border::BorderRenderElement;
use niri_config::{Color, CornerRadius};
use smithay::backend::renderer::element::RenderElement;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size};
@@ -85,7 +85,7 @@ impl TestCase for GradientArea {
let g_loc = ((size.w - g_size.w) / 2, (size.h - g_size.h) / 2);
let g_area = Rectangle::from_loc_and_size(g_loc, g_size);
self.border.update(g_size, true);
self.border.update(g_size, true, CornerRadius::default());
rv.extend(
self.border
.render(
@@ -98,17 +98,23 @@ impl TestCase for GradientArea {
);
rv.extend(
GradientRenderElement::new(
renderer,
Scale::from(1.),
area,
g_area,
[1., 0., 0., 1.],
[0., 1., 0., 1.],
FRAC_PI_4,
)
.into_iter()
.map(|elem| Box::new(elem) as _),
BorderRenderElement::shader(renderer)
.map(|shader| {
BorderRenderElement::new(
shader.clone(),
Scale::from(1.),
area,
g_area,
[1., 0., 0., 1.],
[0., 1., 0., 1.],
FRAC_PI_4,
area,
0.,
CornerRadius::default(),
)
})
.into_iter()
.map(|elem| Box::new(elem) as _),
);
rv
+124 -30
View File
@@ -1,30 +1,32 @@
use std::cmp::{max, min};
use std::iter::zip;
use arrayvec::ArrayVec;
use niri_config::GradientRelativeTo;
use niri_config::{CornerRadius, GradientRelativeTo};
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
use smithay::backend::renderer::element::Kind;
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
use crate::niri_render_elements;
use crate::render_helpers::gradient::GradientRenderElement;
use crate::render_helpers::border::BorderRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
#[derive(Debug)]
pub struct FocusRing {
buffers: [SolidColorBuffer; 4],
locations: [Point<i32, Logical>; 4],
sizes: [Size<i32, Logical>; 4],
buffers: [SolidColorBuffer; 8],
locations: [Point<i32, Logical>; 8],
sizes: [Size<i32, Logical>; 8],
full_size: Size<i32, Logical>,
is_active: bool,
is_border: bool,
radius: CornerRadius,
config: niri_config::FocusRing,
}
niri_render_elements! {
FocusRingRenderElement => {
SolidColor = SolidColorRenderElement,
Gradient = GradientRenderElement,
Gradient = BorderRenderElement,
}
}
@@ -37,6 +39,7 @@ impl FocusRing {
full_size: Default::default(),
is_active: false,
is_border: false,
radius: Default::default(),
config,
}
}
@@ -45,24 +48,69 @@ impl FocusRing {
self.config = config;
}
pub fn update(&mut self, win_size: Size<i32, Logical>, is_border: bool) {
pub fn update(&mut self, win_size: Size<i32, Logical>, is_border: bool, radius: CornerRadius) {
let width = i32::from(self.config.width);
self.full_size = win_size + Size::from((width * 2, width * 2));
let radius = radius.fit_to(self.full_size.w as f32, self.full_size.h as f32);
if is_border {
self.sizes[0] = Size::from((win_size.w + width * 2, width));
self.sizes[1] = Size::from((win_size.w + width * 2, width));
self.sizes[2] = Size::from((width, win_size.h));
self.sizes[3] = Size::from((width, win_size.h));
let top_left = max(width, radius.top_left.ceil() as i32);
let top_right = min(
self.full_size.w - top_left,
max(width, radius.top_right.ceil() as i32),
);
let bottom_left = min(
self.full_size.h - top_left,
max(width, radius.bottom_left.ceil() as i32),
);
let bottom_right = min(
self.full_size.h - top_right,
min(
self.full_size.w - bottom_left,
max(width, radius.bottom_right.ceil() as i32),
),
);
// Top edge.
self.sizes[0] = Size::from((win_size.w + width * 2 - top_left - top_right, width));
self.locations[0] = Point::from((-width + top_left, -width));
// Bottom edge.
self.sizes[1] =
Size::from((win_size.w + width * 2 - bottom_left - bottom_right, width));
self.locations[1] = Point::from((-width + bottom_left, win_size.h));
// Left edge.
self.sizes[2] = Size::from((width, win_size.h + width * 2 - top_left - bottom_left));
self.locations[2] = Point::from((-width, -width + top_left));
// Right edge.
self.sizes[3] = Size::from((width, win_size.h + width * 2 - top_right - bottom_right));
self.locations[3] = Point::from((win_size.w, -width + top_right));
// Top-left corner.
self.sizes[4] = Size::from((top_left, top_left));
self.locations[4] = Point::from((-width, -width));
// Top-right corner.
self.sizes[5] = Size::from((top_right, top_right));
self.locations[5] = Point::from((win_size.w + width - top_right, -width));
// Bottom-right corner.
self.sizes[6] = Size::from((bottom_right, bottom_right));
self.locations[6] = Point::from((
win_size.w + width - bottom_right,
win_size.h + width - bottom_right,
));
// Bottom-left corner.
self.sizes[7] = Size::from((bottom_left, bottom_left));
self.locations[7] = Point::from((-width, win_size.h + width - bottom_left));
for (buf, size) in zip(&mut self.buffers, self.sizes) {
buf.resize(size);
}
self.locations[0] = Point::from((-width, -width));
self.locations[1] = Point::from((-width, win_size.h));
self.locations[2] = Point::from((-width, 0));
self.locations[3] = Point::from((win_size.w, 0));
} else {
self.sizes[0] = self.full_size;
self.buffers[0].resize(self.sizes[0]);
@@ -70,6 +118,7 @@ impl FocusRing {
}
self.is_border = is_border;
self.radius = radius;
}
pub fn set_active(&mut self, is_active: bool) {
@@ -93,38 +142,83 @@ impl FocusRing {
scale: Scale<f64>,
view_size: Size<i32, Logical>,
) -> impl Iterator<Item = FocusRingRenderElement> {
let mut rv = ArrayVec::<_, 4>::new();
let mut rv = ArrayVec::<_, 8>::new();
if self.config.off {
return rv.into_iter();
}
let border_width = -self.locations[0].y;
// If drawing as a border with width = 0, then there's nothing to draw.
if self.is_border && border_width == 0 {
return rv.into_iter();
}
let gradient = if self.is_active {
self.config.active_gradient
} else {
self.config.inactive_gradient
};
let color = if self.is_active {
self.config.active_color
} else {
self.config.inactive_color
};
let full_rect = Rectangle::from_loc_and_size(location + self.locations[0], self.full_size);
let offset = Point::from((border_width, border_width));
let full_rect = Rectangle::from_loc_and_size(location - offset, self.full_size);
let view_rect = Rectangle::from_loc_and_size((0, 0), view_size);
let border_width = if self.is_border {
// HACK: increase the border width used for the inner rounded corners a tiny bit to
// reduce background bleed.
border_width as f32 + 0.5 / scale.x as f32
} else {
0.
};
let shader = BorderRenderElement::shader(renderer);
let mut push = |buffer, location: Point<i32, Logical>, size: Size<i32, Logical>| {
let elem = gradient.and_then(|gradient| {
let elem = if let Some(gradient) = gradient {
let gradient_area = match gradient.relative_to {
GradientRelativeTo::Window => full_rect,
GradientRelativeTo::WorkspaceView => view_rect,
};
GradientRenderElement::new(
renderer,
scale,
Rectangle::from_loc_and_size(location, size),
gradient_area,
gradient.from.into(),
gradient.to.into(),
((gradient.angle as f32) - 90.).to_radians(),
)
.map(Into::into)
});
shader.cloned().map(|shader| {
BorderRenderElement::new(
shader,
scale,
Rectangle::from_loc_and_size(location, size),
gradient_area,
gradient.from.into(),
gradient.to.into(),
((gradient.angle as f32) - 90.).to_radians(),
full_rect,
border_width,
self.radius,
)
.into()
})
} else if self.radius != CornerRadius::default() {
shader.cloned().map(|shader| {
BorderRenderElement::new(
shader,
scale,
Rectangle::from_loc_and_size(location, size),
full_rect,
color.into(),
color.into(),
0.,
full_rect,
border_width,
self.radius,
)
.into()
})
} else {
None
};
let elem = elem.unwrap_or_else(|| {
SolidColorRenderElement::from_buffer(
+117 -15
View File
@@ -3,6 +3,7 @@ use std::cmp::max;
use std::rc::Rc;
use std::time::Duration;
use niri_config::CornerRadius;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
use smithay::backend::renderer::element::utils::RescaleRenderElement;
@@ -17,6 +18,8 @@ use super::{
};
use crate::animation::Animation;
use crate::niri_render_elements;
use crate::render_helpers::border::BorderRenderElement;
use crate::render_helpers::clipped_surface::ClippedSurfaceRenderElement;
use crate::render_helpers::offscreen::OffscreenRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::resize::ResizeRenderElement;
@@ -76,6 +79,8 @@ niri_render_elements! {
SolidColor = SolidColorRenderElement,
Offscreen = RescaleRenderElement<OffscreenRenderElement>,
Resize = ResizeRenderElement,
Border = BorderRenderElement,
ClippedSurface = ClippedSurfaceRenderElement<R>,
}
}
@@ -205,13 +210,26 @@ impl<W: LayoutElement> Tile<W> {
}
}
let draw_border_with_background = self
.window
.rules()
let rules = self.window.rules();
let draw_border_with_background = rules
.draw_border_with_background
.unwrap_or_else(|| !self.window.has_ssd());
self.border
.update(self.animated_window_size(), !draw_border_with_background);
let border_width = self.effective_border_width().unwrap_or(0);
let radius = if self.is_fullscreen {
CornerRadius::default()
} else {
rules
.geometry_corner_radius
.map_or(CornerRadius::default(), |radius| {
radius.expanded_by(border_width as f32)
})
};
self.border.update(
self.animated_window_size(),
!draw_border_with_background,
radius,
);
self.border.set_active(is_active);
let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
@@ -219,8 +237,19 @@ impl<W: LayoutElement> Tile<W> {
} else {
draw_border_with_background
};
self.focus_ring
.update(self.animated_tile_size(), !draw_focus_ring_with_background);
let radius = if self.is_fullscreen {
CornerRadius::default()
} else if self.effective_border_width().is_some() {
radius
} else {
rules.geometry_corner_radius.unwrap_or_default()
}
.expanded_by(self.focus_ring.width() as f32);
self.focus_ring.update(
self.animated_tile_size(),
!draw_focus_ring_with_background,
radius,
);
self.focus_ring.set_active(is_active);
}
@@ -573,6 +602,11 @@ impl<W: LayoutElement> Tile<W> {
.map_err(|err| warn!("error rendering window to texture: {err:?}"))
.ok();
let rules = self.window.rules();
let clip_to_geometry =
!self.is_fullscreen && rules.clip_to_geometry == Some(true);
let corner_radius = rules.geometry_corner_radius.unwrap_or_default();
if let Some((texture_current, _sync_point, texture_current_geo)) = current {
let elem = ResizeRenderElement::new(
shader,
@@ -584,8 +618,12 @@ impl<W: LayoutElement> Tile<W> {
window_size,
resize.anim.value() as f32,
resize.anim.clamped_value().clamp(0., 1.) as f32,
corner_radius,
clip_to_geometry,
alpha,
);
// FIXME: with split popups, this will use the resize element ID for
// popups, but we want the real IDs.
self.window
.set_offscreen_element_id(Some(elem.id().clone()));
resize_shader = Some(elem.into());
@@ -610,14 +648,77 @@ impl<W: LayoutElement> Tile<W> {
}
// If we're not resizing, render the window itself.
let mut window = None;
let mut window_surface = None;
let mut window_popups = None;
if resize_shader.is_none() && resize_fallback.is_none() {
window = Some(
self.window
.render(renderer, window_render_loc, scale, alpha, target)
.into_iter()
.map(Into::into),
);
let window = self
.window
.render(renderer, window_render_loc, scale, alpha, target);
let geo = Rectangle::from_loc_and_size(window_render_loc, window_size);
let rules = self.window.rules();
let clip_to_geometry = !self.is_fullscreen && rules.clip_to_geometry == Some(true);
let radius = rules
.geometry_corner_radius
.unwrap_or_default()
.fit_to(window_size.w as f32, window_size.h as f32);
let clip_shader = ClippedSurfaceRenderElement::shader(renderer).cloned();
let border_shader = BorderRenderElement::shader(renderer).cloned();
window_surface = Some(window.normal.into_iter().map(move |elem| match elem {
LayoutElementRenderElement::Wayland(elem) => {
// If we should clip to geometry, render a clipped window.
if clip_to_geometry {
if let Some(shader) = clip_shader.clone() {
if ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radius) {
return ClippedSurfaceRenderElement::new(
elem,
scale,
geo,
shader.clone(),
radius,
)
.into();
}
}
}
// Otherwise, render it normally.
LayoutElementRenderElement::Wayland(elem).into()
}
LayoutElementRenderElement::SolidColor(elem) => {
// In this branch we're rendering a blocked-out window with a solid
// color. We need to render it with a rounded corner shader even if
// clip_to_geometry is false, because in this case we're assuming that
// the unclipped window CSD already has corners rounded to the
// user-provided radius, so our blocked-out rendering should match that
// radius.
if radius != CornerRadius::default() {
if let Some(shader) = border_shader.clone() {
return BorderRenderElement::new(
shader,
scale,
elem.geometry(Scale::from(1.)).to_logical(1),
Rectangle::from_loc_and_size(Point::from((0, 0)), geo.size),
elem.color(),
elem.color(),
0.,
elem.geometry(Scale::from(1.)).to_logical(1),
0.,
radius,
)
.into();
}
}
// Otherwise, render the solid color as is.
LayoutElementRenderElement::SolidColor(elem).into()
}
}));
window_popups = Some(window.popups.into_iter().map(Into::into));
}
let rv = resize_popups
@@ -625,7 +726,8 @@ impl<W: LayoutElement> Tile<W> {
.flatten()
.chain(resize_shader)
.chain(resize_fallback)
.chain(window.into_iter().flatten());
.chain(window_popups.into_iter().flatten())
.chain(window_surface.into_iter().flatten());
let elem = self.is_fullscreen.then(|| {
SolidColorRenderElement::from_buffer(
@@ -1,30 +1,40 @@
use glam::Vec2;
use std::collections::HashMap;
use glam::{Mat3, Vec2};
use niri_config::CornerRadius;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::element::PixelShaderElement;
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, Uniform};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
use super::primary_gpu_pixel_shader::PrimaryGpuPixelShaderRenderElement;
use super::renderer::NiriRenderer;
use super::shaders::Shaders;
use super::shader_element::{ShaderProgram, ShaderRenderElement};
use super::shaders::{mat3_uniform, Shaders};
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
/// Renders a sub- or super-rect of an angled linear gradient like CSS linear-gradient(angle, a, b).
/// Renders a wide variety of borders and border parts.
///
/// This includes:
/// * sub- or super-rect of an angled linear gradient like CSS linear-gradient(angle, a, b).
/// * corner rounding.
/// * as a background rectangle and as parts of a border line.
#[derive(Debug)]
pub struct GradientRenderElement(PrimaryGpuPixelShaderRenderElement);
pub struct BorderRenderElement(ShaderRenderElement);
impl GradientRenderElement {
impl BorderRenderElement {
#[allow(clippy::too_many_arguments)]
pub fn new(
renderer: &mut impl NiriRenderer,
shader: ShaderProgram,
scale: Scale<f64>,
area: Rectangle<i32, Logical>,
gradient_area: Rectangle<i32, Logical>,
color_from: [f32; 4],
color_to: [f32; 4],
angle: f32,
) -> Option<Self> {
let shader = Shaders::get(renderer).gradient_border.clone()?;
geometry: Rectangle<i32, Logical>,
border_width: f32,
corner_radius: CornerRadius,
) -> Self {
let grad_offset = (area.loc - gradient_area.loc).to_f64().to_physical(scale);
let grad_dir = Vec2::from_angle(angle);
@@ -42,9 +52,24 @@ impl GradientRenderElement {
grad_vec = -grad_vec;
}
let elem = PixelShaderElement::new(
let area_physical = area.to_physical_precise_round(scale);
let area_loc = Vec2::new(area_physical.loc.x, area_physical.loc.y);
let area_size = Vec2::new(area_physical.size.w, area_physical.size.h);
let geo = geometry.to_physical_precise_round(scale);
let geo_loc = Vec2::new(geo.loc.x, geo.loc.y);
let geo_size = Vec2::new(geo.size.w, geo.size.h);
let input_to_geo =
Mat3::from_scale(area_size) * Mat3::from_translation((area_loc - geo_loc) / area_size);
let corner_radius = corner_radius.scaled_by(scale.x as f32);
let border_width = border_width * scale.x as f32;
let elem = ShaderRenderElement::new(
shader,
HashMap::new(),
area,
area.size.to_f64().to_buffer(scale, Transform::Normal),
None,
1.,
vec![
@@ -53,14 +78,22 @@ impl GradientRenderElement {
Uniform::new("grad_offset", (grad_offset.x as f32, grad_offset.y as f32)),
Uniform::new("grad_width", w),
Uniform::new("grad_vec", grad_vec.to_array()),
mat3_uniform("input_to_geo", input_to_geo),
Uniform::new("geo_size", geo_size.to_array()),
Uniform::new("outer_radius", <[f32; 4]>::from(corner_radius)),
Uniform::new("border_width", border_width),
],
Kind::Unspecified,
);
Some(Self(PrimaryGpuPixelShaderRenderElement(elem)))
Self(elem)
}
pub fn shader(renderer: &mut impl NiriRenderer) -> Option<&ShaderProgram> {
Shaders::get(renderer).border.as_ref()
}
}
impl Element for GradientRenderElement {
impl Element for BorderRenderElement {
fn id(&self) -> &Id {
self.0.id()
}
@@ -102,7 +135,7 @@ impl Element for GradientRenderElement {
}
}
impl RenderElement<GlesRenderer> for GradientRenderElement {
impl RenderElement<GlesRenderer> for BorderRenderElement {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
@@ -118,7 +151,7 @@ impl RenderElement<GlesRenderer> for GradientRenderElement {
}
}
impl<'render> RenderElement<TtyRenderer<'render>> for GradientRenderElement {
impl<'render> RenderElement<TtyRenderer<'render>> for BorderRenderElement {
fn draw(
&self,
frame: &mut TtyFrame<'_, '_>,
+269
View File
@@ -0,0 +1,269 @@
use glam::{Mat3, Vec2};
use niri_config::CornerRadius;
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{
GlesError, GlesFrame, GlesRenderer, GlesTexProgram, Uniform,
};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform};
use super::renderer::{AsGlesFrame as _, NiriRenderer};
use super::shaders::{mat3_uniform, Shaders};
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
#[derive(Debug)]
pub struct ClippedSurfaceRenderElement<R: NiriRenderer> {
inner: WaylandSurfaceRenderElement<R>,
program: GlesTexProgram,
corner_radius: CornerRadius,
geometry: Rectangle<i32, Logical>,
input_to_geo: Mat3,
}
impl<R: NiriRenderer> ClippedSurfaceRenderElement<R> {
pub fn new(
elem: WaylandSurfaceRenderElement<R>,
scale: Scale<f64>,
geometry: Rectangle<i32, Logical>,
program: GlesTexProgram,
corner_radius: CornerRadius,
) -> Self {
let elem_geo = elem.geometry(scale);
let elem_geo_loc = Vec2::new(elem_geo.loc.x as f32, elem_geo.loc.y as f32);
let elem_geo_size = Vec2::new(elem_geo.size.w as f32, elem_geo.size.h as f32);
let geo = geometry.to_physical_precise_round(scale);
let geo_loc = Vec2::new(geo.loc.x, geo.loc.y);
let geo_size = Vec2::new(geo.size.w, geo.size.h);
let buf_size = elem.buffer_size().unwrap();
let buf_size = Vec2::new(buf_size.w as f32, buf_size.h as f32);
let view = elem.view().unwrap();
let src_loc = Vec2::new(view.src.loc.x as f32, view.src.loc.y as f32);
let src_size = Vec2::new(view.src.size.w as f32, view.src.size.h as f32);
let transform = elem.transform();
// HACK: ??? for some reason flipped ones are fine.
let transform = match transform {
Transform::_90 => Transform::_270,
Transform::_270 => Transform::_90,
x => x,
};
let transform_matrix = Mat3::from_translation(Vec2::new(0.5, 0.5))
* Mat3::from_cols_array(transform.matrix().as_ref())
* Mat3::from_translation(-Vec2::new(0.5, 0.5));
// FIXME: y_inverted
let input_to_geo = transform_matrix * Mat3::from_scale(elem_geo_size / geo_size)
* Mat3::from_translation((elem_geo_loc - geo_loc) / elem_geo_size)
// Apply viewporter src.
* Mat3::from_scale(buf_size / src_size)
* Mat3::from_translation(-src_loc / buf_size);
Self {
inner: elem,
program,
corner_radius,
geometry,
input_to_geo,
}
}
pub fn shader(renderer: &mut R) -> Option<&GlesTexProgram> {
Shaders::get(renderer).clipped_surface.as_ref()
}
pub fn will_clip(
elem: &WaylandSurfaceRenderElement<R>,
scale: Scale<f64>,
geometry: Rectangle<i32, Logical>,
corner_radius: CornerRadius,
) -> bool {
let elem_geo = elem.geometry(scale);
let geo = geometry.to_physical_precise_round(scale);
if corner_radius == CornerRadius::default() {
!geo.contains_rect(elem_geo)
} else {
let corners = Self::rounded_corners(geometry.to_f64(), corner_radius);
let corners = corners
.into_iter()
.map(|rect| rect.to_physical_precise_round(scale));
let geo = Rectangle::subtract_rects_many([geo], corners);
!Rectangle::subtract_rects_many([elem_geo], geo).is_empty()
}
}
fn rounded_corners(
geo: Rectangle<f64, Logical>,
corner_radius: CornerRadius,
) -> [Rectangle<f64, Logical>; 4] {
let top_left = corner_radius.top_left as f64;
let top_right = corner_radius.top_right as f64;
let bottom_right = corner_radius.bottom_right as f64;
let bottom_left = corner_radius.bottom_left as f64;
[
Rectangle::from_loc_and_size(geo.loc, (top_left, top_left)),
Rectangle::from_loc_and_size(
(geo.loc.x + geo.size.w - top_right, geo.loc.y),
(top_right, top_right),
),
Rectangle::from_loc_and_size(
(
geo.loc.x + geo.size.w - bottom_right,
geo.loc.y + geo.size.h - bottom_right,
),
(bottom_right, bottom_right),
),
Rectangle::from_loc_and_size(
(geo.loc.x, geo.loc.y + geo.size.h - bottom_left),
(bottom_left, bottom_left),
),
]
}
}
impl<R: NiriRenderer> Element for ClippedSurfaceRenderElement<R> {
fn id(&self) -> &Id {
self.inner.id()
}
fn current_commit(&self) -> CommitCounter {
self.inner.current_commit()
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
self.inner.geometry(scale)
}
fn src(&self) -> Rectangle<f64, Buffer> {
self.inner.src()
}
fn transform(&self) -> Transform {
self.inner.transform()
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
// FIXME: radius changes need to cause damage.
let damage = self.inner.damage_since(scale, commit);
// Intersect with geometry, since we're clipping by it.
let mut geo = self.geometry.to_physical_precise_round(scale);
geo.loc -= self.geometry(scale).loc;
damage
.into_iter()
.filter_map(|rect| rect.intersection(geo))
.collect()
}
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
let regions = self.inner.opaque_regions(scale);
// Intersect with geometry, since we're clipping by it.
let mut geo = self.geometry.to_physical_precise_round(scale);
geo.loc -= self.geometry(scale).loc;
let regions = regions
.into_iter()
.filter_map(|rect| rect.intersection(geo));
// Subtract the rounded corners.
if self.corner_radius == CornerRadius::default() {
regions.collect()
} else {
let corners = Self::rounded_corners(self.geometry.to_f64(), self.corner_radius);
let elem_loc = self.geometry(scale).loc;
let corners = corners.into_iter().map(|rect| {
let mut rect = rect.to_physical_precise_round(scale);
rect.loc -= elem_loc;
rect
});
Rectangle::subtract_rects_many(regions, corners)
}
}
fn alpha(&self) -> f32 {
self.inner.alpha()
}
fn kind(&self) -> Kind {
self.inner.kind()
}
}
impl RenderElement<GlesRenderer> for ClippedSurfaceRenderElement<GlesRenderer> {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), GlesError> {
frame.override_default_tex_program(
self.program.clone(),
vec![
Uniform::new(
"geo_size",
(self.geometry.size.w as f32, self.geometry.size.h as f32),
),
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
mat3_uniform("input_to_geo", self.input_to_geo),
],
);
RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage)?;
frame.clear_tex_program_override();
Ok(())
}
fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
// If scanout for things other than Wayland buffers is implemented, this will need to take
// the target GPU into account.
None
}
}
impl<'render> RenderElement<TtyRenderer<'render>>
for ClippedSurfaceRenderElement<TtyRenderer<'render>>
{
fn draw(
&self,
frame: &mut TtyFrame<'render, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), TtyRendererError<'render>> {
frame.as_gles_frame().override_default_tex_program(
self.program.clone(),
vec![
Uniform::new(
"geo_size",
(self.geometry.size.w as f32, self.geometry.size.h as f32),
),
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
mat3_uniform("input_to_geo", self.input_to_geo),
],
);
RenderElement::draw(&self.inner, frame, src, dst, damage)?;
frame.as_gles_frame().clear_tex_program_override();
Ok(())
}
fn underlying_storage(
&self,
_renderer: &mut TtyRenderer<'render>,
) -> Option<UnderlyingStorage> {
// If scanout for things other than Wayland buffers is implemented, this will need to take
// the target GPU into account.
None
}
}
+3 -3
View File
@@ -16,15 +16,15 @@ use smithay::wayland::shm;
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
pub mod gradient;
pub mod border;
pub mod clipped_surface;
pub mod offscreen;
pub mod primary_gpu_pixel_shader;
pub mod primary_gpu_pixel_shader_with_textures;
pub mod primary_gpu_texture;
pub mod render_elements;
pub mod renderer;
pub mod resize;
pub mod resources;
pub mod shader_element;
pub mod shaders;
pub mod snapshot;
pub mod surface;
@@ -1,97 +0,0 @@
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::element::PixelShaderElement;
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform};
use super::renderer::AsGlesFrame;
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU.
#[derive(Debug)]
pub struct PrimaryGpuPixelShaderRenderElement(pub PixelShaderElement);
impl Element for PrimaryGpuPixelShaderRenderElement {
fn id(&self) -> &Id {
self.0.id()
}
fn current_commit(&self) -> CommitCounter {
self.0.current_commit()
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
self.0.geometry(scale)
}
fn transform(&self) -> Transform {
self.0.transform()
}
fn src(&self) -> Rectangle<f64, Buffer> {
self.0.src()
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
self.0.damage_since(scale, commit)
}
fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> {
self.0.opaque_regions(scale)
}
fn alpha(&self) -> f32 {
self.0.alpha()
}
fn kind(&self) -> Kind {
self.0.kind()
}
}
impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderRenderElement {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), GlesError> {
let gles_frame = frame.as_gles_frame();
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
Ok(())
}
fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
// If scanout for things other than Wayland buffers is implemented, this will need to take
// the target GPU into account.
None
}
}
impl<'render> RenderElement<TtyRenderer<'render>> for PrimaryGpuPixelShaderRenderElement {
fn draw(
&self,
frame: &mut TtyFrame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), TtyRendererError<'render>> {
let gles_frame = frame.as_gles_frame();
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
Ok(())
}
fn underlying_storage(
&self,
_renderer: &mut TtyRenderer<'render>,
) -> Option<UnderlyingStorage> {
// If scanout for things other than Wayland buffers is implemented, this will need to take
// the target GPU into account.
None
}
}
+14 -7
View File
@@ -1,25 +1,24 @@
use std::collections::HashMap;
use glam::{Mat3, Vec2};
use niri_config::CornerRadius;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet};
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size, Transform};
use super::primary_gpu_pixel_shader_with_textures::{
PixelWithTexturesProgram, PrimaryGpuPixelShaderWithTexturesRenderElement,
};
use super::renderer::{AsGlesFrame, NiriRenderer};
use super::shader_element::{ShaderProgram, ShaderRenderElement};
use super::shaders::{mat3_uniform, Shaders};
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
#[derive(Debug)]
pub struct ResizeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement);
pub struct ResizeRenderElement(ShaderRenderElement);
impl ResizeRenderElement {
#[allow(clippy::too_many_arguments)]
pub fn new(
shader: PixelWithTexturesProgram,
shader: ShaderProgram,
area: Rectangle<i32, Logical>,
scale: Scale<f64>,
texture_prev: (GlesTexture, Rectangle<i32, Physical>),
@@ -28,6 +27,8 @@ impl ResizeRenderElement {
size_next: Size<i32, Logical>,
progress: f32,
clamped_progress: f32,
corner_radius: CornerRadius,
clip_to_geometry: bool,
result_alpha: f32,
) -> Self {
let curr_geo = area;
@@ -84,9 +85,13 @@ impl ResizeRenderElement {
* Mat3::from_scale(size_next / tex_next_geo_size * scale);
let curr_geo_size = curr_geo_size * scale;
let corner_radius = corner_radius
.scaled_by(scale.x)
.fit_to(curr_geo_size.x, curr_geo_size.y);
let clip_to_geometry = if clip_to_geometry { 1. } else { 0. };
// Create the shader.
Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new(
Self(ShaderRenderElement::new(
shader,
HashMap::from([
(String::from("niri_tex_prev"), texture_prev),
@@ -105,12 +110,14 @@ impl ResizeRenderElement {
mat3_uniform("niri_geo_to_tex_next", geo_to_tex_next),
Uniform::new("niri_progress", progress),
Uniform::new("niri_clamped_progress", clamped_progress),
Uniform::new("niri_corner_radius", <[f32; 4]>::from(corner_radius)),
Uniform::new("niri_clip_to_geometry", clip_to_geometry),
],
Kind::Unspecified,
))
}
pub fn shader(renderer: &mut impl NiriRenderer) -> Option<PixelWithTexturesProgram> {
pub fn shader(renderer: &mut impl NiriRenderer) -> Option<ShaderProgram> {
Shaders::get(renderer).resize()
}
}
@@ -9,18 +9,17 @@ use smithay::backend::renderer::gles::{
UniformDesc, UniformName,
};
use smithay::backend::renderer::utils::CommitCounter;
use smithay::backend::renderer::DebugFlags;
use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size};
use super::renderer::AsGlesFrame;
use super::resources::Resources;
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU.
///
/// The shader accepts textures as input.
/// Renders a shader with optional texture input, on the primary GPU.
#[derive(Debug)]
pub struct PrimaryGpuPixelShaderWithTexturesRenderElement {
shader: PixelWithTexturesProgram,
pub struct ShaderRenderElement {
shader: ShaderProgram,
textures: HashMap<String, GlesTexture>,
id: Id,
commit_counter: CommitCounter,
@@ -33,10 +32,17 @@ pub struct PrimaryGpuPixelShaderWithTexturesRenderElement {
}
#[derive(Debug, Clone)]
pub struct PixelWithTexturesProgram(Rc<PixelWithTexturesProgramInner>);
pub struct ShaderProgram(Rc<ShaderProgramInner>);
#[derive(Debug)]
struct PixelWithTexturesProgramInner {
struct ShaderProgramInner {
normal: ShaderProgramInternal,
debug: ShaderProgramInternal,
uniform_tint: ffi::types::GLint,
}
#[derive(Debug)]
struct ShaderProgramInternal {
program: ffi::types::GLuint,
uniform_tex_matrix: ffi::types::GLint,
uniform_matrix: ffi::types::GLint,
@@ -54,10 +60,12 @@ unsafe fn compile_program(
additional_uniforms: &[UniformName<'_>],
texture_uniforms: &[&str],
// destruction_callback_sender: Sender<CleanupResource>,
) -> Result<PixelWithTexturesProgram, GlesError> {
let shader = src;
let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), shader)? };
) -> Result<ShaderProgram, GlesError> {
let shader = format!("#version 100\n{}", src);
let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), &shader)? };
let debug_shader = format!("#version 100\n#define DEBUG_FLAGS\n{}", src);
let debug_program =
unsafe { link_program(gl, include_str!("shaders/texture.vert"), &debug_shader)? };
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
let vert_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated");
@@ -65,9 +73,10 @@ unsafe fn compile_program(
let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
let size = CStr::from_bytes_with_nul(b"niri_size\0").expect("NULL terminated");
let alpha = CStr::from_bytes_with_nul(b"niri_alpha\0").expect("NULL terminated");
let tint = CStr::from_bytes_with_nul(b"niri_tint\0").expect("NULL terminated");
Ok(PixelWithTexturesProgram(Rc::new(
PixelWithTexturesProgramInner {
Ok(ShaderProgram(Rc::new(ShaderProgramInner {
normal: ShaderProgramInternal {
program,
uniform_matrix: gl
.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
@@ -106,10 +115,60 @@ unsafe fn compile_program(
})
.collect(),
},
)))
debug: ShaderProgramInternal {
program: debug_program,
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_size: gl
.GetUniformLocation(debug_program, size.as_ptr() as *const ffi::types::GLchar),
uniform_alpha: gl
.GetUniformLocation(debug_program, alpha.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,
),
additional_uniforms: additional_uniforms
.iter()
.map(|uniform| {
let name =
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
let location = gl.GetUniformLocation(
debug_program,
name.as_ptr() as *const ffi::types::GLchar,
);
(
uniform.name.clone().into_owned(),
UniformDesc {
location,
type_: uniform.type_,
},
)
})
.collect(),
texture_uniforms: texture_uniforms
.iter()
.map(|name_| {
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
let location = gl.GetUniformLocation(
debug_program,
name.as_ptr() as *const ffi::types::GLchar,
);
(name_.to_string(), location)
})
.collect(),
},
uniform_tint: gl
.GetUniformLocation(debug_program, tint.as_ptr() as *const ffi::types::GLchar),
})))
}
impl PixelWithTexturesProgram {
impl ShaderProgram {
pub fn compile(
renderer: &mut GlesRenderer,
src: &str,
@@ -123,15 +182,16 @@ impl PixelWithTexturesProgram {
pub fn destroy(self, renderer: &mut GlesRenderer) -> Result<(), GlesError> {
renderer.with_context(move |gl| unsafe {
gl.DeleteProgram(self.0.program);
gl.DeleteProgram(self.0.normal.program);
gl.DeleteProgram(self.0.debug.program);
})
}
}
impl PrimaryGpuPixelShaderWithTexturesRenderElement {
impl ShaderRenderElement {
#[allow(clippy::too_many_arguments)]
pub fn new(
shader: PixelWithTexturesProgram,
shader: ShaderProgram,
textures: HashMap<String, GlesTexture>,
area: Rectangle<i32, Logical>,
size: Size<f64, Buffer>,
@@ -158,7 +218,7 @@ impl PrimaryGpuPixelShaderWithTexturesRenderElement {
}
}
impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement {
impl Element for ShaderRenderElement {
fn id(&self) -> &Id {
&self.id
}
@@ -191,7 +251,7 @@ impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement {
}
}
impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElement {
impl RenderElement<GlesRenderer> for ShaderRenderElement {
fn draw(
&self,
frame: &mut GlesFrame<'_>,
@@ -274,7 +334,14 @@ impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElem
//apply output transformation
matrix = Mat3::from_cols_array(frame.projection()) * matrix;
let program = &self.shader.0;
let has_debug = !frame.debug_flags().is_empty();
let has_tint = frame.debug_flags().contains(DebugFlags::TINT);
let program = if has_debug {
&self.shader.0.debug
} else {
&self.shader.0.normal
};
// render
frame.with_context(move |gl| -> Result<(), GlesError> {
@@ -317,6 +384,11 @@ impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElem
gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32);
gl.Uniform1f(program.uniform_alpha, self.alpha);
let tint = if has_tint { 1.0f32 } else { 0.0f32 };
if has_debug {
gl.Uniform1f(self.shader.0.uniform_tint, tint);
}
for uniform in &self.additional_uniforms {
let desc =
program
@@ -408,9 +480,7 @@ impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderWithTexturesRenderElem
}
}
impl<'render> RenderElement<TtyRenderer<'render>>
for PrimaryGpuPixelShaderWithTexturesRenderElement
{
impl<'render> RenderElement<TtyRenderer<'render>> for ShaderRenderElement {
fn draw(
&self,
frame: &mut TtyFrame<'_, '_>,
+87
View File
@@ -0,0 +1,87 @@
precision mediump float;
#if defined(DEBUG_FLAGS)
uniform float niri_tint;
#endif
uniform float niri_alpha;
uniform vec2 niri_size;
varying vec2 niri_v_coords;
uniform vec4 color_from;
uniform vec4 color_to;
uniform vec2 grad_offset;
uniform float grad_width;
uniform vec2 grad_vec;
uniform mat3 input_to_geo;
uniform vec2 geo_size;
uniform vec4 outer_radius;
uniform float border_width;
vec4 gradient_color(vec2 coords) {
coords = coords + grad_offset;
if ((grad_vec.x < 0.0 && 0.0 <= grad_vec.y) || (0.0 <= grad_vec.x && grad_vec.y < 0.0))
coords.x -= grad_width;
float frac = dot(coords, grad_vec) / dot(grad_vec, grad_vec);
if (grad_vec.y < 0.0)
frac += 1.0;
frac = clamp(frac, 0.0, 1.0);
return mix(color_from, color_to, frac);
}
float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
vec2 center;
float radius;
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
radius = corner_radius.x;
center = vec2(radius, radius);
} else if (size.x - corner_radius.y < coords.x && coords.y < corner_radius.y) {
radius = corner_radius.y;
center = vec2(size.x - radius, radius);
} else if (size.x - corner_radius.z < coords.x && size.y - corner_radius.z < coords.y) {
radius = corner_radius.z;
center = vec2(size.x - radius, size.y - radius);
} else if (coords.x < corner_radius.w && size.y - corner_radius.w < coords.y) {
radius = corner_radius.w;
center = vec2(radius, size.y - radius);
} else {
return 1.0;
}
float dist = distance(coords, center);
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
}
void main() {
vec4 color = gradient_color(niri_v_coords * niri_size);
vec3 coords_geo = input_to_geo * vec3(niri_v_coords, 1.0);
color = color * rounding_alpha(coords_geo.xy, geo_size, outer_radius);
if (border_width > 0.0) {
coords_geo -= vec3(border_width);
vec2 inner_geo_size = geo_size - vec2(border_width * 2.0);
if (0.0 <= coords_geo.x && coords_geo.x <= inner_geo_size.x
&& 0.0 <= coords_geo.y && coords_geo.y <= inner_geo_size.y)
{
vec4 inner_radius = max(outer_radius - vec4(border_width), 0.0);
color = color * (1.0 - rounding_alpha(coords_geo.xy, inner_geo_size, inner_radius));
}
}
color = color * niri_alpha;
#if defined(DEBUG_FLAGS)
if (niri_tint == 1.0)
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
#endif
gl_FragColor = color;
}
@@ -0,0 +1,77 @@
#version 100
//_DEFINES_
#if defined(EXTERNAL)
#extension GL_OES_EGL_image_external : require
#endif
precision mediump 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 vec2 geo_size;
uniform vec4 corner_radius;
uniform mat3 input_to_geo;
float rounding_alpha(vec2 coords, vec2 size) {
vec2 center;
float radius;
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
radius = corner_radius.x;
center = vec2(radius, radius);
} else if (size.x - corner_radius.y < coords.x && coords.y < corner_radius.y) {
radius = corner_radius.y;
center = vec2(size.x - radius, radius);
} else if (size.x - corner_radius.z < coords.x && size.y - corner_radius.z < coords.y) {
radius = corner_radius.z;
center = vec2(size.x - radius, size.y - radius);
} else if (coords.x < corner_radius.w && size.y - corner_radius.w < coords.y) {
radius = corner_radius.w;
center = vec2(radius, size.y - radius);
} else {
return 1.0;
}
float dist = distance(coords, center);
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
}
void main() {
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0);
// Sample the texture.
vec4 color = texture2D(tex, v_coords);
#if defined(NO_ALPHA)
color = vec4(color.rgb, 1.0);
#endif
if (coords_geo.x < 0.0 || 1.0 < coords_geo.x || coords_geo.y < 0.0 || 1.0 < coords_geo.y) {
// Clip outside geometry.
color = vec4(0.0);
} else {
// Apply corner rounding inside geometry.
color = color * rounding_alpha(coords_geo.xy * geo_size, geo_size);
}
// Apply final alpha and tint.
color = 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;
}
@@ -1,35 +0,0 @@
precision mediump float;
uniform float alpha;
#if defined(DEBUG_FLAGS)
uniform float tint;
#endif
uniform vec2 size;
varying vec2 v_coords;
uniform vec4 color_from;
uniform vec4 color_to;
uniform vec2 grad_offset;
uniform float grad_width;
uniform vec2 grad_vec;
void main() {
vec2 coords = v_coords * size + grad_offset;
if ((grad_vec.x < 0.0 && 0.0 <= grad_vec.y) || (0.0 <= grad_vec.x && grad_vec.y < 0.0))
coords.x -= grad_width;
float frac = dot(coords, grad_vec) / dot(grad_vec, grad_vec);
if (grad_vec.y < 0.0)
frac += 1.0;
frac = clamp(frac, 0.0, 1.0);
vec4 out_color = mix(color_from, color_to, frac);
#if defined(DEBUG_FLAGS)
if (tint == 1.0)
out_color = vec4(0.0, 0.3, 0.0, 0.2) + out_color * 0.8;
#endif
gl_FragColor = out_color;
}
+45 -22
View File
@@ -2,35 +2,55 @@ use std::cell::RefCell;
use glam::Mat3;
use smithay::backend::renderer::gles::{
GlesError, GlesPixelProgram, GlesRenderer, Uniform, UniformName, UniformType, UniformValue,
GlesError, GlesRenderer, GlesTexProgram, Uniform, UniformName, UniformType, UniformValue,
};
use super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram;
use super::renderer::NiriRenderer;
use super::shader_element::ShaderProgram;
pub struct Shaders {
pub gradient_border: Option<GlesPixelProgram>,
pub resize: Option<PixelWithTexturesProgram>,
pub custom_resize: RefCell<Option<PixelWithTexturesProgram>>,
pub border: Option<ShaderProgram>,
pub clipped_surface: Option<GlesTexProgram>,
pub resize: Option<ShaderProgram>,
pub custom_resize: RefCell<Option<ShaderProgram>>,
}
impl Shaders {
fn compile(renderer: &mut GlesRenderer) -> Self {
let _span = tracy_client::span!("Shaders::compile");
let gradient_border = renderer
.compile_custom_pixel_shader(
include_str!("gradient_border.frag"),
let border = ShaderProgram::compile(
renderer,
include_str!("border.frag"),
&[
UniformName::new("color_from", UniformType::_4f),
UniformName::new("color_to", UniformType::_4f),
UniformName::new("grad_offset", UniformType::_2f),
UniformName::new("grad_width", UniformType::_1f),
UniformName::new("grad_vec", UniformType::_2f),
UniformName::new("input_to_geo", UniformType::Matrix3x3),
UniformName::new("geo_size", UniformType::_2f),
UniformName::new("outer_radius", UniformType::_4f),
UniformName::new("border_width", UniformType::_1f),
],
&[],
)
.map_err(|err| {
warn!("error compiling border shader: {err:?}");
})
.ok();
let clipped_surface = renderer
.compile_custom_texture_shader(
include_str!("clipped_surface.frag"),
&[
UniformName::new("color_from", UniformType::_4f),
UniformName::new("color_to", UniformType::_4f),
UniformName::new("grad_offset", UniformType::_2f),
UniformName::new("grad_width", UniformType::_1f),
UniformName::new("grad_vec", UniformType::_2f),
UniformName::new("geo_size", UniformType::_2f),
UniformName::new("corner_radius", UniformType::_4f),
UniformName::new("input_to_geo", UniformType::Matrix3x3),
],
)
.map_err(|err| {
warn!("error compiling gradient border shader: {err:?}");
warn!("error compiling clipped surface shader: {err:?}");
})
.ok();
@@ -41,7 +61,8 @@ impl Shaders {
.ok();
Self {
gradient_border,
border,
clipped_surface,
resize,
custom_resize: RefCell::new(None),
}
@@ -56,12 +77,12 @@ impl Shaders {
pub fn replace_custom_resize_program(
&self,
program: Option<PixelWithTexturesProgram>,
) -> Option<PixelWithTexturesProgram> {
program: Option<ShaderProgram>,
) -> Option<ShaderProgram> {
self.custom_resize.replace(program)
}
pub fn resize(&self) -> Option<PixelWithTexturesProgram> {
pub fn resize(&self) -> Option<ShaderProgram> {
self.custom_resize
.borrow()
.clone()
@@ -80,12 +101,12 @@ pub fn init(renderer: &mut GlesRenderer) {
fn compile_resize_program(
renderer: &mut GlesRenderer,
src: &str,
) -> Result<PixelWithTexturesProgram, GlesError> {
let mut program = include_str!("resize-prelude.frag").to_string();
) -> Result<ShaderProgram, GlesError> {
let mut program = include_str!("resize_prelude.frag").to_string();
program.push_str(src);
program.push_str(include_str!("resize-epilogue.frag"));
program.push_str(include_str!("resize_epilogue.frag"));
PixelWithTexturesProgram::compile(
ShaderProgram::compile(
renderer,
&program,
&[
@@ -97,6 +118,8 @@ fn compile_resize_program(
UniformName::new("niri_geo_to_tex_next", UniformType::Matrix3x3),
UniformName::new("niri_progress", UniformType::_1f),
UniformName::new("niri_clamped_progress", UniformType::_1f),
UniformName::new("niri_corner_radius", UniformType::_4f),
UniformName::new("niri_clip_to_geometry", UniformType::_1f),
],
&["niri_tex_prev", "niri_tex_next"],
)
@@ -1,9 +0,0 @@
void main() {
vec3 coords_curr_geo = niri_input_to_curr_geo * vec3(niri_v_coords, 1.0);
vec3 size_curr_geo = vec3(niri_curr_geo_size, 1.0);
vec4 color = resize_color(coords_curr_geo, size_curr_geo);
gl_FragColor = color * niri_alpha;
}
@@ -1,22 +0,0 @@
#version 100
precision mediump float;
varying vec2 niri_v_coords;
uniform vec2 niri_size;
uniform mat3 niri_input_to_curr_geo;
uniform mat3 niri_curr_geo_to_prev_geo;
uniform mat3 niri_curr_geo_to_next_geo;
uniform vec2 niri_curr_geo_size;
uniform sampler2D niri_tex_prev;
uniform mat3 niri_geo_to_tex_prev;
uniform sampler2D niri_tex_next;
uniform mat3 niri_geo_to_tex_next;
uniform float niri_progress;
uniform float niri_clamped_progress;
uniform float niri_alpha;
@@ -0,0 +1,27 @@
void main() {
vec3 coords_curr_geo = niri_input_to_curr_geo * vec3(niri_v_coords, 1.0);
vec3 size_curr_geo = vec3(niri_curr_geo_size, 1.0);
vec4 color = resize_color(coords_curr_geo, size_curr_geo);
if (niri_clip_to_geometry == 1.0) {
if (coords_curr_geo.x < 0.0 || 1.0 < coords_curr_geo.x
|| coords_curr_geo.y < 0.0 || 1.0 < coords_curr_geo.y) {
// Clip outside geometry.
color = vec4(0.0);
} else {
// Apply corner rounding inside geometry.
color = color * niri_rounding_alpha(coords_curr_geo.xy * size_curr_geo.xy, size_curr_geo.xy);
}
}
color = color * niri_alpha;
#if defined(DEBUG_FLAGS)
if (niri_tint == 1.0)
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
#endif
gl_FragColor = color;
}
@@ -0,0 +1,51 @@
precision mediump float;
#if defined(DEBUG_FLAGS)
uniform float niri_tint;
#endif
varying vec2 niri_v_coords;
uniform vec2 niri_size;
uniform mat3 niri_input_to_curr_geo;
uniform mat3 niri_curr_geo_to_prev_geo;
uniform mat3 niri_curr_geo_to_next_geo;
uniform vec2 niri_curr_geo_size;
uniform sampler2D niri_tex_prev;
uniform mat3 niri_geo_to_tex_prev;
uniform sampler2D niri_tex_next;
uniform mat3 niri_geo_to_tex_next;
uniform float niri_progress;
uniform float niri_clamped_progress;
uniform vec4 niri_corner_radius;
uniform float niri_clip_to_geometry;
uniform float niri_alpha;
float niri_rounding_alpha(vec2 coords, vec2 size) {
vec2 center;
float radius;
if (coords.x < niri_corner_radius.x && coords.y < niri_corner_radius.x) {
radius = niri_corner_radius.x;
center = vec2(radius, radius);
} else if (size.x - niri_corner_radius.y < coords.x && coords.y < niri_corner_radius.y) {
radius = niri_corner_radius.y;
center = vec2(size.x - radius, radius);
} else if (size.x - niri_corner_radius.z < coords.x && size.y - niri_corner_radius.z < coords.y) {
radius = niri_corner_radius.z;
center = vec2(size.x - radius, size.y - radius);
} else if (coords.x < niri_corner_radius.w && size.y - niri_corner_radius.w < coords.y) {
radius = niri_corner_radius.w;
center = vec2(radius, size.y - radius);
} else {
return 1.0;
}
float dist = distance(coords, center);
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
}
+15 -1
View File
@@ -1,4 +1,4 @@
use niri_config::{BlockOutFrom, BorderRule, Match, WindowRule};
use niri_config::{BlockOutFrom, BorderRule, CornerRadius, Match, WindowRule};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::wayland::compositor::with_states;
use smithay::wayland::shell::xdg::{
@@ -61,6 +61,12 @@ pub struct ResolvedWindowRules {
/// Extra opacity to draw this window with.
pub opacity: Option<f32>,
/// Corner radius to assume this window has.
pub geometry_corner_radius: Option<CornerRadius>,
/// Whether to clip this window to its geometry, including the corner radius.
pub clip_to_geometry: Option<bool>,
/// Whether to block out this window from certain render targets.
pub block_out_from: Option<BlockOutFrom>,
}
@@ -112,6 +118,8 @@ impl ResolvedWindowRules {
},
draw_border_with_background: None,
opacity: None,
geometry_corner_radius: None,
clip_to_geometry: None,
block_out_from: None,
}
}
@@ -190,6 +198,12 @@ impl ResolvedWindowRules {
if let Some(x) = rule.opacity {
resolved.opacity = Some(x);
}
if let Some(x) = rule.geometry_corner_radius {
resolved.geometry_corner_radius = Some(x);
}
if let Some(x) = rule.clip_to_geometry {
resolved.clip_to_geometry = Some(x);
}
if let Some(x) = rule.block_out_from {
resolved.block_out_from = Some(x);
}
+74
View File
@@ -59,6 +59,9 @@ window-rule {
// Same as focus-ring.
}
geometry-corner-radius 12
clip-to-geometry true
min-width 100
max-width 200
min-height 300
@@ -378,6 +381,77 @@ window-rule {
}
```
#### `geometry-corner-radius`
<sup>Since: 0.1.6</sup>
Set the corner radius of the window.
On its own, this setting will only affect the border and the focus ring—they will round their corners to match the geometry corner radius.
If you'd like to force-round the corners of the window itself, set `clip-to-geometry true` in addition to this setting.
```
window-rule {
geometry-corner-radius 12
}
```
The radius is set in logical pixels, and controls the radius of the window itself, that is, the inner radius of the border:
![](./img/geometry-corner-radius.png)
Instead of one radius, you can set four, for each corner.
The order is the same as in CSS: top-left, top-right, bottom-right, bottom-left.
```
window-rule {
geometry-corner-radius 8 8 0 0
}
```
This way, you can match GTK 3 applications which have square bottom corners:
![](./img/different-corner-radius.png)
#### `clip-to-geometry`
<sup>Since: 0.1.6</sup>
Clips the window to its visual geometry.
This will cut out any client-side window shadows, and also round window corners according to `geometry-corner-radius`.
![](./img/clip-to-geometry.png)
```
window-rule {
clip-to-geometry true
}
```
Enable border, set `geometry-corner-radius` and `clip-to-geometry`, and you've got a classic setup:
![](./img/border-radius-clip.png)
```
prefer-no-csd
layout {
focus-ring {
off
}
border {
width 2
}
}
window-rule {
geometry-corner-radius 12
clip-to-geometry true
}
```
#### Size Overrides
You can amend the window's minimum and maximum size in logical pixels.
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6246413f95e5aa2d63db3fc8bc7726e45af3d20a95ea8f66614f4d62fa7a6624
size 34239
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:88ca1517153755ee64b3fae4e67eced1f83873026ad1ad35c44b85f31fa8171d
size 33591
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e420523fff7891335ef9af5410d946abdd468f1e1dad88e0c189a2b21c9bc7f3
size 11099
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3ed3d553cae8948726ab03db3bf4ad9ef2dc9cf4593e24a96fad9d8820af949d
size 25777