element: Introduce SurfaceRenderElement

This commit is contained in:
Victoria Brekenfeld 2026-03-04 18:16:48 +01:00 committed by Victoria Brekenfeld
parent 8b990e5658
commit 178c6593dc
12 changed files with 254 additions and 213 deletions

View file

@ -0,0 +1,272 @@
// Taken and modified from niri, licensed GPL-3.
use std::borrow::{Borrow, BorrowMut};
use cgmath::{Matrix3, Vector2};
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use smithay::{
backend::renderer::{
ImportAll, Renderer,
element::{
Element, Id, Kind, RenderElement, UnderlyingStorage,
surface::WaylandSurfaceRenderElement,
},
gles::{GlesFrame, GlesRenderer, GlesTexProgram, Uniform, UniformValue},
utils::{CommitCounter, DamageSet, OpaqueRegions},
},
utils::user_data::UserDataMap,
};
use crate::backend::render::element::AsGlowRenderer;
pub static CLIPPING_SHADER: &str = include_str!("../shaders/clipped_surface.frag");
pub struct ClippingShader(pub GlesTexProgram);
impl ClippingShader {
pub fn get<R: AsGlowRenderer>(renderer: &R) -> GlesTexProgram {
Borrow::<GlesRenderer>::borrow(renderer.glow_renderer())
.egl_context()
.user_data()
.get::<ClippingShader>()
.expect("Custom Shaders not initialized")
.0
.clone()
}
}
#[derive(Debug)]
pub struct ClippedSurfaceRenderElement<R: Renderer> {
inner: WaylandSurfaceRenderElement<R>,
program: GlesTexProgram,
radius: [u8; 4],
geometry: Rectangle<f64, Logical>,
uniforms: Vec<Uniform<'static>>,
}
impl<R> ClippedSurfaceRenderElement<R>
where
R: Renderer + ImportAll,
{
pub fn new(
renderer: &mut R,
elem: WaylandSurfaceRenderElement<R>,
scale: Scale<f64>,
geometry: Rectangle<f64, Logical>,
radius: [u8; 4],
) -> Self
where
R: AsGlowRenderer,
{
let elem_geo = elem.geometry(scale);
let geo: Rectangle<i32, Physical> = geometry.to_physical_precise_round(scale);
let buf_size = elem.buffer_size();
let view = elem.view();
let transform = elem.transform();
let transform_matrix = Matrix3::<f32>::from_translation(Vector2::new(0.5, 0.5))
* transform.matrix()
* Matrix3::<f32>::from_translation(-Vector2::new(0.5, 0.5));
let geo_scale = {
let Scale { x, y } = elem_geo.size.to_f64() / geo.size.to_f64();
Matrix3::from_nonuniform_scale(x as f32, y as f32)
};
let geo_translation = {
let offset = (elem_geo.loc - geo.loc).to_f64();
Matrix3::from_translation(Vector2::new(
(offset.x / elem_geo.size.w as f64) as f32,
(offset.y / elem_geo.size.h as f64) as f32,
))
};
let buf_scale = {
let Scale { x, y } = buf_size.to_f64() / view.src.size.to_f64();
Matrix3::from_nonuniform_scale(x as f32, y as f32)
};
let buf_translation = Matrix3::from_translation(Vector2::new(
(view.src.loc.x / buf_size.w as f64) as f32,
(view.src.loc.y / buf_size.h as f64) as f32,
));
let input_to_geo =
transform_matrix * geo_scale * geo_translation * buf_scale * buf_translation;
let uniforms = vec![
Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)),
Uniform::new(
"corner_radius",
[
radius[3] as f32,
radius[1] as f32,
radius[0] as f32,
radius[2] as f32,
],
),
Uniform::new(
"input_to_geo",
UniformValue::Matrix3x3 {
matrices: vec![*AsRef::<[f32; 9]>::as_ref(&input_to_geo)],
transpose: false,
},
),
];
Self {
inner: elem,
program: ClippingShader::get(renderer),
radius,
geometry,
uniforms,
}
}
pub fn will_clip(
elem: &WaylandSurfaceRenderElement<R>,
scale: Scale<f64>,
geometry: Rectangle<f64, Logical>,
radius: [u8; 4],
) -> bool {
let elem_geo = elem.geometry(scale);
let geo = geometry.to_physical_precise_round(scale);
let corners = Self::rounded_corners(geometry, radius);
let corners = corners
.into_iter()
.map(|rect| rect.to_physical_precise_up(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>,
radius: [u8; 4],
) -> [Rectangle<f64, Logical>; 4] {
let top_left = radius[3] as f64;
let top_right = radius[1] as f64;
let bottom_right = radius[0] as f64;
let bottom_left = radius[2] as f64;
[
Rectangle::new(geo.loc, Size::from((top_left, top_left))),
Rectangle::new(
Point::from((geo.loc.x + geo.size.w - top_right, geo.loc.y)),
Size::from((top_right, top_right)),
),
Rectangle::new(
Point::from((
geo.loc.x + geo.size.w - bottom_right,
geo.loc.y + geo.size.h - bottom_right,
)),
Size::from((bottom_right, bottom_right)),
),
Rectangle::new(
Point::from((geo.loc.x, geo.loc.y + geo.size.h - bottom_left)),
Size::from((bottom_left, bottom_left)),
),
]
}
}
impl<R> Element for ClippedSurfaceRenderElement<R>
where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: 'static,
{
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>) -> OpaqueRegions<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.
let corners = Self::rounded_corners(self.geometry, self.radius);
let elem_loc = self.geometry(scale).loc;
let corners = corners.into_iter().map(|rect| {
let mut rect = rect.to_physical_precise_up(scale);
rect.loc -= elem_loc;
rect
});
OpaqueRegions::from_slice(&Rectangle::subtract_rects_many(regions, corners))
}
fn alpha(&self) -> f32 {
self.inner.alpha()
}
fn kind(&self) -> Kind {
self.inner.kind()
}
}
impl<R> RenderElement<R> for ClippedSurfaceRenderElement<R>
where
R: AsGlowRenderer + Renderer + ImportAll,
R::TextureId: 'static,
{
fn draw(
&self,
frame: &mut R::Frame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
opaque_regions: &[Rectangle<i32, Physical>],
cache: Option<&UserDataMap>,
) -> Result<(), R::Error> {
BorrowMut::<GlesFrame>::borrow_mut(<R as AsGlowRenderer>::glow_frame_mut(frame))
.override_default_tex_program(self.program.clone(), self.uniforms.clone());
self.inner
.draw(frame, src, dst, damage, opaque_regions, cache)?;
BorrowMut::<GlesFrame>::borrow_mut(<R as AsGlowRenderer>::glow_frame_mut(frame))
.clear_tex_program_override();
Ok(())
}
fn underlying_storage(&self, _renderer: &mut R) -> Option<UnderlyingStorage<'_>> {
None
}
}

View file

@ -0,0 +1,111 @@
use smithay::{
backend::renderer::{
ImportAll, Renderer,
element::surface::{KindEvaluation, WaylandSurfaceRenderElement},
utils::RendererSurfaceStateUserData,
},
reexports::wayland_server::protocol::wl_surface,
render_elements,
utils::{Logical, Physical, Point, Rectangle, Scale},
wayland::compositor::{self, TraversalAction},
};
use tracing::warn;
use crate::backend::render::{
element::AsGlowRenderer, wayland::clipped_surface::ClippedSurfaceRenderElement,
};
pub mod clipped_surface;
render_elements! {
pub SurfaceRenderElement<R> where R: AsGlowRenderer + ImportAll;
Clipped=ClippedSurfaceRenderElement<R>,
Wayland=WaylandSurfaceRenderElement<R>,
}
pub fn render_elements_from_surface_tree<R, E>(
renderer: &mut R,
main_surface: &wl_surface::WlSurface,
location: impl Into<Point<i32, Physical>>,
geometry: impl Into<Rectangle<f64, Logical>>,
scale: impl Into<Scale<f64>>,
alpha: f32,
should_clip: bool,
radii: [u8; 4],
kind: impl Into<KindEvaluation>,
) -> Vec<E>
where
R: Renderer + ImportAll + AsGlowRenderer,
R::TextureId: Clone + 'static,
E: From<SurfaceRenderElement<R>>,
{
let location = location.into().to_f64();
let geometry = geometry.into().to_f64();
let scale = scale.into();
let kind = kind.into();
let mut surfaces: Vec<E> = Vec::new();
compositor::with_surface_tree_downward(
main_surface,
location,
|_, states, location| {
let mut location = *location;
let data = states.data_map.get::<RendererSurfaceStateUserData>();
if let Some(data) = data {
if let Some(view) = data.lock().unwrap().view() {
location += view.offset.to_f64().to_physical(scale);
TraversalAction::DoChildren(location)
} else {
TraversalAction::SkipChildren
}
} else {
TraversalAction::SkipChildren
}
},
|surface, states, location| {
let mut location = *location;
let kind = kind.eval(states);
let data = states.data_map.get::<RendererSurfaceStateUserData>();
if let Some(data) = data {
let has_view = if let Some(view) = data.lock().unwrap().view() {
location += view.offset.to_f64().to_physical(scale);
true
} else {
false
};
if has_view {
match WaylandSurfaceRenderElement::from_surface(
renderer, surface, states, location, alpha, kind,
) {
Ok(Some(surface)) => {
let elem: SurfaceRenderElement<R> = if radii.iter().any(|r| *r != 0)
&& should_clip
&& ClippedSurfaceRenderElement::will_clip(
&surface, scale, geometry, radii,
) {
ClippedSurfaceRenderElement::new(
renderer, surface, scale, geometry, radii,
)
.into()
} else {
surface.into()
};
surfaces.push(elem.into());
}
Ok(None) => {} // surface is not mapped
Err(err) => {
warn!("Failed to import surface: {}", err);
}
};
}
}
},
|_, _, _| true,
);
surfaces
}