feat: render semantic layer materials

This commit is contained in:
Samuel Onoja committed 2026-09-25 05:13:47 +01:00
1 parent ecefae6f81
commit ae6ec1c5f5
2 files changed
+170 -13

No files matched your search

+4
View File
@@ -122,6 +122,8 @@ pub struct Ferese {
pub(crate) window_borders: HashMap<WindowId, crate::winit::WindowBorderBuffers>, pub(crate) window_borders: HashMap<WindowId, crate::winit::WindowBorderBuffers>,
pub(crate) window_shadows: HashMap<WindowId, crate::winit::WindowShadowBuffers>, pub(crate) window_shadows: HashMap<WindowId, crate::winit::WindowShadowBuffers>,
pub(crate) rounded_clip_programs: HashMap<ErasedContextId, crate::winit::RoundedClipPrograms>, pub(crate) rounded_clip_programs: HashMap<ErasedContextId, crate::winit::RoundedClipPrograms>,
pub(crate) material_programs: HashMap<ErasedContextId, crate::winit::MaterialProgram>,
pub(crate) material_buffers: HashMap<WlSurface, crate::winit::MaterialBuffers>,
closing_windows: HashMap<WindowId, ClosingAnimation>, closing_windows: HashMap<WindowId, ClosingAnimation>,
viewport_animations: HashMap<WorkspaceId, AnimatedValue>, viewport_animations: HashMap<WorkspaceId, AnimatedValue>,
scrolling_world_x: HashMap<WindowId, (WorkspaceId, AnimatedValue)>, scrolling_world_x: HashMap<WindowId, (WorkspaceId, AnimatedValue)>,
@@ -434,6 +436,8 @@ impl Ferese {
window_borders: HashMap::new(), window_borders: HashMap::new(),
window_shadows: HashMap::new(), window_shadows: HashMap::new(),
rounded_clip_programs: HashMap::new(), rounded_clip_programs: HashMap::new(),
material_programs: HashMap::new(),
material_buffers: HashMap::new(),
closing_windows: HashMap::new(), closing_windows: HashMap::new(),
viewport_animations: HashMap::new(), viewport_animations: HashMap::new(),
scrolling_world_x: HashMap::new(), scrolling_world_x: HashMap::new(),
+166 -13
View File
@@ -40,6 +40,7 @@ use smithay::{
output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
reexports::calloop::EventLoop, reexports::calloop::EventLoop,
reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind, reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind,
reexports::wayland_server::Resource,
utils::{ utils::{
Buffer, Clock, Logical, Monotonic, Physical, Point, Rectangle, Scale as RenderScale, Buffer, Clock, Logical, Monotonic, Physical, Point, Rectangle, Scale as RenderScale,
Transform, Transform,
@@ -210,6 +211,26 @@ void main() {
} }
"#; "#;
const MATERIAL_SHADER: &str = r#"
precision mediump float;
uniform float alpha;
uniform vec4 tint;
uniform float noise_amount;
varying vec2 v_coords;
float random(vec2 point) {
return fract(sin(dot(point, vec2(12.9898, 78.233))) * 43758.5453);
}
void main() {
float grain = (random(gl_FragCoord.xy) - 0.5) * noise_amount;
vec3 color = clamp(tint.rgb + vec3(grain), 0.0, 1.0);
float opacity = tint.a * alpha;
gl_FragColor = vec4(color * opacity, opacity);
}
"#;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct RoundedClipPrograms { pub(crate) struct RoundedClipPrograms {
texture: GlesTexProgram, texture: GlesTexProgram,
@@ -217,6 +238,29 @@ pub(crate) struct RoundedClipPrograms {
shadow: GlesPixelProgram, shadow: GlesPixelProgram,
} }
#[derive(Clone, Debug)]
pub(crate) struct MaterialProgram(GlesPixelProgram);
#[derive(Clone, Debug, PartialEq)]
struct MaterialParameters {
geometry: Rectangle<i32, Logical>,
tint: [f32; 4],
noise: f32,
generation: u64,
opaque: bool,
}
#[derive(Debug)]
struct CachedMaterial {
element: PixelShaderElement,
parameters: MaterialParameters,
}
#[derive(Debug, Default)]
pub(crate) struct MaterialBuffers {
contexts: HashMap<ErasedContextId, CachedMaterial>,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
struct BorderParameters { struct BorderParameters {
geometry: Rectangle<i32, Logical>, geometry: Rectangle<i32, Logical>,
@@ -569,6 +613,7 @@ fn output_elements(
Vec::new() Vec::new()
}; };
elements.extend(layer_elements( elements.extend(layer_elements(
state,
renderer, renderer,
output, output,
&[Layer::Overlay, Layer::Top], &[Layer::Overlay, Layer::Top],
@@ -680,6 +725,7 @@ fn output_elements(
} }
} }
elements.extend(layer_elements( elements.extend(layer_elements(
state,
renderer, renderer,
output, output,
&[Layer::Bottom, Layer::Background], &[Layer::Bottom, Layer::Background],
@@ -1081,6 +1127,7 @@ fn layer_surfaces(output: &Output) -> Vec<LayerSurface> {
} }
fn layer_elements( fn layer_elements(
state: &mut Ferese,
renderer: &mut GlesRenderer, renderer: &mut GlesRenderer,
output: &Output, output: &Output,
requested_layers: &[Layer], requested_layers: &[Layer],
@@ -1098,33 +1145,139 @@ fn layer_elements(
.flat_map(|requested| { .flat_map(|requested| {
map.layers_on(*requested).rev().filter_map(|layer| { map.layers_on(*requested).rev().filter_map(|layer| {
map.layer_geometry(layer) map.layer_geometry(layer)
.map(|geometry| (geometry.loc, layer.clone())) .map(|geometry| (geometry, layer.clone()))
}) })
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
layers state
.into_iter() .material_buffers
.flat_map(|(location, layer)| { .retain(|surface, _| surface.is_alive());
let mut elements = Vec::new();
for (geometry, layer) in layers {
elements.extend(
AsRenderElements::<GlesRenderer>::render_elements::< AsRenderElements::<GlesRenderer>::render_elements::<
WaylandSurfaceRenderElement<GlesRenderer>, WaylandSurfaceRenderElement<GlesRenderer>,
>( >(
&layer, &layer,
renderer, renderer,
location.to_physical_precise_round(scale), geometry.loc.to_physical_precise_round(scale),
scale.into(), scale.into(),
1.0, 1.0,
) )
}) .into_iter()
.filter_map(|element| { .filter_map(|element| {
let origin = Point::<i32, Physical>::default(); let origin = Point::<i32, Physical>::default();
let element = RescaleRenderElement::from_element(element, origin, 1.0); let element = RescaleRenderElement::from_element(element, origin, 1.0);
let element = RelocateRenderElement::from_element(element, origin, Relocate::Relative); let element =
RelocateRenderElement::from_element(element, origin, Relocate::Relative);
CropRenderElement::from_element(element, scale, output_crop).map(Into::into) CropRenderElement::from_element(element, scale, output_crop).map(Into::into)
}) }),
.collect() );
if let Some(material) = material_element(state, renderer, &layer, geometry) {
elements.push(material.into());
}
}
elements
}
fn material_element(
state: &mut Ferese,
renderer: &mut GlesRenderer,
layer: &LayerSurface,
geometry: Rectangle<i32, Logical>,
) -> Option<PixelShaderElement> {
let surface = layer.wl_surface();
let (role, generation) = crate::effects::surface_role(surface)?;
let material = crate::effects::resolve_material(role, state.theme_settings.material_style);
let program = material_program(state, renderer)?;
let tint = match material.style {
crate::config::MaterialStyle::Solid => [0.067, 0.094, 0.129, material.opacity],
crate::config::MaterialStyle::Glass | crate::config::MaterialStyle::Translucent => {
[0.067, 0.094, 0.129, material.opacity]
}
};
let parameters = MaterialParameters {
geometry,
tint,
noise: material.noise,
generation,
opaque: material.opacity == 1.0,
};
let context = renderer.context_id().erased();
let buffers = state.material_buffers.entry(surface.clone()).or_default();
if !buffers.contexts.contains_key(&context) {
let element = PixelShaderElement::new(
program.0,
geometry,
parameters
.opaque
.then(|| vec![Rectangle::from_size(geometry.size)]),
1.0,
material_uniforms(&parameters),
RenderElementKind::Unspecified,
);
buffers.contexts.insert(
context.clone(),
CachedMaterial {
element,
parameters: parameters.clone(),
},
);
}
let cached = buffers.contexts.get_mut(&context)?;
if cached.parameters != parameters {
if cached.parameters.geometry != parameters.geometry
|| cached.parameters.opaque != parameters.opaque
{
let opaque = parameters
.opaque
.then(|| vec![Rectangle::from_size(parameters.geometry.size)]);
cached.element.resize(parameters.geometry, opaque);
}
cached
.element
.update_uniforms(material_uniforms(&parameters));
cached.parameters = parameters;
}
Some(cached.element.clone())
}
fn material_program(state: &mut Ferese, renderer: &mut GlesRenderer) -> Option<MaterialProgram> {
let context = renderer.context_id().erased();
if let Some(program) = state.material_programs.get(&context) {
return Some(program.clone());
}
let uniforms = [
UniformName::new("tint", UniformType::_4f),
UniformName::new("noise_amount", UniformType::_1f),
];
match renderer.compile_custom_pixel_shader(MATERIAL_SHADER, &uniforms) {
Ok(program) => {
let program = MaterialProgram(program);
state.material_programs.insert(context, program.clone());
Some(program)
}
Err(error) => {
tracing::error!(%error, "failed to compile semantic material shader");
None
}
}
}
fn material_uniforms(parameters: &MaterialParameters) -> Vec<Uniform<'static>> {
vec![
Uniform::new("tint", parameters.tint).into_owned(),
Uniform::new("noise_amount", parameters.noise).into_owned(),
]
} }
fn cursor_elements( fn cursor_elements(