feat: render semantic glass backdrops
This commit is contained in:
1 parent
b3c5821e3b
commit
389581e763
4 files changed
+409
-29
No files matched your search
@@ -31,7 +31,6 @@ pub(crate) enum SemanticRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
#[allow(dead_code)] // Used by the material renderer in the next M7 slice.
|
|
||||||
pub(crate) struct ResolvedMaterial {
|
pub(crate) struct ResolvedMaterial {
|
||||||
pub style: MaterialStyle,
|
pub style: MaterialStyle,
|
||||||
pub opacity: f32,
|
pub opacity: f32,
|
||||||
@@ -41,7 +40,6 @@ pub(crate) struct ResolvedMaterial {
|
|||||||
pub noise: f32,
|
pub noise: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)] // Used by the material renderer in the next M7 slice.
|
|
||||||
pub(crate) fn resolve_material(role: SemanticRole, style: MaterialStyle) -> ResolvedMaterial {
|
pub(crate) fn resolve_material(role: SemanticRole, style: MaterialStyle) -> ResolvedMaterial {
|
||||||
let (opacity, blur, saturation, brightness, noise) = match role {
|
let (opacity, blur, saturation, brightness, noise) = match role {
|
||||||
SemanticRole::Panel => (0.78, 24.0, 1.08, 1.02, 0.012),
|
SemanticRole::Panel => (0.78, 24.0, 1.08, 1.02, 0.012),
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ pub struct Ferese {
|
|||||||
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_programs: HashMap<ErasedContextId, crate::winit::MaterialProgram>,
|
||||||
|
pub(crate) backdrop_programs: HashMap<ErasedContextId, crate::winit::BackdropProgram>,
|
||||||
pub(crate) material_buffers: HashMap<WlSurface, crate::winit::MaterialBuffers>,
|
pub(crate) material_buffers: HashMap<WlSurface, crate::winit::MaterialBuffers>,
|
||||||
material_scene_generation: u64,
|
material_scene_generation: u64,
|
||||||
closing_windows: HashMap<WindowId, ClosingAnimation>,
|
closing_windows: HashMap<WindowId, ClosingAnimation>,
|
||||||
@@ -438,6 +439,7 @@ impl Ferese {
|
|||||||
window_shadows: HashMap::new(),
|
window_shadows: HashMap::new(),
|
||||||
rounded_clip_programs: HashMap::new(),
|
rounded_clip_programs: HashMap::new(),
|
||||||
material_programs: HashMap::new(),
|
material_programs: HashMap::new(),
|
||||||
|
backdrop_programs: HashMap::new(),
|
||||||
material_buffers: HashMap::new(),
|
material_buffers: HashMap::new(),
|
||||||
material_scene_generation: 0,
|
material_scene_generation: 0,
|
||||||
closing_windows: HashMap::new(),
|
closing_windows: HashMap::new(),
|
||||||
|
|||||||
+400
-27
@@ -6,8 +6,9 @@ use std::{
|
|||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
|
allocator::Fourcc,
|
||||||
renderer::{
|
renderer::{
|
||||||
Color32F, ErasedContextId, Frame, ImportDma, Renderer,
|
Color32F, ErasedContextId, Frame, ImportDma, Offscreen, Renderer, Texture,
|
||||||
damage::OutputDamageTracker,
|
damage::OutputDamageTracker,
|
||||||
element::{
|
element::{
|
||||||
AsRenderElements, Element, Id, Kind as RenderElementKind, RenderElement,
|
AsRenderElements, Element, Id, Kind as RenderElementKind, RenderElement,
|
||||||
@@ -24,8 +25,8 @@ use smithay::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gles::{
|
gles::{
|
||||||
GlesError, GlesFrame, GlesPixelProgram, GlesRenderer, GlesTexProgram, Uniform,
|
GlesError, GlesFrame, GlesPixelProgram, GlesRenderer, GlesTexProgram, GlesTexture,
|
||||||
UniformName, UniformType, element::PixelShaderElement,
|
Uniform, UniformName, UniformType, element::PixelShaderElement,
|
||||||
},
|
},
|
||||||
utils::{CommitCounter, DamageSet, OpaqueRegions, draw_render_elements},
|
utils::{CommitCounter, DamageSet, OpaqueRegions, draw_render_elements},
|
||||||
},
|
},
|
||||||
@@ -71,6 +72,7 @@ render_elements! {
|
|||||||
Surface=SurfaceRenderElement,
|
Surface=SurfaceRenderElement,
|
||||||
Memory=MemoryRenderElement,
|
Memory=MemoryRenderElement,
|
||||||
Border=PixelShaderElement,
|
Border=PixelShaderElement,
|
||||||
|
Backdrop=BackdropRenderElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
render_elements! {
|
render_elements! {
|
||||||
@@ -231,6 +233,62 @@ void main() {
|
|||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
const BACKDROP_SHADER: &str = r#"#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;
|
||||||
|
uniform vec4 visible_rect;
|
||||||
|
uniform vec2 texture_size;
|
||||||
|
uniform float blur_radius;
|
||||||
|
uniform float saturation;
|
||||||
|
uniform float brightness;
|
||||||
|
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() {
|
||||||
|
vec2 point = gl_FragCoord.xy;
|
||||||
|
vec2 visible_max = visible_rect.xy + visible_rect.zw;
|
||||||
|
if (point.x < visible_rect.x || point.y < visible_rect.y
|
||||||
|
|| point.x >= visible_max.x || point.y >= visible_max.y) {
|
||||||
|
gl_FragColor = vec4(0.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 step_size = vec2(blur_radius) / max(texture_size, vec2(1.0));
|
||||||
|
vec4 color = texture2D(tex, v_coords) * 0.20;
|
||||||
|
color += texture2D(tex, v_coords + vec2(step_size.x, 0.0)) * 0.12;
|
||||||
|
color += texture2D(tex, v_coords - vec2(step_size.x, 0.0)) * 0.12;
|
||||||
|
color += texture2D(tex, v_coords + vec2(0.0, step_size.y)) * 0.12;
|
||||||
|
color += texture2D(tex, v_coords - vec2(0.0, step_size.y)) * 0.12;
|
||||||
|
color += texture2D(tex, v_coords + step_size) * 0.08;
|
||||||
|
color += texture2D(tex, v_coords - step_size) * 0.08;
|
||||||
|
color += texture2D(tex, v_coords + vec2(step_size.x, -step_size.y)) * 0.08;
|
||||||
|
color += texture2D(tex, v_coords + vec2(-step_size.x, step_size.y)) * 0.08;
|
||||||
|
|
||||||
|
float luma = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||||
|
vec3 adjusted = mix(vec3(luma), color.rgb, saturation) * brightness;
|
||||||
|
adjusted = mix(adjusted, tint.rgb, tint.a);
|
||||||
|
adjusted += vec3((random(point) - 0.5) * noise_amount);
|
||||||
|
gl_FragColor = vec4(clamp(adjusted, 0.0, 1.0) * alpha, alpha);
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct RoundedClipPrograms {
|
pub(crate) struct RoundedClipPrograms {
|
||||||
texture: GlesTexProgram,
|
texture: GlesTexProgram,
|
||||||
@@ -241,6 +299,9 @@ pub(crate) struct RoundedClipPrograms {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct MaterialProgram(GlesPixelProgram);
|
pub(crate) struct MaterialProgram(GlesPixelProgram);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct BackdropProgram(GlesTexProgram);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
struct MaterialParameters {
|
struct MaterialParameters {
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Logical>,
|
||||||
@@ -249,19 +310,162 @@ struct MaterialParameters {
|
|||||||
generation: u64,
|
generation: u64,
|
||||||
scene_generation: u64,
|
scene_generation: u64,
|
||||||
opaque: bool,
|
opaque: bool,
|
||||||
|
blur: f32,
|
||||||
|
saturation: f32,
|
||||||
|
brightness: f32,
|
||||||
|
sample_geometry: Rectangle<i32, Logical>,
|
||||||
|
sample_physical: Rectangle<i32, Physical>,
|
||||||
|
visible_framebuffer: [f32; 4],
|
||||||
|
sample_framebuffer: [f32; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct CachedMaterial {
|
struct CachedMaterial {
|
||||||
element: PixelShaderElement,
|
element: CachedMaterialElement,
|
||||||
parameters: MaterialParameters,
|
parameters: MaterialParameters,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum CachedMaterialElement {
|
||||||
|
Tint(PixelShaderElement),
|
||||||
|
Backdrop(BackdropRenderElement),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct MaterialBuffers {
|
pub(crate) struct MaterialBuffers {
|
||||||
contexts: HashMap<ErasedContextId, CachedMaterial>,
|
contexts: HashMap<ErasedContextId, CachedMaterial>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct BackdropRenderElement {
|
||||||
|
texture: GlesTexture,
|
||||||
|
program: GlesTexProgram,
|
||||||
|
id: Id,
|
||||||
|
commit: CommitCounter,
|
||||||
|
geometry: Rectangle<i32, Logical>,
|
||||||
|
capture_rect: [i32; 4],
|
||||||
|
uniforms: Vec<Uniform<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackdropRenderElement {
|
||||||
|
fn new(texture: GlesTexture, program: GlesTexProgram, parameters: &MaterialParameters) -> Self {
|
||||||
|
Self {
|
||||||
|
texture,
|
||||||
|
program,
|
||||||
|
id: Id::new(),
|
||||||
|
commit: CommitCounter::default(),
|
||||||
|
geometry: parameters.sample_geometry,
|
||||||
|
capture_rect: framebuffer_capture_rect(parameters.sample_framebuffer),
|
||||||
|
uniforms: backdrop_uniforms(parameters),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, parameters: &MaterialParameters) {
|
||||||
|
self.geometry = parameters.sample_geometry;
|
||||||
|
self.capture_rect = framebuffer_capture_rect(parameters.sample_framebuffer);
|
||||||
|
self.uniforms = backdrop_uniforms(parameters);
|
||||||
|
self.commit.increment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for BackdropRenderElement {
|
||||||
|
fn id(&self) -> &Id {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_commit(&self) -> CommitCounter {
|
||||||
|
self.commit
|
||||||
|
}
|
||||||
|
|
||||||
|
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||||
|
Rectangle::from_size(self.texture.size().to_f64())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn geometry(&self, scale: RenderScale<f64>) -> Rectangle<i32, Physical> {
|
||||||
|
self.geometry.to_physical_precise_round(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn damage_since(
|
||||||
|
&self,
|
||||||
|
scale: RenderScale<f64>,
|
||||||
|
commit: Option<CommitCounter>,
|
||||||
|
) -> DamageSet<i32, Physical> {
|
||||||
|
if commit == Some(self.commit) {
|
||||||
|
DamageSet::default()
|
||||||
|
} else {
|
||||||
|
DamageSet::from_slice(&[Rectangle::from_size(self.geometry(scale).size)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opaque_regions(&self, _scale: RenderScale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||||
|
// Backdrop dependencies must remain visible to damage tracking even
|
||||||
|
// though the shader produces opaque pixels inside the visible region.
|
||||||
|
OpaqueRegions::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alpha(&self) -> f32 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> RenderElementKind {
|
||||||
|
RenderElementKind::Unspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderElement<GlesRenderer> for BackdropRenderElement {
|
||||||
|
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> {
|
||||||
|
let texture = self.texture.tex_id();
|
||||||
|
let [x, y, width, height] = self.capture_rect;
|
||||||
|
frame.with_context(|gl| unsafe {
|
||||||
|
gl.BindTexture(smithay::backend::renderer::gles::ffi::TEXTURE_2D, texture);
|
||||||
|
gl.TexParameteri(
|
||||||
|
smithay::backend::renderer::gles::ffi::TEXTURE_2D,
|
||||||
|
smithay::backend::renderer::gles::ffi::TEXTURE_WRAP_S,
|
||||||
|
smithay::backend::renderer::gles::ffi::CLAMP_TO_EDGE as i32,
|
||||||
|
);
|
||||||
|
gl.TexParameteri(
|
||||||
|
smithay::backend::renderer::gles::ffi::TEXTURE_2D,
|
||||||
|
smithay::backend::renderer::gles::ffi::TEXTURE_WRAP_T,
|
||||||
|
smithay::backend::renderer::gles::ffi::CLAMP_TO_EDGE as i32,
|
||||||
|
);
|
||||||
|
gl.CopyTexSubImage2D(
|
||||||
|
smithay::backend::renderer::gles::ffi::TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
gl.BindTexture(smithay::backend::renderer::gles::ffi::TEXTURE_2D, 0);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
frame.render_texture_from_to(
|
||||||
|
&self.texture,
|
||||||
|
src,
|
||||||
|
dst,
|
||||||
|
damage,
|
||||||
|
opaque_regions,
|
||||||
|
Transform::Normal,
|
||||||
|
1.0,
|
||||||
|
Some(&self.program),
|
||||||
|
&self.uniforms,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage<'_>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
struct BorderParameters {
|
struct BorderParameters {
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Logical>,
|
||||||
@@ -1179,8 +1383,8 @@ fn layer_elements(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(material) = material_element(state, renderer, &layer, geometry) {
|
if let Some(material) = material_element(state, renderer, output, &layer, geometry) {
|
||||||
elements.push(material.into());
|
elements.push(material);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1190,31 +1394,83 @@ fn layer_elements(
|
|||||||
fn material_element(
|
fn material_element(
|
||||||
state: &mut Ferese,
|
state: &mut Ferese,
|
||||||
renderer: &mut GlesRenderer,
|
renderer: &mut GlesRenderer,
|
||||||
|
output: &Output,
|
||||||
layer: &LayerSurface,
|
layer: &LayerSurface,
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Logical>,
|
||||||
) -> Option<PixelShaderElement> {
|
) -> Option<AnimatedWindowRenderElement> {
|
||||||
let surface = layer.wl_surface();
|
let surface = layer.wl_surface();
|
||||||
let (role, generation) = crate::effects::surface_role(surface)?;
|
let (role, generation) = crate::effects::surface_role(surface)?;
|
||||||
let material = crate::effects::resolve_material(role, state.theme_settings.material_style);
|
let material = crate::effects::resolve_material(role, state.theme_settings.material_style);
|
||||||
let program = material_program(state, renderer)?;
|
let mode = output.current_mode()?;
|
||||||
let tint = match material.style {
|
let scale = output.current_scale().fractional_scale();
|
||||||
crate::config::MaterialStyle::Solid => [0.067, 0.094, 0.129, material.opacity],
|
let output_logical = smithay::utils::Size::<i32, Logical>::from((
|
||||||
crate::config::MaterialStyle::Glass | crate::config::MaterialStyle::Translucent => {
|
(f64::from(mode.size.w) / scale).ceil() as i32,
|
||||||
[0.067, 0.094, 0.129, material.opacity]
|
(f64::from(mode.size.h) / scale).ceil() as i32,
|
||||||
}
|
));
|
||||||
};
|
let sample_geometry =
|
||||||
|
expanded_blur_region(geometry, material.blur.ceil() as i32, output_logical);
|
||||||
|
let sample_physical = sample_geometry.to_physical_precise_round(scale);
|
||||||
|
let transform = output.current_transform().invert();
|
||||||
|
let tint = [0.067, 0.094, 0.129, material.opacity];
|
||||||
let parameters = MaterialParameters {
|
let parameters = MaterialParameters {
|
||||||
geometry,
|
geometry,
|
||||||
tint,
|
tint,
|
||||||
noise: material.noise,
|
noise: material.noise,
|
||||||
generation,
|
generation,
|
||||||
scene_generation: state.material_scene_generation(),
|
scene_generation: matches!(material.style, crate::config::MaterialStyle::Glass)
|
||||||
|
.then(|| state.material_scene_generation())
|
||||||
|
.unwrap_or(0),
|
||||||
opaque: material.opacity == 1.0,
|
opaque: material.opacity == 1.0,
|
||||||
|
blur: material.blur * scale as f32,
|
||||||
|
saturation: material.saturation,
|
||||||
|
brightness: material.brightness,
|
||||||
|
sample_geometry,
|
||||||
|
sample_physical,
|
||||||
|
visible_framebuffer: framebuffer_clip_rect(
|
||||||
|
geometry.to_physical_precise_round(scale),
|
||||||
|
mode.size,
|
||||||
|
transform,
|
||||||
|
),
|
||||||
|
sample_framebuffer: framebuffer_clip_rect(sample_physical, mode.size, transform),
|
||||||
};
|
};
|
||||||
let context = renderer.context_id().erased();
|
let context = renderer.context_id().erased();
|
||||||
let buffers = state.material_buffers.entry(surface.clone()).or_default();
|
let needs_replacement = state
|
||||||
if !buffers.contexts.contains_key(&context) {
|
.material_buffers
|
||||||
let element = PixelShaderElement::new(
|
.get(surface)
|
||||||
|
.and_then(|buffers| buffers.contexts.get(&context))
|
||||||
|
.is_none_or(|cached| match (&cached.element, material.style) {
|
||||||
|
(CachedMaterialElement::Backdrop(_), crate::config::MaterialStyle::Glass) => {
|
||||||
|
cached.parameters.sample_physical.size != parameters.sample_physical.size
|
||||||
|
}
|
||||||
|
(
|
||||||
|
CachedMaterialElement::Tint(_),
|
||||||
|
crate::config::MaterialStyle::Translucent | crate::config::MaterialStyle::Solid,
|
||||||
|
) => false,
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
let mut replacement = needs_replacement
|
||||||
|
.then(|| match material.style {
|
||||||
|
crate::config::MaterialStyle::Glass => {
|
||||||
|
let program = backdrop_program(state, renderer)?;
|
||||||
|
let texture_size = smithay::utils::Size::<i32, Buffer>::from((
|
||||||
|
sample_physical.size.w,
|
||||||
|
sample_physical.size.h,
|
||||||
|
));
|
||||||
|
let texture = Offscreen::<GlesTexture>::create_buffer(
|
||||||
|
renderer,
|
||||||
|
Fourcc::Abgr8888,
|
||||||
|
texture_size,
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
Some(CachedMaterialElement::Backdrop(BackdropRenderElement::new(
|
||||||
|
texture,
|
||||||
|
program.0,
|
||||||
|
¶meters,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
crate::config::MaterialStyle::Translucent | crate::config::MaterialStyle::Solid => {
|
||||||
|
let program = material_program(state, renderer)?;
|
||||||
|
Some(CachedMaterialElement::Tint(PixelShaderElement::new(
|
||||||
program.0,
|
program.0,
|
||||||
geometry,
|
geometry,
|
||||||
parameters
|
parameters
|
||||||
@@ -1223,11 +1479,16 @@ fn material_element(
|
|||||||
1.0,
|
1.0,
|
||||||
material_uniforms(¶meters),
|
material_uniforms(¶meters),
|
||||||
RenderElementKind::Unspecified,
|
RenderElementKind::Unspecified,
|
||||||
);
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
let buffers = state.material_buffers.entry(surface.clone()).or_default();
|
||||||
|
if !buffers.contexts.contains_key(&context) {
|
||||||
buffers.contexts.insert(
|
buffers.contexts.insert(
|
||||||
context.clone(),
|
context.clone(),
|
||||||
CachedMaterial {
|
CachedMaterial {
|
||||||
element,
|
element: replacement.take()?,
|
||||||
parameters: parameters.clone(),
|
parameters: parameters.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1235,21 +1496,32 @@ fn material_element(
|
|||||||
|
|
||||||
let cached = buffers.contexts.get_mut(&context)?;
|
let cached = buffers.contexts.get_mut(&context)?;
|
||||||
if cached.parameters != parameters {
|
if cached.parameters != parameters {
|
||||||
|
match &mut cached.element {
|
||||||
|
CachedMaterialElement::Tint(element) => {
|
||||||
if cached.parameters.geometry != parameters.geometry
|
if cached.parameters.geometry != parameters.geometry
|
||||||
|| cached.parameters.opaque != parameters.opaque
|
|| cached.parameters.opaque != parameters.opaque
|
||||||
{
|
{
|
||||||
let opaque = parameters
|
let opaque = parameters
|
||||||
.opaque
|
.opaque
|
||||||
.then(|| vec![Rectangle::from_size(parameters.geometry.size)]);
|
.then(|| vec![Rectangle::from_size(parameters.geometry.size)]);
|
||||||
cached.element.resize(parameters.geometry, opaque);
|
element.resize(parameters.geometry, opaque);
|
||||||
|
}
|
||||||
|
element.update_uniforms(material_uniforms(¶meters));
|
||||||
|
}
|
||||||
|
CachedMaterialElement::Backdrop(element)
|
||||||
|
if cached.parameters.sample_physical.size == parameters.sample_physical.size =>
|
||||||
|
{
|
||||||
|
element.update(¶meters);
|
||||||
|
}
|
||||||
|
_ => cached.element = replacement.take()?,
|
||||||
}
|
}
|
||||||
cached
|
|
||||||
.element
|
|
||||||
.update_uniforms(material_uniforms(¶meters));
|
|
||||||
cached.parameters = parameters;
|
cached.parameters = parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(cached.element.clone())
|
Some(match &cached.element {
|
||||||
|
CachedMaterialElement::Tint(element) => element.clone().into(),
|
||||||
|
CachedMaterialElement::Backdrop(element) => element.clone().into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn material_program(state: &mut Ferese, renderer: &mut GlesRenderer) -> Option<MaterialProgram> {
|
fn material_program(state: &mut Ferese, renderer: &mut GlesRenderer) -> Option<MaterialProgram> {
|
||||||
@@ -1275,6 +1547,34 @@ fn material_program(state: &mut Ferese, renderer: &mut GlesRenderer) -> Option<M
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn backdrop_program(state: &mut Ferese, renderer: &mut GlesRenderer) -> Option<BackdropProgram> {
|
||||||
|
let context = renderer.context_id().erased();
|
||||||
|
if let Some(program) = state.backdrop_programs.get(&context) {
|
||||||
|
return Some(program.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let uniforms = [
|
||||||
|
UniformName::new("visible_rect", UniformType::_4f),
|
||||||
|
UniformName::new("texture_size", UniformType::_2f),
|
||||||
|
UniformName::new("blur_radius", UniformType::_1f),
|
||||||
|
UniformName::new("saturation", UniformType::_1f),
|
||||||
|
UniformName::new("brightness", UniformType::_1f),
|
||||||
|
UniformName::new("tint", UniformType::_4f),
|
||||||
|
UniformName::new("noise_amount", UniformType::_1f),
|
||||||
|
];
|
||||||
|
match renderer.compile_custom_texture_shader(BACKDROP_SHADER, &uniforms) {
|
||||||
|
Ok(program) => {
|
||||||
|
let program = BackdropProgram(program);
|
||||||
|
state.backdrop_programs.insert(context, program.clone());
|
||||||
|
Some(program)
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(%error, "failed to compile backdrop material shader");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn material_uniforms(parameters: &MaterialParameters) -> Vec<Uniform<'static>> {
|
fn material_uniforms(parameters: &MaterialParameters) -> Vec<Uniform<'static>> {
|
||||||
vec![
|
vec![
|
||||||
Uniform::new("tint", parameters.tint).into_owned(),
|
Uniform::new("tint", parameters.tint).into_owned(),
|
||||||
@@ -1282,6 +1582,51 @@ fn material_uniforms(parameters: &MaterialParameters) -> Vec<Uniform<'static>> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn backdrop_uniforms(parameters: &MaterialParameters) -> Vec<Uniform<'static>> {
|
||||||
|
vec![
|
||||||
|
Uniform::new("visible_rect", parameters.visible_framebuffer).into_owned(),
|
||||||
|
Uniform::new(
|
||||||
|
"texture_size",
|
||||||
|
[
|
||||||
|
parameters.sample_physical.size.w as f32,
|
||||||
|
parameters.sample_physical.size.h as f32,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.into_owned(),
|
||||||
|
Uniform::new("blur_radius", parameters.blur).into_owned(),
|
||||||
|
Uniform::new("saturation", parameters.saturation).into_owned(),
|
||||||
|
Uniform::new("brightness", parameters.brightness).into_owned(),
|
||||||
|
Uniform::new("tint", parameters.tint).into_owned(),
|
||||||
|
Uniform::new("noise_amount", parameters.noise).into_owned(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn framebuffer_capture_rect(rect: [f32; 4]) -> [i32; 4] {
|
||||||
|
[
|
||||||
|
rect[0].round() as i32,
|
||||||
|
rect[1].round() as i32,
|
||||||
|
rect[2].round() as i32,
|
||||||
|
rect[3].round() as i32,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expanded_blur_region(
|
||||||
|
visible: Rectangle<i32, Logical>,
|
||||||
|
radius: i32,
|
||||||
|
output_size: smithay::utils::Size<i32, Logical>,
|
||||||
|
) -> Rectangle<i32, Logical> {
|
||||||
|
let radius = radius.max(0);
|
||||||
|
let left = (visible.loc.x - radius).max(0);
|
||||||
|
let top = (visible.loc.y - radius).max(0);
|
||||||
|
let right = (visible.loc.x + visible.size.w + radius).min(output_size.w);
|
||||||
|
let bottom = (visible.loc.y + visible.size.h + radius).min(output_size.h);
|
||||||
|
|
||||||
|
Rectangle::new(
|
||||||
|
(left, top).into(),
|
||||||
|
((right - left).max(1), (bottom - top).max(1)).into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn cursor_elements(
|
fn cursor_elements(
|
||||||
state: &Ferese,
|
state: &Ferese,
|
||||||
renderer: &mut GlesRenderer,
|
renderer: &mut GlesRenderer,
|
||||||
@@ -1377,8 +1722,8 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
color_with_alpha, framebuffer_clip_rect, normalized_scale, rounded_visual_rect,
|
color_with_alpha, expanded_blur_region, framebuffer_capture_rect, framebuffer_clip_rect,
|
||||||
scaled_visual_rect, shadow_bounds,
|
normalized_scale, rounded_visual_rect, scaled_visual_rect, shadow_bounds,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -1455,6 +1800,34 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blur_sampling_expands_around_the_visible_surface() {
|
||||||
|
let visible = Rectangle::<i32, Logical>::new((100, 80).into(), (400, 300).into());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expanded_blur_region(visible, 24, (1_000, 800).into()),
|
||||||
|
Rectangle::new((76, 56).into(), (448, 348).into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn blur_sampling_clips_to_output_edges() {
|
||||||
|
let visible = Rectangle::<i32, Logical>::new((0, 760).into(), (300, 40).into());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expanded_blur_region(visible, 24, (1_000, 800).into()),
|
||||||
|
Rectangle::new((0, 736).into(), (324, 64).into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn framebuffer_capture_rounds_fractional_bounds() {
|
||||||
|
assert_eq!(
|
||||||
|
framebuffer_capture_rect([10.4, 20.6, 300.2, 199.8]),
|
||||||
|
[10, 21, 300, 200]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shadow_bounds_include_blur_and_offset() {
|
fn shadow_bounds_include_blur_and_offset() {
|
||||||
let geometry = Rectangle::<i32, Logical>::new((100, 80).into(), (400, 300).into());
|
let geometry = Rectangle::<i32, Logical>::new((100, 80).into(), (400, 300).into());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
use std::os::fd::AsFd;
|
use std::os::fd::AsFd;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use ferese_protocols::effects::v1::client::{
|
use ferese_protocols::effects::v1::client::{
|
||||||
ferese_effects_manager_v1::FereseEffectsManagerV1,
|
ferese_effects_manager_v1::FereseEffectsManagerV1,
|
||||||
@@ -60,6 +61,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
return Err(format!("semantic role {role:?} changed layer geometry").into());
|
return Err(format!("semantic role {role:?} changed layer geometry").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the final glass role mapped long enough for the compositor to run
|
||||||
|
// at least one presentation. This turns shader and capture failures into
|
||||||
|
// probe failures instead of disconnecting before the first rendered frame.
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
effects.clear_role();
|
effects.clear_role();
|
||||||
queue.roundtrip(&mut state)?;
|
queue.roundtrip(&mut state)?;
|
||||||
if state.configure_count != initial_configures {
|
if state.configure_count != initial_configures {
|
||||||
|
|||||||
Reference in new issue
Block a user