diff --git a/build/vulkan/hash.rs b/build/vulkan/hash.rs index 6ca12f4f..1a1d5573 100644 --- a/build/vulkan/hash.rs +++ b/build/vulkan/hash.rs @@ -23,6 +23,8 @@ pub const TREES: &[Tree] = &[Tree { "rounded_fill.vert", "rounded_tex.frag", "rounded_tex.vert", + "blur_composite.vert", + "blur_composite.frag", "legacy/fill.frag", "legacy/fill.vert", "legacy/tex.vert", diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index facb7c17..9f0a92c5 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -132,3 +132,40 @@ pub enum WindowCriterionStringField { XRole, Workspace, } + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum LayerKindIpc { + Background, + Bottom, + Top, + Overlay, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LayerMatchIpc { + pub namespace: Option, + pub layer: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LayerRuleIpc { + pub match_: LayerMatchIpc, + pub blur: Option, + pub blur_popups: Option, + pub ignore_alpha: Option, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub struct BlurConfigIpc { + pub passes: u8, + pub size: f32, +} + +impl Default for BlurConfigIpc { + fn default() -> Self { + Self { + passes: 2, + size: 8.0, + } + } +} diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 25e18b14..2717a3e4 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -3,9 +3,9 @@ use { crate::{ _private::{ - ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen, - GenericCriterionIpc, PollableId, VERSION, WindowCriterionIpc, - WindowCriterionStringField, WireMode, bincode_ops, + BlurConfigIpc, ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, + ConfigEntryGen, GenericCriterionIpc, LayerRuleIpc, PollableId, VERSION, + WindowCriterionIpc, WindowCriterionStringField, WireMode, bincode_ops, ipc::{ ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource, }, @@ -872,6 +872,14 @@ impl ConfigClient { self.send(&ClientMessage::TriggerGlobalShortcut { app_id, id }) } + pub fn set_layer_rules(&self, rules: Vec) { + self.send(&ClientMessage::SetLayerRules { rules }) + } + + pub fn set_blur_config(&self, config: BlurConfigIpc) { + self.send(&ClientMessage::SetBlurConfig { config }) + } + pub fn switch_to_vt(&self, vtnr: u32) { self.send(&ClientMessage::SwitchTo { vtnr }) } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 1902c036..5130a98e 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -1,6 +1,9 @@ use { crate::{ - _private::{ClientCriterionIpc, PollableId, WindowCriterionIpc, WireMode}, + _private::{ + BlurConfigIpc, ClientCriterionIpc, LayerRuleIpc, PollableId, WindowCriterionIpc, + WireMode, + }, Axis, Direction, PciId, Workspace, client::{Client, ClientCapabilities, ClientMatcher}, input::{ @@ -916,6 +919,12 @@ pub enum ClientMessage<'a> { app_id: &'a str, id: &'a str, }, + SetLayerRules { + rules: Vec, + }, + SetBlurConfig { + config: BlurConfigIpc, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 089175b1..4d83f1e9 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -397,6 +397,16 @@ pub fn set_corner_radius(radius: f32) { get!().set_corner_radius(radius) } +#[doc(hidden)] +pub fn _set_layer_rules(rules: Vec) { + get!().set_layer_rules(rules) +} + +#[doc(hidden)] +pub fn _set_blur_config(config: crate::_private::BlurConfigIpc) { + get!().set_blur_config(config) +} + /// Returns the current corner radius for window borders. pub fn get_corner_radius() -> f32 { get!(0.0).get_corner_radius() diff --git a/src/compositor.rs b/src/compositor.rs index 61eda51c..ebe6e599 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -397,6 +397,8 @@ fn start_compositor2( virtual_outputs: Default::default(), clean_logs_older_than: Default::default(), hyprland_global_shortcuts: Default::default(), + layer_rules: Default::default(), + blur_config: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index b4b4f37b..b590a1c2 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -3531,6 +3531,12 @@ impl ConfigProxyHandler { ClientMessage::TriggerGlobalShortcut { app_id, id } => { self.handle_trigger_global_shortcut(app_id, id); } + ClientMessage::SetLayerRules { rules } => { + *self.state.layer_rules.borrow_mut() = rules; + } + ClientMessage::SetBlurConfig { config } => { + self.state.blur_config.set(config); + } } Ok(()) } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 8e196ede..bbe9d222 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -104,6 +104,31 @@ pub enum GfxApiOpt { CopyTexture(CopyTexture), RoundedFillRect(RoundedFillRect), RoundedCopyTexture(RoundedCopyTexture), + BlurBackdrop(BlurBackdrop), +} + +#[derive(Debug, Clone)] +pub struct BlurBackdrop { + pub rect: FramebufferRect, + pub passes: u8, + pub offset: f32, + pub mask: Option, +} + +#[derive(Clone)] +pub struct BlurMask { + pub texture: Rc, + pub source: SampleRect, + pub threshold: f32, +} + +impl std::fmt::Debug for BlurMask { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BlurMask") + .field("source", &self.source) + .field("threshold", &self.threshold) + .finish() + } } pub struct GfxRenderPass { @@ -289,6 +314,7 @@ pub struct CopyTexture { pub render_intent: RenderIntent, pub cd: Rc, pub alpha_mode: AlphaMode, + pub discard_alpha: Option, } #[derive(Debug)] @@ -338,6 +364,7 @@ pub struct RoundedCopyTexture { pub corner_radius: [f32; 4], /// Output scale for antialiasing. pub scale: f32, + pub discard_alpha: Option, } #[derive(Clone, Debug, PartialEq)] @@ -1165,6 +1192,7 @@ pub fn renderer_base<'a>( transform, fb_width: width as _, fb_height: height as _, + discard_alpha: None, } } @@ -1309,6 +1337,7 @@ impl GfxRenderPass { GfxApiOpt::CopyTexture(ct) => break 'ct2 ct, GfxApiOpt::RoundedFillRect(_) => return None, GfxApiOpt::RoundedCopyTexture(_) => return None, + GfxApiOpt::BlurBackdrop(_) => return None, } } return None; @@ -1354,6 +1383,7 @@ impl GfxRenderPass { } GfxApiOpt::RoundedFillRect(_) => return None, GfxApiOpt::RoundedCopyTexture(_) => return None, + GfxApiOpt::BlurBackdrop(_) => return None, } } if let Some(clear) = self.clear diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index dc43a79c..760936fa 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -22,8 +22,8 @@ use { crate::{ cmm::cmm_eotf::Eotf, gfx_api::{ - AcquireSync, CopyTexture, FdSync, FramebufferRect, GfxApiOpt, GfxContext, GfxError, - GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SyncFile, + AcquireSync, BlurBackdrop, CopyTexture, FdSync, FramebufferRect, GfxApiOpt, GfxContext, + GfxError, GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SyncFile, }, gfx_apis::gl::{ egl::image::EglImage, @@ -34,8 +34,11 @@ use { texture::Texture, }, sys::{ - GL_BLEND, GL_FALSE, GL_FLOAT, GL_LINEAR, GL_TEXTURE_MIN_FILTER, GL_TEXTURE0, - GL_TRIANGLE_STRIP, GL_TRIANGLES, + GL_BLEND, GL_CLAMP_TO_EDGE, GL_COLOR_ATTACHMENT0, GL_FALSE, GL_FLOAT, + GL_FRAMEBUFFER, GL_LINEAR, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_RGBA, GL_RGBA8, + GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, + GL_TEXTURE_WRAP_T, GL_TEXTURE0, GL_TEXTURE1, GL_TRIANGLE_STRIP, GL_TRIANGLES, + GL_UNSIGNED_BYTE, GLuint, }, }, theme::Color, @@ -271,6 +274,12 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { render_rounded_texture(&fb.ctx, ct); i += 1; } + GfxApiOpt::BlurBackdrop(b) => { + flush_fills!(); + flush_textures!(); + render_blur_backdrop(fb, b); + i += 1; + } } } flush_fills!(); @@ -346,18 +355,6 @@ fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) { (gles.glBindTexture)(target, texture.gl.tex); (gles.glTexParameteri)(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - let progs = match texture.gl.external_only { - true => match &ctx.tex_external { - Some(p) => p, - _ => { - log::error!( - "Trying to render an external-only texture but context does not support the required extension" - ); - return; - } - }, - false => &ctx.tex_internal, - }; let copy_type = match tex.alpha.is_some() { true => TexCopyType::Multiply, false => TexCopyType::Identity, @@ -366,12 +363,32 @@ fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) { true => TexSourceType::HasAlpha, false => TexSourceType::Opaque, }; - if (copy_type, source_type) == (TexCopyType::Identity, TexSourceType::Opaque) { + let prog = if let Some(_) = tex.discard_alpha + && !texture.gl.external_only + { + &ctx.tex_internal_discard[copy_type] + } else { + let progs = match texture.gl.external_only { + true => match &ctx.tex_external { + Some(p) => p, + _ => { + log::error!( + "Trying to render an external-only texture but context does not support the required extension" + ); + return; + } + }, + false => &ctx.tex_internal, + }; + &progs[copy_type][source_type] + }; + if (copy_type, source_type) == (TexCopyType::Identity, TexSourceType::Opaque) + && tex.discard_alpha.is_none() + { (gles.glDisable)(GL_BLEND); } else { (gles.glEnable)(GL_BLEND); } - let prog = &progs[copy_type][source_type]; (gles.glUseProgram)(prog.prog.prog); @@ -383,6 +400,9 @@ fn render_texture(ctx: &GlRenderContext, tex: &CopyTexture) { if let Some(alpha) = tex.alpha { (gles.glUniform1f)(prog.alpha, alpha); } + if let Some(threshold) = tex.discard_alpha { + (gles.glUniform1f)(prog.discard_threshold, threshold); + } (gles.glVertexAttribPointer)( prog.texcoord as _, @@ -514,6 +534,260 @@ fn render_rounded_texture(ctx: &GlRenderContext, ct: &RoundedCopyTexture) { } } +fn render_blur_backdrop(fb: &Framebuffer, b: &BlurBackdrop) { + let ctx = &fb.ctx; + let gles = ctx.ctx.dpy.gles; + let fb_w = fb.gl.width; + let fb_h = fb.gl.height; + if fb_w <= 0 || fb_h <= 0 { + return; + } + let pixel_rect = b.rect.to_rect(fb_w as f32, fb_h as f32); + let x1 = pixel_rect.x1().max(0).min(fb_w); + let y1 = pixel_rect.y1().max(0).min(fb_h); + let x2 = pixel_rect.x2().max(0).min(fb_w); + let y2 = pixel_rect.y2().max(0).min(fb_h); + let w = x2 - x1; + let h = y2 - y1; + if w < 2 || h < 2 { + return; + } + let passes = b.passes.max(1).min(8) as i32; + let offset = b.offset.max(0.0); + let original_fbo = fb.gl.fbo; + + unsafe { + let mut texs = Vec::::with_capacity(passes as usize + 1); + let mut fbos = Vec::::with_capacity(passes as usize + 1); + let mut dims = Vec::<(i32, i32)>::with_capacity(passes as usize + 1); + + let setup_tex = |tex: GLuint| { + (gles.glBindTexture)(GL_TEXTURE_2D, tex); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + }; + + // Level 0: snapshot the source FB region into a texture. + let mut tex0: GLuint = 0; + (gles.glGenTextures)(1, &mut tex0); + (gles.glActiveTexture)(GL_TEXTURE0); + setup_tex(tex0); + (gles.glBindFramebuffer)(GL_FRAMEBUFFER, original_fbo); + (gles.glCopyTexImage2D)(GL_TEXTURE_2D, 0, GL_RGBA8, x1, y1, w, h, 0); + let mut fbo0: GLuint = 0; + (gles.glGenFramebuffers)(1, &mut fbo0); + (gles.glBindFramebuffer)(GL_FRAMEBUFFER, fbo0); + (gles.glFramebufferTexture2D)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0); + texs.push(tex0); + fbos.push(fbo0); + dims.push((w, h)); + + // Allocate down-pass levels. + for i in 1..=passes { + let cw = (w >> i).max(1); + let ch = (h >> i).max(1); + let mut tex: GLuint = 0; + let mut fbo: GLuint = 0; + (gles.glGenTextures)(1, &mut tex); + setup_tex(tex); + (gles.glTexImage2D)( + GL_TEXTURE_2D, + 0, + GL_RGBA8 as _, + cw, + ch, + 0, + GL_RGBA as _, + GL_UNSIGNED_BYTE as _, + std::ptr::null(), + ); + (gles.glGenFramebuffers)(1, &mut fbo); + (gles.glBindFramebuffer)(GL_FRAMEBUFFER, fbo); + (gles.glFramebufferTexture2D)( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + tex, + 0, + ); + texs.push(tex); + fbos.push(fbo); + dims.push((cw, ch)); + } + + (gles.glDisable)(GL_BLEND); + + // Down passes. + let down = &ctx.blur_down; + (gles.glUseProgram)(down.prog.prog); + (gles.glUniform1i)(down.tex, 0); + (gles.glUniform1f)(down.offset, offset); + for i in 1..=(passes as usize) { + let (sw, sh) = dims[i - 1]; + let (dw, dh) = dims[i]; + (gles.glBindFramebuffer)(GL_FRAMEBUFFER, fbos[i]); + (gles.glViewport)(0, 0, dw, dh); + (gles.glActiveTexture)(GL_TEXTURE0); + (gles.glBindTexture)(GL_TEXTURE_2D, texs[i - 1]); + (gles.glUniform2f)(down.halfpixel, 0.5 / sw as f32, 0.5 / sh as f32); + blur_blit(gles, down.pos, down.texcoord); + } + + // Up passes. + let up = &ctx.blur_up; + (gles.glUseProgram)(up.prog.prog); + (gles.glUniform1i)(up.tex, 0); + (gles.glUniform1f)(up.offset, offset); + for i in (0..(passes as usize)).rev() { + let (sw, sh) = dims[i + 1]; + let (dw, dh) = dims[i]; + (gles.glBindFramebuffer)(GL_FRAMEBUFFER, fbos[i]); + (gles.glViewport)(0, 0, dw, dh); + (gles.glActiveTexture)(GL_TEXTURE0); + (gles.glBindTexture)(GL_TEXTURE_2D, texs[i + 1]); + (gles.glUniform2f)(up.halfpixel, 0.5 / sw as f32, 0.5 / sh as f32); + blur_blit(gles, up.pos, up.texcoord); + } + + // Blit back to the original framebuffer at the rect location. + (gles.glBindFramebuffer)(GL_FRAMEBUFFER, original_fbo); + (gles.glViewport)(0, 0, fb_w, fb_h); + let pos = b.rect.to_points(); + let texcoord: [[f32; 2]; 4] = [[1.0, 0.0], [0.0, 0.0], [1.0, 1.0], [0.0, 1.0]]; + let mask_gl = b + .mask + .as_ref() + .and_then(|m| m.texture.as_gl().map(|t| (m, t))); + if let Some((mask, mask_tex_obj)) = mask_gl + && !mask_tex_obj.gl.external_only + { + // Masked composite: src = (blurred * weight, weight); blend = (ONE, ONE_MINUS_SRC_ALPHA). + let prog = &ctx.blur_composite; + (gles.glUseProgram)(prog.prog.prog); + (gles.glActiveTexture)(GL_TEXTURE0); + (gles.glBindTexture)(GL_TEXTURE_2D, texs[0]); + (gles.glUniform1i)(prog.tex, 0); + (gles.glActiveTexture)(GL_TEXTURE1); + (gles.glBindTexture)(GL_TEXTURE_2D, mask_tex_obj.gl.tex); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + (gles.glTexParameteri)(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + (gles.glUniform1i)(prog.mask_tex, 1); + (gles.glUniform1f)(prog.threshold, mask.threshold); + let mask_tc = mask.source.to_points(); + (gles.glVertexAttribPointer)( + prog.texcoord as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + texcoord.as_ptr() as _, + ); + (gles.glVertexAttribPointer)( + prog.mask_texcoord as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + mask_tc.as_ptr() as _, + ); + (gles.glVertexAttribPointer)( + prog.pos as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + pos.as_ptr() as _, + ); + (gles.glEnableVertexAttribArray)(prog.texcoord as _); + (gles.glEnableVertexAttribArray)(prog.mask_texcoord as _); + (gles.glEnableVertexAttribArray)(prog.pos as _); + (gles.glEnable)(GL_BLEND); + (gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + (gles.glDrawArrays)(GL_TRIANGLE_STRIP, 0, 4); + (gles.glDisableVertexAttribArray)(prog.texcoord as _); + (gles.glDisableVertexAttribArray)(prog.mask_texcoord as _); + (gles.glDisableVertexAttribArray)(prog.pos as _); + (gles.glActiveTexture)(GL_TEXTURE1); + (gles.glBindTexture)(GL_TEXTURE_2D, 0); + (gles.glActiveTexture)(GL_TEXTURE0); + (gles.glBindTexture)(GL_TEXTURE_2D, 0); + } else { + let prog = &ctx.tex_internal[TexCopyType::Identity][TexSourceType::Opaque]; + (gles.glUseProgram)(prog.prog.prog); + (gles.glActiveTexture)(GL_TEXTURE0); + (gles.glBindTexture)(GL_TEXTURE_2D, texs[0]); + (gles.glUniform1i)(prog.tex, 0); + (gles.glVertexAttribPointer)( + prog.texcoord as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + texcoord.as_ptr() as _, + ); + (gles.glVertexAttribPointer)( + prog.pos as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + pos.as_ptr() as _, + ); + (gles.glEnableVertexAttribArray)(prog.texcoord as _); + (gles.glEnableVertexAttribArray)(prog.pos as _); + (gles.glDisable)(GL_BLEND); + (gles.glDrawArrays)(GL_TRIANGLE_STRIP, 0, 4); + (gles.glDisableVertexAttribArray)(prog.texcoord as _); + (gles.glDisableVertexAttribArray)(prog.pos as _); + (gles.glBindTexture)(GL_TEXTURE_2D, 0); + } + + // Cleanup. + for fbo in &fbos { + (gles.glDeleteFramebuffers)(1, fbo); + } + for tex in &texs { + (gles.glDeleteTextures)(1, tex); + } + } +} + +unsafe fn blur_blit( + gles: &crate::gfx_apis::gl::sys::GlesV2, + pos_loc: crate::gfx_apis::gl::sys::GLint, + texcoord_loc: crate::gfx_apis::gl::sys::GLint, +) { + static FULLSCREEN_POS: [[f32; 2]; 4] = [[1.0, -1.0], [-1.0, -1.0], [1.0, 1.0], [-1.0, 1.0]]; + static FULLSCREEN_TC: [[f32; 2]; 4] = [[1.0, 0.0], [0.0, 0.0], [1.0, 1.0], [0.0, 1.0]]; + unsafe { + (gles.glVertexAttribPointer)( + pos_loc as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + FULLSCREEN_POS.as_ptr() as _, + ); + (gles.glVertexAttribPointer)( + texcoord_loc as _, + 2, + GL_FLOAT, + GL_FALSE, + 0, + FULLSCREEN_TC.as_ptr() as _, + ); + (gles.glEnableVertexAttribArray)(pos_loc as _); + (gles.glEnableVertexAttribArray)(texcoord_loc as _); + (gles.glDrawArrays)(GL_TRIANGLE_STRIP, 0, 4); + (gles.glDisableVertexAttribArray)(pos_loc as _); + (gles.glDisableVertexAttribArray)(texcoord_loc as _); + } +} + fn handle_explicit_sync(ctx: &GlRenderContext, img: Option<&Rc>, sync: &AcquireSync) { let Some(sync_file) = sync.get_sync_file() else { return; diff --git a/src/gfx_apis/gl/gl/sys.rs b/src/gfx_apis/gl/gl/sys.rs index 5d79cba8..533207a4 100644 --- a/src/gfx_apis/gl/gl/sys.rs +++ b/src/gfx_apis/gl/gl/sys.rs @@ -30,9 +30,9 @@ pub const GL_LINEAR: GLint = 0x2601; pub const GL_LINK_STATUS: GLenum = 0x8B82; pub const GL_RENDERBUFFER: GLenum = 0x8D41; pub const GL_TEXTURE0: GLenum = 0x84C0; +pub const GL_TEXTURE1: GLenum = 0x84C1; pub const GL_TEXTURE_2D: GLenum = 0x0DE1; pub const GL_TEXTURE_EXTERNAL_OES: GLenum = 0x8D65; -#[expect(dead_code)] pub const GL_TEXTURE_MAG_FILTER: GLenum = 0x2800; pub const GL_TEXTURE_MIN_FILTER: GLenum = 0x2801; pub const GL_TEXTURE_WRAP_S: GLenum = 0x2802; @@ -98,6 +98,25 @@ dynload! { pixels: *const c::c_void, ), + glCopyTexImage2D: unsafe fn( + target: GLenum, + level: GLint, + internalformat: GLenum, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + ), + + glFramebufferTexture2D: unsafe fn( + target: GLenum, + attachment: GLenum, + textarget: GLenum, + texture: GLuint, + level: GLint, + ), + glEnable: unsafe fn(cap: GLenum), glDisable: unsafe fn(cap: GLenum), glViewport: unsafe fn(x: GLint, y: GLint, width: GLsizei, height: GLsizei), diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 00bfe9c2..d5508563 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -40,26 +40,51 @@ pub(crate) struct TexProg { pub(crate) texcoord: GLint, pub(crate) tex: GLint, pub(crate) alpha: GLint, + pub(crate) discard_threshold: GLint, } impl TexProg { - unsafe fn from(prog: GlProgram, alpha_multiplier: bool) -> Self { + unsafe fn from(prog: GlProgram, alpha_multiplier: bool, discard: bool) -> Self { unsafe { let alpha = match alpha_multiplier { true => prog.get_uniform_location(c"alpha"), false => 0, }; + let discard_threshold = match discard { + true => prog.get_uniform_location(c"discard_threshold"), + false => 0, + }; Self { pos: prog.get_attrib_location(c"pos"), texcoord: prog.get_attrib_location(c"texcoord"), tex: prog.get_uniform_location(c"tex"), alpha, + discard_threshold, prog, } } } } +pub(crate) struct BlurProg { + pub(crate) prog: GlProgram, + pub(crate) pos: GLint, + pub(crate) texcoord: GLint, + pub(crate) tex: GLint, + pub(crate) halfpixel: GLint, + pub(crate) offset: GLint, +} + +pub(crate) struct BlurCompositeProg { + pub(crate) prog: GlProgram, + pub(crate) pos: GLint, + pub(crate) texcoord: GLint, + pub(crate) mask_texcoord: GLint, + pub(crate) tex: GLint, + pub(crate) mask_tex: GLint, + pub(crate) threshold: GLint, +} + pub(crate) struct RoundedFillProg { pub(crate) prog: GlProgram, pub(crate) pos: GLint, @@ -104,6 +129,11 @@ pub(in crate::gfx_apis::gl) struct GlRenderContext { pub(crate) tex_internal: StaticMap>, pub(crate) tex_external: Option>>, + pub(crate) tex_internal_discard: StaticMap, + + pub(crate) blur_down: BlurProg, + pub(crate) blur_up: BlurProg, + pub(crate) blur_composite: BlurCompositeProg, pub(crate) fill_prog: GlProgram, pub(crate) fill_prog_pos: GLint, @@ -150,7 +180,7 @@ impl GlRenderContext { unsafe fn new(ctx: &Rc, node: &Rc) -> Result { let tex_vert = include_str!("../shaders/tex.vert.glsl"); let tex_frag = include_str!("../shaders/tex.frag.glsl"); - let create_programs = |external: bool| { + let create_programs = |external: bool, discard: bool| { let create_program = |alpha_multiplier: bool, alpha: bool| { let mut tex_frac_src = String::new(); if external { @@ -162,10 +192,13 @@ impl GlRenderContext { if alpha { tex_frac_src.push_str("#define ALPHA\n"); } + if discard { + tex_frac_src.push_str("#define DISCARD\n"); + } tex_frac_src.push_str(tex_frag); unsafe { let prog = GlProgram::from_shaders(ctx, tex_vert, &tex_frac_src)?; - Ok::<_, RenderError>(TexProg::from(prog, alpha_multiplier)) + Ok::<_, RenderError>(TexProg::from(prog, alpha_multiplier, discard)) } }; Ok::<_, RenderError>(static_map! { @@ -179,12 +212,57 @@ impl GlRenderContext { }, }) }; - let tex_internal = create_programs(false)?; + let tex_internal = create_programs(false, false)?; let tex_external = if ctx.ext.contains(GL_OES_EGL_IMAGE_EXTERNAL) { - Some(create_programs(true)?) + Some(create_programs(true, false)?) } else { None }; + let create_discard_program = |alpha_multiplier: bool| { + let mut src = String::from("#define ALPHA\n#define DISCARD\n"); + if alpha_multiplier { + src.push_str("#define ALPHA_MULTIPLIER\n"); + } + src.push_str(tex_frag); + unsafe { + let prog = GlProgram::from_shaders(ctx, tex_vert, &src)?; + Ok::<_, RenderError>(TexProg::from(prog, alpha_multiplier, true)) + } + }; + let tex_internal_discard = static_map! { + TexCopyType::Identity => create_discard_program(false)?, + TexCopyType::Multiply => create_discard_program(true)?, + }; + let blur_vert = include_str!("../shaders/blur.vert.glsl"); + let create_blur_prog = |frag_src: &str| unsafe { + let prog = GlProgram::from_shaders(ctx, blur_vert, frag_src)?; + Ok::<_, RenderError>(BlurProg { + pos: prog.get_attrib_location(c"pos"), + texcoord: prog.get_attrib_location(c"texcoord"), + tex: prog.get_uniform_location(c"tex"), + halfpixel: prog.get_uniform_location(c"halfpixel"), + offset: prog.get_uniform_location(c"offset"), + prog, + }) + }; + let blur_down = create_blur_prog(include_str!("../shaders/blur_down.frag.glsl"))?; + let blur_up = create_blur_prog(include_str!("../shaders/blur_up.frag.glsl"))?; + let blur_composite = unsafe { + let prog = GlProgram::from_shaders( + ctx, + include_str!("../shaders/blur_composite.vert.glsl"), + include_str!("../shaders/blur_composite.frag.glsl"), + )?; + BlurCompositeProg { + pos: prog.get_attrib_location(c"pos"), + texcoord: prog.get_attrib_location(c"texcoord"), + mask_texcoord: prog.get_attrib_location(c"mask_texcoord"), + tex: prog.get_uniform_location(c"tex"), + mask_tex: prog.get_uniform_location(c"mask_tex"), + threshold: prog.get_uniform_location(c"threshold"), + prog, + } + }; let fill_prog = unsafe { GlProgram::from_shaders( ctx, @@ -269,6 +347,11 @@ impl GlRenderContext { tex_internal, tex_external, + tex_internal_discard, + + blur_down, + blur_up, + blur_composite, fill_prog_pos: unsafe { fill_prog.get_attrib_location(c"pos") }, fill_prog_color: unsafe { fill_prog.get_uniform_location(c"color") }, diff --git a/src/gfx_apis/gl/shaders/blur.vert.glsl b/src/gfx_apis/gl/shaders/blur.vert.glsl new file mode 100644 index 00000000..29718593 --- /dev/null +++ b/src/gfx_apis/gl/shaders/blur.vert.glsl @@ -0,0 +1,8 @@ +attribute vec2 pos; +attribute vec2 texcoord; +varying vec2 v_texcoord; + +void main() { + gl_Position = vec4(pos, 0.0, 1.0); + v_texcoord = texcoord; +} diff --git a/src/gfx_apis/gl/shaders/blur_composite.frag.glsl b/src/gfx_apis/gl/shaders/blur_composite.frag.glsl new file mode 100644 index 00000000..5165ea1d --- /dev/null +++ b/src/gfx_apis/gl/shaders/blur_composite.frag.glsl @@ -0,0 +1,17 @@ +precision mediump float; + +varying vec2 v_texcoord; +varying vec2 v_mask_texcoord; +uniform sampler2D tex; +uniform sampler2D mask_tex; +uniform float threshold; + +void main() { + vec3 blurred = texture2D(tex, v_texcoord).rgb; + vec2 uv = v_mask_texcoord; + float in_range = step(0.0, uv.x) * step(uv.x, 1.0) + * step(0.0, uv.y) * step(uv.y, 1.0); + float a = texture2D(mask_tex, clamp(uv, 0.0, 1.0)).a * in_range; + float weight = smoothstep(0.0, max(threshold, 0.001), a); + gl_FragColor = vec4(blurred * weight, weight); +} diff --git a/src/gfx_apis/gl/shaders/blur_composite.vert.glsl b/src/gfx_apis/gl/shaders/blur_composite.vert.glsl new file mode 100644 index 00000000..e3737069 --- /dev/null +++ b/src/gfx_apis/gl/shaders/blur_composite.vert.glsl @@ -0,0 +1,11 @@ +attribute vec2 pos; +attribute vec2 texcoord; +attribute vec2 mask_texcoord; +varying vec2 v_texcoord; +varying vec2 v_mask_texcoord; + +void main() { + gl_Position = vec4(pos, 0.0, 1.0); + v_texcoord = texcoord; + v_mask_texcoord = mask_texcoord; +} diff --git a/src/gfx_apis/gl/shaders/blur_down.frag.glsl b/src/gfx_apis/gl/shaders/blur_down.frag.glsl new file mode 100644 index 00000000..9f7b532b --- /dev/null +++ b/src/gfx_apis/gl/shaders/blur_down.frag.glsl @@ -0,0 +1,16 @@ +precision mediump float; + +varying vec2 v_texcoord; +uniform sampler2D tex; +uniform vec2 halfpixel; +uniform float offset; + +void main() { + vec2 hp = halfpixel * offset; + vec4 sum = texture2D(tex, v_texcoord) * 4.0; + sum += texture2D(tex, v_texcoord - hp); + sum += texture2D(tex, v_texcoord + hp); + sum += texture2D(tex, v_texcoord + vec2(hp.x, -hp.y)); + sum += texture2D(tex, v_texcoord - vec2(hp.x, -hp.y)); + gl_FragColor = sum / 8.0; +} diff --git a/src/gfx_apis/gl/shaders/blur_up.frag.glsl b/src/gfx_apis/gl/shaders/blur_up.frag.glsl new file mode 100644 index 00000000..34e61165 --- /dev/null +++ b/src/gfx_apis/gl/shaders/blur_up.frag.glsl @@ -0,0 +1,19 @@ +precision mediump float; + +varying vec2 v_texcoord; +uniform sampler2D tex; +uniform vec2 halfpixel; +uniform float offset; + +void main() { + vec2 hp = halfpixel * offset; + vec4 sum = texture2D(tex, v_texcoord + vec2(-hp.x * 2.0, 0.0)); + sum += texture2D(tex, v_texcoord + vec2(-hp.x, hp.y)) * 2.0; + sum += texture2D(tex, v_texcoord + vec2(0.0, hp.y * 2.0)); + sum += texture2D(tex, v_texcoord + vec2(hp.x, hp.y)) * 2.0; + sum += texture2D(tex, v_texcoord + vec2(hp.x * 2.0, 0.0)); + sum += texture2D(tex, v_texcoord + vec2(hp.x, -hp.y)) * 2.0; + sum += texture2D(tex, v_texcoord + vec2(0.0, -hp.y * 2.0)); + sum += texture2D(tex, v_texcoord + vec2(-hp.x, -hp.y)) * 2.0; + gl_FragColor = sum / 12.0; +} diff --git a/src/gfx_apis/gl/shaders/rounded_tex.frag.glsl b/src/gfx_apis/gl/shaders/rounded_tex.frag.glsl index 867f44da..46fe0871 100644 --- a/src/gfx_apis/gl/shaders/rounded_tex.frag.glsl +++ b/src/gfx_apis/gl/shaders/rounded_tex.frag.glsl @@ -18,6 +18,9 @@ uniform float alpha; uniform vec2 size; uniform vec4 corner_radius; uniform float scale; +#ifdef DISCARD +uniform float discard_threshold; +#endif float rounding_alpha(vec2 coords, vec2 half_size, vec4 radii) { float radius; @@ -48,6 +51,9 @@ float rounding_alpha(vec2 coords, vec2 half_size, vec4 radii) { void main() { vec2 half_size = size / 2.0; float ra = rounding_alpha(v_geo, half_size, corner_radius); +#ifdef DISCARD + if (texture2D(tex, v_texcoord).a < discard_threshold) discard; +#endif #ifdef ALPHA diff --git a/src/gfx_apis/gl/shaders/tex.frag.glsl b/src/gfx_apis/gl/shaders/tex.frag.glsl index 213ed085..53340cca 100644 --- a/src/gfx_apis/gl/shaders/tex.frag.glsl +++ b/src/gfx_apis/gl/shaders/tex.frag.glsl @@ -12,22 +12,29 @@ uniform sampler2D tex; #ifdef ALPHA_MULTIPLIER uniform float alpha; #endif +#ifdef DISCARD +uniform float discard_threshold; +#endif void main() { + vec4 sampled = texture2D(tex, v_texcoord); +#ifdef DISCARD + if (sampled.a < discard_threshold) discard; +#endif #ifdef ALPHA #ifdef ALPHA_MULTIPLIER - gl_FragColor = texture2D(tex, v_texcoord) * alpha; + gl_FragColor = sampled * alpha; #else // !ALPHA_MULTIPLIER - gl_FragColor = texture2D(tex, v_texcoord); + gl_FragColor = sampled; #endif // ALPHA_MULTIPLIER #else // !ALPHA #ifdef ALPHA_MULTIPLIER - gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb * alpha, alpha); + gl_FragColor = vec4(sampled.rgb * alpha, alpha); #else // !ALPHA_MULTIPLIER - gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0); + gl_FragColor = vec4(sampled.rgb, 1.0); #endif // ALPHA_MULTIPLIER #endif // ALPHA diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 47e1af43..63145e7e 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -1,6 +1,7 @@ mod allocator; mod alpha_modes; mod blend_buffer; +mod blur; mod bo_allocator; mod buffer_cache; mod command; diff --git a/src/gfx_apis/vulkan/blur.rs b/src/gfx_apis/vulkan/blur.rs new file mode 100644 index 00000000..7c4734a2 --- /dev/null +++ b/src/gfx_apis/vulkan/blur.rs @@ -0,0 +1,544 @@ +use { + crate::gfx_apis::vulkan::{ + VulkanError, + image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory}, + renderer::VulkanRenderer, + shaders::BlurCompositePushConstants, + }, + ash::vk::{ + AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BlitImageInfo2, CommandBuffer, + DependencyInfoKHR, DescriptorImageInfo, DescriptorType, Extent2D, Extent3D, Filter, + ImageAspectFlags, ImageBlit2, ImageCreateInfo, ImageLayout, ImageMemoryBarrier2, + ImageSubresourceLayers, ImageSubresourceRange, ImageTiling, ImageType, ImageUsageFlags, + ImageViewCreateInfo, ImageViewType, Offset2D, Offset3D, PipelineBindPoint, + PipelineStageFlags2, Rect2D, RenderingAttachmentInfo, RenderingInfo, SampleCountFlags, + ShaderStageFlags, SharingMode, Viewport, WriteDescriptorSet, + }, + gpu_alloc::UsageFlags, + run_on_drop::on_drop, + std::{cell::Cell, collections::hash_map::Entry, rc::Rc, slice}, +}; + +const BLUR_SCRATCH_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( + ImageUsageFlags::TRANSFER_SRC.as_raw() + | ImageUsageFlags::TRANSFER_DST.as_raw() + | ImageUsageFlags::SAMPLED.as_raw(), +); + +pub(super) struct BlurMaskRecord<'a> { + pub(super) mask_view: ash::vk::ImageView, + pub(super) mask_source_points: [[f32; 2]; 4], + pub(super) target_points: [[f32; 2]; 4], + pub(super) threshold: f32, + pub(super) _phantom: std::marker::PhantomData<&'a ()>, +} + +impl VulkanRenderer { + fn acquire_blur_scratch( + self: &Rc, + width: u32, + height: u32, + format: ash::vk::Format, + ) -> Result, VulkanError> { + let key = (width, height, format); + let cached = &mut *self.blur_scratch.borrow_mut(); + let entry = cached.entry(key); + if let Entry::Occupied(e) = &entry + && let Some(img) = e.get().upgrade() + { + return Ok(img); + } + let create_info = ImageCreateInfo::default() + .image_type(ImageType::TYPE_2D) + .format(format) + .mip_levels(1) + .array_layers(1) + .tiling(ImageTiling::OPTIMAL) + .samples(SampleCountFlags::TYPE_1) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .extent(Extent3D { + width, + height, + depth: 1, + }) + .usage(BLUR_SCRATCH_USAGE); + let image = unsafe { self.device.device.create_image(&create_info, None) }; + let image = image.map_err(VulkanError::CreateImage)?; + let destroy_image = on_drop(|| unsafe { self.device.device.destroy_image(image, None) }); + let memory_requirements = + unsafe { self.device.device.get_image_memory_requirements(image) }; + let allocation = + self.allocator + .alloc(&memory_requirements, UsageFlags::FAST_DEVICE_ACCESS, false)?; + let res = unsafe { + self.device + .device + .bind_image_memory(image, allocation.memory, allocation.offset) + }; + res.map_err(VulkanError::BindImageMemory)?; + // No view needed (we only blit), but VulkanImage requires one. + let image_view_create_info = ImageViewCreateInfo::default() + .image(image) + .format(format) + .view_type(ImageViewType::TYPE_2D) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let view = unsafe { + self.device + .device + .create_image_view(&image_view_create_info, None) + }; + let view = view.map_err(VulkanError::CreateImageView)?; + destroy_image.forget(); + // Reuse the BLEND_FORMAT placeholder; the format field is informational + // here, blit ops use the actual VkFormat above. + let img = Rc::new(VulkanImage { + renderer: self.clone(), + format: crate::gfx_apis::vulkan::format::BLEND_FORMAT, + width, + height, + stride: 0, + texture_view: view, + render_view: None, + image, + is_undefined: Cell::new(true), + contents_are_undefined: Cell::new(true), + queue_state: Cell::new(QueueState::Acquired { + family: QueueFamily::Gfx, + }), + ty: VulkanImageMemory::Blend(allocation), + bridge: None, + sampled_image_descriptor: None, + execution_version: Default::default(), + }); + match entry { + Entry::Occupied(mut e) => { + e.insert(Rc::downgrade(&img)); + } + Entry::Vacant(e) => { + e.insert(Rc::downgrade(&img)); + } + } + Ok(img) + } + + /// Records a backdrop blur of the given pixel rect on the target image. + /// Caller is responsible for ending the current dynamic render pass before + /// invoking, and for restarting it afterward (with LOAD). + pub(super) fn record_blur( + self: &Rc, + buf: CommandBuffer, + target: &VulkanImage, + rect: [i32; 4], + passes: u8, + scratch_out: &mut Vec>, + mask: Option<&BlurMaskRecord<'_>>, + ) -> Result<(), VulkanError> { + let [x1, y1, x2, y2] = rect; + let x1 = x1.max(0).min(target.width as i32); + let y1 = y1.max(0).min(target.height as i32); + let x2 = x2.max(0).min(target.width as i32); + let y2 = y2.max(0).min(target.height as i32); + let w = (x2 - x1) as u32; + let h = (y2 - y1) as u32; + if w < 4 || h < 4 { + return Ok(()); + } + let passes = passes.clamp(1, 6) as u32; + + let format = target.format.vk_format; + let mut levels: Vec> = + Vec::with_capacity(passes as usize + 1); + levels.push(self.acquire_blur_scratch(w, h, format)?); + let mut cw = w; + let mut ch = h; + for _ in 0..passes { + cw = (cw / 2).max(1); + ch = (ch / 2).max(1); + levels.push(self.acquire_blur_scratch(cw, ch, format)?); + } + + let dev = &self.device.device; + let subres = ImageSubresourceLayers::default() + .aspect_mask(ImageAspectFlags::COLOR) + .layer_count(1) + .base_array_layer(0) + .mip_level(0); + let subres_range = ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }; + + let barrier = |image, + old: ImageLayout, + new: ImageLayout, + src_stage: PipelineStageFlags2, + src_access: AccessFlags2, + dst_stage: PipelineStageFlags2, + dst_access: AccessFlags2| { + ImageMemoryBarrier2::default() + .image(image) + .old_layout(old) + .new_layout(new) + .subresource_range(subres_range) + .src_stage_mask(src_stage) + .src_access_mask(src_access) + .dst_stage_mask(dst_stage) + .dst_access_mask(dst_access) + .src_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED) + }; + + let do_barriers = |barriers: &[ImageMemoryBarrier2]| unsafe { + let dep = DependencyInfoKHR::default().image_memory_barriers(barriers); + dev.cmd_pipeline_barrier2(buf, &dep); + }; + + // Step 1: target COLOR_ATTACHMENT -> TRANSFER_SRC. + // Step 1: levels[0] UNDEFINED -> TRANSFER_DST. + do_barriers(&[ + barrier( + target.image, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + ImageLayout::TRANSFER_SRC_OPTIMAL, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + ), + barrier( + levels[0].image, + ImageLayout::UNDEFINED, + ImageLayout::TRANSFER_DST_OPTIMAL, + PipelineStageFlags2::TOP_OF_PIPE, + AccessFlags2::empty(), + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + ), + ]); + + // Step 2: blit target rect -> levels[0] full. + let blit = ImageBlit2::default() + .src_subresource(subres) + .dst_subresource(subres) + .src_offsets([ + Offset3D { x: x1, y: y1, z: 0 }, + Offset3D { x: x2, y: y2, z: 1 }, + ]) + .dst_offsets([ + Offset3D { x: 0, y: 0, z: 0 }, + Offset3D { + x: w as i32, + y: h as i32, + z: 1, + }, + ]); + let blit_info = BlitImageInfo2::default() + .src_image(target.image) + .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .dst_image(levels[0].image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .filter(Filter::LINEAR) + .regions(slice::from_ref(&blit)); + unsafe { + dev.cmd_blit_image2(buf, &blit_info); + } + + // Down passes: levels[i-1] -> levels[i] with linear filter. + for i in 1..=passes as usize { + let (src, dst) = (&levels[i - 1], &levels[i]); + do_barriers(&[ + barrier( + src.image, + ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::TRANSFER_SRC_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + ), + barrier( + dst.image, + ImageLayout::UNDEFINED, + ImageLayout::TRANSFER_DST_OPTIMAL, + PipelineStageFlags2::TOP_OF_PIPE, + AccessFlags2::empty(), + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + ), + ]); + let blit = ImageBlit2::default() + .src_subresource(subres) + .dst_subresource(subres) + .src_offsets([ + Offset3D { x: 0, y: 0, z: 0 }, + Offset3D { + x: src.width as i32, + y: src.height as i32, + z: 1, + }, + ]) + .dst_offsets([ + Offset3D { x: 0, y: 0, z: 0 }, + Offset3D { + x: dst.width as i32, + y: dst.height as i32, + z: 1, + }, + ]); + let blit_info = BlitImageInfo2::default() + .src_image(src.image) + .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .dst_image(dst.image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .filter(Filter::LINEAR) + .regions(slice::from_ref(&blit)); + unsafe { + dev.cmd_blit_image2(buf, &blit_info); + } + } + + // Up passes: levels[i+1] -> levels[i] with linear filter. + for i in (0..passes as usize).rev() { + let (src, dst) = (&levels[i + 1], &levels[i]); + do_barriers(&[ + barrier( + src.image, + ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::TRANSFER_SRC_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + ), + barrier( + dst.image, + ImageLayout::TRANSFER_SRC_OPTIMAL, + ImageLayout::TRANSFER_DST_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + ), + ]); + let blit = ImageBlit2::default() + .src_subresource(subres) + .dst_subresource(subres) + .src_offsets([ + Offset3D { x: 0, y: 0, z: 0 }, + Offset3D { + x: src.width as i32, + y: src.height as i32, + z: 1, + }, + ]) + .dst_offsets([ + Offset3D { x: 0, y: 0, z: 0 }, + Offset3D { + x: dst.width as i32, + y: dst.height as i32, + z: 1, + }, + ]); + let blit_info = BlitImageInfo2::default() + .src_image(src.image) + .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .dst_image(dst.image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .filter(Filter::LINEAR) + .regions(slice::from_ref(&blit)); + unsafe { + dev.cmd_blit_image2(buf, &blit_info); + } + } + + if let Some(mask) = mask { + // Masked composite path: + // levels[0] (TRANSFER_DST) -> SHADER_READ_ONLY_OPTIMAL + // target (TRANSFER_SRC) -> COLOR_ATTACHMENT_OPTIMAL + // draw composite shader sampling levels[0] + mask, blending onto fb + do_barriers(&[ + barrier( + levels[0].image, + ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::SHADER_READ_ONLY_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::FRAGMENT_SHADER, + AccessFlags2::SHADER_SAMPLED_READ, + ), + barrier( + target.image, + ImageLayout::TRANSFER_SRC_OPTIMAL, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, + ), + ]); + + let pipeline = self.get_or_create_blur_composite_pipeline(target.format.vk_format)?; + + let target_render_view = target + .render_view + .unwrap_or(target.texture_view); + let color_attachment = RenderingAttachmentInfo::default() + .image_view(target_render_view) + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .load_op(AttachmentLoadOp::LOAD) + .store_op(AttachmentStoreOp::STORE); + let render_area = Rect2D { + offset: Offset2D { x: 0, y: 0 }, + extent: Extent2D { + width: target.width, + height: target.height, + }, + }; + let rendering_info = RenderingInfo::default() + .render_area(render_area) + .layer_count(1) + .color_attachments(slice::from_ref(&color_attachment)); + let viewport = Viewport { + x: 0.0, + y: 0.0, + width: target.width as f32, + height: target.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }; + let scissor = Rect2D { + offset: Offset2D { x: x1, y: y1 }, + extent: Extent2D { + width: w, + height: h, + }, + }; + + // Identity uv across blurred level 0 (full image is the blurred rect). + let blurred_tc: [[f32; 2]; 4] = [ + [1.0, 0.0], + [0.0, 0.0], + [1.0, 1.0], + [0.0, 1.0], + ]; + let push = BlurCompositePushConstants { + pos: mask.target_points, + blurred_tex_pos: blurred_tc, + mask_tex_pos: mask.mask_source_points, + threshold: mask.threshold, + }; + + let blurred_image_info = DescriptorImageInfo::default() + .image_view(levels[0].texture_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let mask_image_info = DescriptorImageInfo::default() + .image_view(mask.mask_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let writes = [ + WriteDescriptorSet::default() + .dst_binding(0) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&blurred_image_info)), + WriteDescriptorSet::default() + .dst_binding(1) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&mask_image_info)), + ]; + + unsafe { + dev.cmd_begin_rendering(buf, &rendering_info); + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + dev.cmd_set_viewport(buf, 0, slice::from_ref(&viewport)); + dev.cmd_set_scissor(buf, 0, slice::from_ref(&scissor)); + self.device.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &writes, + ); + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + dev.cmd_end_rendering(buf); + } + } else { + // Final blit: levels[0] -> target rect. + do_barriers(&[ + barrier( + levels[0].image, + ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::TRANSFER_SRC_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + ), + barrier( + target.image, + ImageLayout::TRANSFER_SRC_OPTIMAL, + ImageLayout::TRANSFER_DST_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_READ, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + ), + ]); + let blit = ImageBlit2::default() + .src_subresource(subres) + .dst_subresource(subres) + .src_offsets([ + Offset3D { x: 0, y: 0, z: 0 }, + Offset3D { + x: w as i32, + y: h as i32, + z: 1, + }, + ]) + .dst_offsets([ + Offset3D { x: x1, y: y1, z: 0 }, + Offset3D { x: x2, y: y2, z: 1 }, + ]); + let blit_info = BlitImageInfo2::default() + .src_image(levels[0].image) + .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .dst_image(target.image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .filter(Filter::NEAREST) + .regions(slice::from_ref(&blit)); + unsafe { + dev.cmd_blit_image2(buf, &blit_info); + } + + // Restore target to COLOR_ATTACHMENT_OPTIMAL. + do_barriers(&[barrier( + target.image, + ImageLayout::TRANSFER_DST_OPTIMAL, + ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + PipelineStageFlags2::TRANSFER, + AccessFlags2::TRANSFER_WRITE, + PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, + AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, + )]); + } + + // Hold the scratch images until the frame is submitted. + scratch_out.extend(levels); + Ok(()) + } +} diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs index 66f519d2..fa207f7a 100644 --- a/src/gfx_apis/vulkan/descriptor.rs +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -54,6 +54,39 @@ impl VulkanDevice { })) } + pub(super) fn create_blur_composite_descriptor_set_layout( + self: &Rc, + sampler: &Rc, + ) -> Result, VulkanError> { + let immutable_samplers = [sampler.sampler, sampler.sampler]; + let bindings = [ + DescriptorSetLayoutBinding::default() + .binding(0) + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .immutable_samplers(&immutable_samplers[0..1]), + DescriptorSetLayoutBinding::default() + .binding(1) + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .immutable_samplers(&immutable_samplers[1..2]), + ]; + let create_info = DescriptorSetLayoutCreateInfo::default() + .bindings(&bindings) + .flags(DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR); + let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; + let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; + Ok(Rc::new(VulkanDescriptorSetLayout { + device: self.clone(), + layout, + size: 0, + offsets: Default::default(), + _sampler: Some(sampler.clone()), + })) + } + pub(super) fn create_tex_sampler_descriptor_set_layout( self: &Rc, sampler: &Rc, diff --git a/src/gfx_apis/vulkan/format.rs b/src/gfx_apis/vulkan/format.rs index 71386749..edb76a8b 100644 --- a/src/gfx_apis/vulkan/format.rs +++ b/src/gfx_apis/vulkan/format.rs @@ -58,7 +58,8 @@ pub struct VulkanBlendBufferLimits { const FRAMEBUFFER_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() - | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(), + | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw() + | FormatFeatureFlags::TRANSFER_DST.as_raw(), ); const FRAMEBUFFER_BRIDGED_FEATURES: FormatFeatureFlags = FormatFeatureFlags::TRANSFER_DST; const TEX_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( @@ -73,7 +74,9 @@ const SHM_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw(TRANSFER_FEATURES.as_raw() | TEX_FEATURES.as_raw()); const FRAMEBUFFER_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( - ImageUsageFlags::COLOR_ATTACHMENT.as_raw() | ImageUsageFlags::TRANSFER_SRC.as_raw(), + ImageUsageFlags::COLOR_ATTACHMENT.as_raw() + | ImageUsageFlags::TRANSFER_SRC.as_raw() + | ImageUsageFlags::TRANSFER_DST.as_raw(), ); const FRAMEBUFFER_BRIDGED_USAGE: ImageUsageFlags = ImageUsageFlags::TRANSFER_DST; const TEX_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index d0a48d26..9758e7ff 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -26,6 +26,7 @@ use { sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ + BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BlurCompositePushConstants, ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants, InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG, LEGACY_ROUNDED_FILL_VERT, LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT, @@ -118,6 +119,10 @@ pub struct VulkanRenderer { StaticMap>>, pub(super) tex_descriptor_set_layouts: ArrayVec, 2>, pub(super) out_descriptor_set_layout: Option>, + pub(super) blur_composite_vert_shader: Rc, + pub(super) blur_composite_frag_shader: Rc, + pub(super) blur_composite_descriptor_set_layout: Rc, + pub(super) blur_composite_pipelines: CopyHashMap>, pub(super) defunct: Cell, pub(super) pending_cpu_jobs: CopyHashMap, pub(super) shm_allocator: Rc, @@ -126,6 +131,7 @@ pub struct VulkanRenderer { pub(super) sampler_descriptor_buffer_cache: Rc, pub(super) resource_descriptor_buffer_cache: Rc, pub(super) blend_buffers: RefCell>>, + pub(super) blur_scratch: RefCell>>, pub(super) shader_buffer_cache: Rc, pub(super) uniform_buffer_cache: Rc, pub(super) render_tls: Option>, @@ -207,6 +213,7 @@ pub(super) struct Memory { blend_buffer_eotf_args_address: Option, blend_buffer_inv_eotf_args_address: Option, fb_inv_eotf_args_address: Option, + blur_scratch: Vec>, } type Point = [[f32; 2]; 4]; @@ -216,6 +223,19 @@ enum VulkanOp { Tex(VulkanTexOp), RoundedFill(VulkanRoundedFillOp), RoundedTex(VulkanRoundedTexOp), + BlurBarrier(VulkanBlurOp), +} + +struct VulkanBlurOp { + rect: crate::gfx_api::FramebufferRect, + passes: u8, + mask: Option, +} + +struct VulkanBlurMask { + tex: Rc, + source: crate::gfx_api::SampleRect, + threshold: f32, } struct VulkanTexOp { @@ -300,6 +320,7 @@ pub(super) struct PendingFrame { _fb: Rc, _bb: Option>, _textures: Vec, + _blur_scratch: Vec>, wait_semaphores: Cell>>, waiter: Cell>>, vulkan_sync: Option, @@ -381,6 +402,10 @@ impl VulkanDevice { .as_ref() .map(|_| self.create_tex_resource_descriptor_set_layout()) .transpose()?; + let blur_composite_vert_shader = self.create_shader(BLUR_COMPOSITE_VERT)?; + let blur_composite_frag_shader = self.create_shader(BLUR_COMPOSITE_FRAG)?; + let blur_composite_descriptor_set_layout = + self.create_blur_composite_descriptor_set_layout(&sampler)?; let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; let transfer_command_buffers = self .distinct_transfer_queue_family_idx @@ -468,6 +493,10 @@ impl VulkanDevice { rounded_tex_pipelines: Default::default(), tex_descriptor_set_layouts, out_descriptor_set_layout, + blur_composite_vert_shader, + blur_composite_frag_shader, + blur_composite_descriptor_set_layout, + blur_composite_pipelines: Default::default(), defunct: Cell::new(false), pending_cpu_jobs: Default::default(), shm_allocator, @@ -476,6 +505,7 @@ impl VulkanDevice { sampler_descriptor_buffer_cache, resource_descriptor_buffer_cache, blend_buffers: Default::default(), + blur_scratch: Default::default(), shader_buffer_cache, uniform_buffer_cache, render_tls: self.create_timeline_semaphore_or_log(), @@ -741,6 +771,121 @@ impl VulkanRenderer { Ok(out) } + pub(super) fn get_or_create_blur_composite_pipeline( + &self, + format: vk::Format, + ) -> Result, VulkanError> { + if let Some(pl) = self.blur_composite_pipelines.get(&format) { + return Ok(pl); + } + let pl = self.create_blur_composite_pipeline(format)?; + self.blur_composite_pipelines.set(format, pl.clone()); + Ok(pl) + } + + fn create_blur_composite_pipeline( + &self, + format: vk::Format, + ) -> Result, VulkanError> { + use ash::vk::{ + BlendFactor, BlendOp, ColorComponentFlags, CullModeFlags, DynamicState, FrontFace, + GraphicsPipelineCreateInfo, PipelineCache, PipelineColorBlendAttachmentState, + PipelineColorBlendStateCreateInfo, PipelineDynamicStateCreateInfo, + PipelineInputAssemblyStateCreateInfo, PipelineLayoutCreateInfo, + PipelineMultisampleStateCreateInfo, PipelineRasterizationStateCreateInfo, + PipelineRenderingCreateInfo, PipelineShaderStageCreateInfo, + PipelineVertexInputStateCreateInfo, PipelineViewportStateCreateInfo, PolygonMode, + PrimitiveTopology, PushConstantRange, SampleCountFlags, + }; + let dev = &self.device.device; + let push_range = PushConstantRange::default() + .stage_flags(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT) + .offset(0) + .size(size_of::() as u32); + let set_layouts = [self.blur_composite_descriptor_set_layout.layout]; + let layout_info = PipelineLayoutCreateInfo::default() + .push_constant_ranges(slice::from_ref(&push_range)) + .set_layouts(&set_layouts); + let pipeline_layout = unsafe { dev.create_pipeline_layout(&layout_info, None) }; + let pipeline_layout = pipeline_layout.map_err(VulkanError::CreatePipelineLayout)?; + let destroy_layout = + run_on_drop::on_drop(|| unsafe { dev.destroy_pipeline_layout(pipeline_layout, None) }); + let stages = [ + PipelineShaderStageCreateInfo::default() + .stage(ShaderStageFlags::VERTEX) + .module(self.blur_composite_vert_shader.module) + .name(c"main"), + PipelineShaderStageCreateInfo::default() + .stage(ShaderStageFlags::FRAGMENT) + .module(self.blur_composite_frag_shader.module) + .name(c"main"), + ]; + let input_assembly_state = PipelineInputAssemblyStateCreateInfo::default() + .topology(PrimitiveTopology::TRIANGLE_STRIP); + let vertex_input_state = PipelineVertexInputStateCreateInfo::default(); + let rasterization_state = PipelineRasterizationStateCreateInfo::default() + .polygon_mode(PolygonMode::FILL) + .cull_mode(CullModeFlags::NONE) + .line_width(1.0) + .front_face(FrontFace::COUNTER_CLOCKWISE); + let multisampling_state = PipelineMultisampleStateCreateInfo::default() + .sample_shading_enable(false) + .rasterization_samples(SampleCountFlags::TYPE_1); + let blending = PipelineColorBlendAttachmentState::default() + .color_write_mask(ColorComponentFlags::RGBA) + .blend_enable(true) + .src_color_blend_factor(BlendFactor::ONE) + .dst_color_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .color_blend_op(BlendOp::ADD) + .src_alpha_blend_factor(BlendFactor::ONE) + .dst_alpha_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .alpha_blend_op(BlendOp::ADD); + let color_blend_state = PipelineColorBlendStateCreateInfo::default() + .attachments(slice::from_ref(&blending)); + let dynamic_states = [DynamicState::VIEWPORT, DynamicState::SCISSOR]; + let dynamic_state = + PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); + let viewport_state = PipelineViewportStateCreateInfo::default() + .viewport_count(1) + .scissor_count(1); + let mut pipeline_rendering_create_info = PipelineRenderingCreateInfo::default() + .color_attachment_formats(slice::from_ref(&format)); + let create_info = GraphicsPipelineCreateInfo::default() + .push_next(&mut pipeline_rendering_create_info) + .stages(&stages) + .input_assembly_state(&input_assembly_state) + .vertex_input_state(&vertex_input_state) + .rasterization_state(&rasterization_state) + .multisample_state(&multisampling_state) + .color_blend_state(&color_blend_state) + .dynamic_state(&dynamic_state) + .viewport_state(&viewport_state) + .layout(pipeline_layout); + let pipelines = unsafe { + dev.create_graphics_pipelines( + PipelineCache::null(), + slice::from_ref(&create_info), + None, + ) + }; + let mut pipelines = pipelines + .map_err(|e| e.1) + .map_err(VulkanError::CreatePipeline)?; + let pipeline = pipelines.pop().unwrap(); + destroy_layout.forget(); + Ok(Rc::new(VulkanPipeline { + vert: self.blur_composite_vert_shader.clone(), + _frag: self.blur_composite_frag_shader.clone(), + pipeline_layout, + pipeline, + _descriptor_set_layouts: { + let mut v = ArrayVec::new(); + v.push(self.blur_composite_descriptor_set_layout.clone()); + v + }, + })) + } + pub(super) fn allocate_point(&self) -> u64 { self.last_point.fetch_add(1) + 1 } @@ -936,6 +1081,9 @@ impl VulkanRenderer { }, VulkanOp::Tex(t) => Key::Tex(t.index), VulkanOp::RoundedTex(t) => Key::Tex(t.index), + VulkanOp::BlurBarrier(_) => unreachable!( + "BlurBarrier ops are pushed directly to mops, never ops_tmp" + ), } }); let mops = &mut memory.ops[pass]; @@ -989,6 +1137,9 @@ impl VulkanRenderer { .extend_from_slice(uapi::as_bytes(&vertex)); mops.push(VulkanOp::RoundedTex(c)); } + VulkanOp::BlurBarrier(_) => unreachable!( + "BlurBarrier ops are pushed directly to mops, never ops_tmp" + ), } } } @@ -1224,6 +1375,37 @@ impl VulkanRenderer { })); } } + GfxApiOpt::BlurBackdrop(b) => { + // Flush all pending ops in original order, then push a + // barrier op to FrameBuffer pass that will end + restart + // the render pass to do the blur work in between. + sync(memory); + let mask = if let Some(m) = &b.mask { + let tex = m.texture.clone().into_vk(&self.device.device)?; + if tex.contents_are_undefined.get() { + None + } else if tex.queue_state.get().acquire(QueueFamily::Gfx) + == QueueTransfer::Impossible + { + None + } else { + Some(VulkanBlurMask { + tex, + source: m.source, + threshold: m.threshold, + }) + } + } else { + None + }; + memory.ops[RenderPass::FrameBuffer].push(VulkanOp::BlurBarrier( + VulkanBlurOp { + rect: b.rect, + passes: b.passes, + mask, + }, + )); + } } } sync(memory); @@ -1311,6 +1493,7 @@ impl VulkanRenderer { VulkanOp::RoundedTex(c) => { c.range_address += buffer.buffer.address; } + VulkanOp::BlurBarrier(_) => {} } } } @@ -1404,6 +1587,20 @@ impl VulkanRenderer { acquire_sync: acquire_sync.take().unwrap(), release_sync, }); + } else if let VulkanOp::BlurBarrier(b) = cmd + && let Some(m) = &b.mask + { + let tex = &m.tex; + if tex.execution_version.replace(execution) != execution { + match tex.queue_state.get().acquire(QueueFamily::Gfx) { + QueueTransfer::Unnecessary => {} + QueueTransfer::Possible => memory.queue_transfer.push(tex.clone()), + QueueTransfer::Impossible => {} + } + if let VulkanImageMemory::DmaBuf(_) = &tex.ty { + memory.dmabuf_sample.push(tex.clone()) + } + } } } } @@ -1625,24 +1822,27 @@ impl VulkanRenderer { } fn record_draws( - &self, + self: &Rc, buf: CommandBuffer, target: &VulkanImage, pass: RenderPass, target_cd: &ColorDescription, ) -> Result<(), VulkanError> { zone!("record_draws"); - let memory = &*self.memory.borrow(); + let mut local_blur_scratch: Vec> = vec![]; + let memory_ref = self.memory.borrow(); + let memory = &*memory_ref; let fill_pl = self.get_or_create_fill_pipelines(target.format.vk_format)?; let tex_pl = self.get_or_create_tex_pipelines(target.format.vk_format, target_cd); let rounded_fill_pl = self.get_or_create_rounded_fill_pipelines(target.format.vk_format)?; let rounded_tex_pl = self.get_or_create_rounded_tex_pipelines(target.format.vk_format, target_cd); let dev = &self.device.device; - let mut current_pipeline = None; - let mut bind = |pipeline: &VulkanPipeline| { - if current_pipeline != Some(pipeline.pipeline) { - current_pipeline = Some(pipeline.pipeline); + let current_pipeline: Cell> = Cell::new(None); + let cp = ¤t_pipeline; + let bind = |pipeline: &VulkanPipeline| { + if cp.get() != Some(pipeline.pipeline) { + cp.set(Some(pipeline.pipeline)); unsafe { dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); } @@ -1910,8 +2110,67 @@ impl VulkanRenderer { } } } + VulkanOp::BlurBarrier(blur) => { + // Blur is only meaningful in the FrameBuffer pass. + if pass != RenderPass::FrameBuffer { + continue; + } + // End the current dynamic render pass, run the blur work + // (image-blit cascade between scratch images), and resume + // the render pass with LOAD so subsequent draws layer on + // top of the blurred backdrop. + unsafe { + dev.cmd_end_rendering(buf); + } + let pix = blur + .rect + .to_rect(target.width as f32, target.height as f32); + let rect_arr = [pix.x1(), pix.y1(), pix.x2(), pix.y2()]; + let mask_record = blur.mask.as_ref().map(|m| { + crate::gfx_apis::vulkan::blur::BlurMaskRecord { + mask_view: m.tex.texture_view, + mask_source_points: m.source.to_points(), + target_points: blur.rect.to_points(), + threshold: m.threshold, + _phantom: std::marker::PhantomData, + } + }); + self.record_blur( + buf, + target, + rect_arr, + blur.passes, + &mut local_blur_scratch, + mask_record.as_ref(), + )?; + self.begin_rendering_load(buf, target); + // Pipeline state is invalidated across the render-pass + // break — force re-bind on next draw. + cp.set(None); + // Restore viewport/scissor (dynamic state may persist + // across rendering scope, but be safe). + let viewport = Viewport { + x: 0.0, + y: 0.0, + width: target.width as _, + height: target.height as _, + min_depth: 0.0, + max_depth: 1.0, + }; + unsafe { + dev.cmd_set_viewport(buf, 0, slice::from_ref(&viewport)); + dev.cmd_set_scissor(buf, 0, slice::from_ref(&full_scissor)); + } + } } } + drop(memory_ref); + if !local_blur_scratch.is_empty() { + self.memory + .borrow_mut() + .blur_scratch + .extend(local_blur_scratch); + } Ok(()) } @@ -2007,6 +2266,27 @@ impl VulkanRenderer { } } + fn begin_rendering_load(&self, buf: CommandBuffer, target: &VulkanImage) { + let attachment = RenderingAttachmentInfo::default() + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .image_view(target.render_view.unwrap_or(target.texture_view)) + .store_op(AttachmentStoreOp::STORE) + .load_op(AttachmentLoadOp::LOAD); + let info = RenderingInfo::default() + .render_area(Rect2D { + offset: Default::default(), + extent: Extent2D { + width: target.width, + height: target.height, + }, + }) + .layer_count(1) + .color_attachments(slice::from_ref(&attachment)); + unsafe { + self.device.device.cmd_begin_rendering(buf, &info); + } + } + fn copy_bridge_to_dmabuf(&self, buf: CommandBuffer, fb: &VulkanImage, region: &Region) { zone!("copy_bridge_to_dmabuf"); let Some(bridge) = &fb.bridge else { @@ -2318,6 +2598,7 @@ impl VulkanRenderer { _fb: fb.clone(), _bb: bb, _textures: mem::take(&mut memory.textures), + _blur_scratch: mem::take(&mut memory.blur_scratch), wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)), waiter: Cell::new(None), vulkan_sync: memory.release_vulkan_sync.take(), @@ -2428,6 +2709,7 @@ impl VulkanRenderer { (false, rf.rect) } GfxApiOpt::RoundedCopyTexture(ct) => (false, ct.target), + GfxApiOpt::BlurBackdrop(_) => continue, }; if opaque || bb.is_none() { tag |= 1; diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index c09c01c5..607fd49a 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -19,6 +19,8 @@ pub const ROUNDED_FILL_VERT: &[u8] = include_bytes!("shaders_bin/rounded_fill.ve pub const ROUNDED_FILL_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_fill.frag.spv"); pub const ROUNDED_TEX_VERT: &[u8] = include_bytes!("shaders_bin/rounded_tex.vert.spv"); pub const ROUNDED_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_tex.frag.spv"); +pub const BLUR_COMPOSITE_VERT: &[u8] = include_bytes!("shaders_bin/blur_composite.vert.spv"); +pub const BLUR_COMPOSITE_FRAG: &[u8] = include_bytes!("shaders_bin/blur_composite.frag.spv"); pub const LEGACY_ROUNDED_FILL_VERT: &[u8] = include_bytes!("shaders_bin/legacy_rounded_fill.vert.spv"); pub const LEGACY_ROUNDED_FILL_FRAG: &[u8] = @@ -182,6 +184,17 @@ pub struct OutPushConstants { unsafe impl Packed for OutPushConstants {} +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct BlurCompositePushConstants { + pub pos: [[f32; 2]; 4], + pub blurred_tex_pos: [[f32; 2]; 4], + pub mask_tex_pos: [[f32; 2]; 4], + pub threshold: f32, +} + +unsafe impl Packed for BlurCompositePushConstants {} + impl VulkanDevice { pub(super) fn create_shader( self: &Rc, diff --git a/src/gfx_apis/vulkan/shaders/blur_composite.frag b/src/gfx_apis/vulkan/shaders/blur_composite.frag new file mode 100644 index 00000000..a6fdfc0d --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/blur_composite.frag @@ -0,0 +1,22 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D blurred_tex; +layout(set = 0, binding = 1) uniform sampler2D mask_tex; + +layout(push_constant, std430) uniform Data { + layout(offset = 96) float threshold; +} data; + +layout(location = 0) in vec2 v_blurred_tex_pos; +layout(location = 1) in vec2 v_mask_tex_pos; +layout(location = 0) out vec4 out_color; + +void main() { + vec3 blurred = textureLod(blurred_tex, v_blurred_tex_pos, 0).rgb; + vec2 uv = v_mask_tex_pos; + float in_range = step(0.0, uv.x) * step(uv.x, 1.0) + * step(0.0, uv.y) * step(uv.y, 1.0); + float a = textureLod(mask_tex, clamp(uv, 0.0, 1.0), 0).a * in_range; + float weight = smoothstep(0.0, max(data.threshold, 0.001), a); + out_color = vec4(blurred * weight, weight); +} diff --git a/src/gfx_apis/vulkan/shaders/blur_composite.vert b/src/gfx_apis/vulkan/shaders/blur_composite.vert new file mode 100644 index 00000000..18f71919 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/blur_composite.vert @@ -0,0 +1,38 @@ +#version 450 + +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; + layout(offset = 32) vec2 blurred_tex_pos[4]; + layout(offset = 64) vec2 mask_tex_pos[4]; + layout(offset = 96) float threshold; +} data; + +layout(location = 0) out vec2 v_blurred_tex_pos; +layout(location = 1) out vec2 v_mask_tex_pos; + +void main() { + vec2 p; + switch (gl_VertexIndex) { + case 0: + p = data.pos[0]; + v_blurred_tex_pos = data.blurred_tex_pos[0]; + v_mask_tex_pos = data.mask_tex_pos[0]; + break; + case 1: + p = data.pos[1]; + v_blurred_tex_pos = data.blurred_tex_pos[1]; + v_mask_tex_pos = data.mask_tex_pos[1]; + break; + case 2: + p = data.pos[2]; + v_blurred_tex_pos = data.blurred_tex_pos[2]; + v_mask_tex_pos = data.mask_tex_pos[2]; + break; + case 3: + p = data.pos[3]; + v_blurred_tex_pos = data.blurred_tex_pos[3]; + v_mask_tex_pos = data.mask_tex_pos[3]; + break; + } + gl_Position = vec4(p, 0.0, 1.0); +} diff --git a/src/gfx_apis/vulkan/shaders_bin/blur_composite.frag.spv b/src/gfx_apis/vulkan/shaders_bin/blur_composite.frag.spv new file mode 100644 index 00000000..7f68ffb9 Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/blur_composite.frag.spv differ diff --git a/src/gfx_apis/vulkan/shaders_bin/blur_composite.vert.spv b/src/gfx_apis/vulkan/shaders_bin/blur_composite.vert.spv new file mode 100644 index 00000000..f574440c Binary files /dev/null and b/src/gfx_apis/vulkan/shaders_bin/blur_composite.vert.spv differ diff --git a/src/gfx_apis/vulkan/shaders_hash.txt b/src/gfx_apis/vulkan/shaders_hash.txt index 1923f498..de378df5 100644 --- a/src/gfx_apis/vulkan/shaders_hash.txt +++ b/src/gfx_apis/vulkan/shaders_hash.txt @@ -1,4 +1,6 @@ 302a9f250bdc4f8e0e71a9f77c9a8a7aa55fd003bc91c2422a700c4abd83f54e src/gfx_apis/vulkan/shaders/alpha_modes.glsl +16ad6f1eb029ccce5e0204a7d79709b05a8a708133feaf8bb20a24371de25ed7 src/gfx_apis/vulkan/shaders/blur_composite.frag +6399e23afa2e07c98b9fd1a4e853ea974a9958547ce65734846483bd7cbc8461 src/gfx_apis/vulkan/shaders/blur_composite.vert b6a0df1e231fab533499329636b7a580384784418baee06c147af5fcc384cf5c src/gfx_apis/vulkan/shaders/eotfs.glsl 8a38df18851cd13884499820f26939fb7319f45d913d867f254d8118d59fb117 src/gfx_apis/vulkan/shaders/fill.common.glsl 21c488d12aa5ad2f109ec44cb856dfe837e02ea9025b5ed64439d742c17cbf30 src/gfx_apis/vulkan/shaders/fill.frag diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 14b131fe..96d3c332 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -57,6 +57,18 @@ pub trait XdgPopupParent { fn allow_popup_focus(&self) -> bool { false } + /// If the popup descends from a layer surface with `blur_popups` or + /// `ignore_alpha` set, returns those settings so they can be applied + /// when rendering the popup. + fn layer_blur_settings(&self) -> Option { + None + } +} + +#[derive(Copy, Clone, Default)] +pub struct LayerPopupBlur { + pub blur: bool, + pub ignore_alpha: Option, } pub struct XdgPopup { @@ -406,7 +418,38 @@ impl Node for XdgPopup { } fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) { - renderer.render_xdg_surface(&self.xdg, x, y, bounds) + let settings = self + .parent + .get() + .and_then(|p| p.layer_blur_settings()); + if let Some(s) = settings { + if s.blur { + let extents = self.xdg.surface.extents.get(); + let geo = self.xdg.geometry(); + let (gx, gy) = geo.translate(x, y); + let rect = extents.move_(gx, gy); + let scaled = renderer.base.scale_rect(rect); + let cfg = renderer.state.blur_config.get(); + let mask = s.ignore_alpha.and_then(|threshold| { + let buffer = self.xdg.surface.buffer.get()?; + let texture = buffer.buffer.buf.get_texture(&self.xdg.surface)?; + let source = *self.xdg.surface.buffer_points_norm.borrow(); + Some(crate::gfx_api::BlurMask { + texture, + source, + threshold, + }) + }); + renderer + .base + .push_blur_backdrop(scaled, cfg.passes, cfg.size, mask); + } + renderer.base.discard_alpha = s.ignore_alpha; + renderer.render_xdg_surface(&self.xdg, x, y, bounds); + renderer.base.discard_alpha = None; + } else { + renderer.render_xdg_surface(&self.xdg, x, y, bounds); + } } fn node_client(&self) -> Option> { diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index ad8e40e8..49087454 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -53,12 +53,15 @@ pub struct ZwlrLayerSurfaceV1 { pub client: Rc, pub surface: Rc, pub output: Rc, - pub _namespace: String, + pub namespace: String, pub tracker: Tracker, output_extents: Cell, pos: Cell, mapped: Cell, layer: Cell, + pub blur: Cell, + pub blur_popups: Cell, + pub ignore_alpha: Cell>, requested_serial: NumCell, size: Cell<(i32, i32)>, anchor: Cell, @@ -160,12 +163,15 @@ impl ZwlrLayerSurfaceV1 { client: shell.client.clone(), surface: surface.clone(), output: output.clone(), - _namespace: namespace.to_string(), + namespace: namespace.to_string(), tracker: Default::default(), output_extents: Default::default(), pos: Default::default(), mapped: Cell::new(false), layer: Cell::new(layer), + blur: Cell::new(false), + blur_popups: Cell::new(false), + ignore_alpha: Cell::new(None), requested_serial: Default::default(), size: Cell::new((0, 0)), anchor: Cell::new(0), @@ -192,9 +198,42 @@ impl ZwlrLayerSurfaceV1 { self.surface .set_output(&output, NodeLocation::Output(output.id)); } + self.apply_layer_rules(); Ok(()) } + fn apply_layer_rules(&self) { + use jay_config::_private::LayerKindIpc; + let rules = self.client.state.layer_rules.borrow(); + let my_layer = match self.layer.get() { + crate::ifs::zwlr_layer_shell_v1::BACKGROUND => LayerKindIpc::Background, + crate::ifs::zwlr_layer_shell_v1::BOTTOM => LayerKindIpc::Bottom, + crate::ifs::zwlr_layer_shell_v1::TOP => LayerKindIpc::Top, + _ => LayerKindIpc::Overlay, + }; + for rule in rules.iter() { + if let Some(ns) = &rule.match_.namespace + && ns != &self.namespace + { + continue; + } + if let Some(layer) = rule.match_.layer + && layer != my_layer + { + continue; + } + if let Some(b) = rule.blur { + self.blur.set(b); + } + if let Some(b) = rule.blur_popups { + self.blur_popups.set(b); + } + if let Some(a) = rule.ignore_alpha { + self.ignore_alpha.set(Some(a)); + } + } + } + fn send_configure(&self, serial: u32, width: u32, height: u32) { self.client.event(Configure { self_id: self.id, @@ -773,6 +812,24 @@ impl XdgPopupParent for Popup { }; NodeLayerLink::StackedAboveLayers(link) } + + fn layer_blur_settings( + &self, + ) -> Option { + let blur = self.parent.blur.get() && self.parent.blur_popups.get(); + let ignore_alpha = if self.parent.blur_popups.get() { + self.parent.ignore_alpha.get() + } else { + None + }; + if !blur && ignore_alpha.is_none() { + return None; + } + Some(crate::ifs::wl_surface::xdg_surface::xdg_popup::LayerPopupBlur { + blur, + ignore_alpha, + }) + } } object_base! { diff --git a/src/renderer.rs b/src/renderer.rs index 543757e7..ddb5ab7c 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -852,7 +852,29 @@ impl Renderer<'_> { pub fn render_layer_surface(&mut self, surface: &ZwlrLayerSurfaceV1, x: i32, y: i32) { let (dx, dy) = surface.surface.extents.get().position(); + let blur = surface.blur.get(); + let ignore_alpha = surface.ignore_alpha.get(); + if blur { + let extents = surface.surface.extents.get(); + let rect = extents.move_(x - dx, y - dy); + let scaled = self.base.scale_rect(rect); + let cfg = self.state.blur_config.get(); + let mask = ignore_alpha.and_then(|threshold| { + let buffer = surface.surface.buffer.get()?; + let texture = buffer.buffer.buf.get_texture(&surface.surface)?; + let source = *surface.surface.buffer_points_norm.borrow(); + Some(crate::gfx_api::BlurMask { + texture, + source, + threshold, + }) + }); + self.base + .push_blur_backdrop(scaled, cfg.passes, cfg.size, mask); + } + self.base.discard_alpha = ignore_alpha; self.render_surface(&surface.surface, x - dx, y - dy, None); + self.base.discard_alpha = None; } fn bounds_are_opaque( diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index 221ae7b7..f2c95265 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -5,8 +5,9 @@ use { cmm_render_intent::RenderIntent, }, gfx_api::{ - AcquireSync, AlphaMode, BufferResv, CopyTexture, FillRect, FramebufferRect, GfxApiOpt, - GfxTexture, ReleaseSync, RoundedCopyTexture, RoundedFillRect, SampleRect, + AcquireSync, AlphaMode, BlurBackdrop, BlurMask, BufferResv, CopyTexture, FillRect, + FramebufferRect, GfxApiOpt, GfxTexture, ReleaseSync, RoundedCopyTexture, + RoundedFillRect, SampleRect, }, rect::Rect, scale::Scale, @@ -24,6 +25,7 @@ pub struct RendererBase<'a> { pub transform: Transform, pub fb_width: f32, pub fb_height: f32, + pub discard_alpha: Option, } impl RendererBase<'_> { @@ -247,6 +249,7 @@ impl RendererBase<'_> { render_intent, cd: cd.clone(), alpha_mode, + discard_alpha: self.discard_alpha, })); } @@ -387,12 +390,37 @@ impl RendererBase<'_> { size: [width, height], corner_radius: cr, scale, + discard_alpha: self.discard_alpha, })); } pub fn sync(&mut self) { self.ops.push(GfxApiOpt::Sync); } + + pub fn push_blur_backdrop( + &mut self, + rect: Rect, + passes: u8, + offset: f32, + mask: Option, + ) { + let target = FramebufferRect::new( + rect.x1() as f32, + rect.y1() as f32, + rect.x2() as f32, + rect.y2() as f32, + self.transform, + self.fb_width, + self.fb_height, + ); + self.ops.push(GfxApiOpt::BlurBackdrop(BlurBackdrop { + rect: target, + passes, + offset, + mask, + })); + } } #[inline] diff --git a/src/state.rs b/src/state.rs index 957ba369..53acf724 100644 --- a/src/state.rs +++ b/src/state.rs @@ -305,6 +305,8 @@ pub struct State { pub clean_logs_older_than: Cell>, pub hyprland_global_shortcuts: CopyHashMap<(String, String), Rc>, + pub layer_rules: RefCell>, + pub blur_config: Cell, } // impl Drop for State { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 60a336f7..66326d57 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -365,6 +365,34 @@ pub struct WindowMatch { pub content_types: Option, } +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum LayerKind { + Background, + Bottom, + Top, + Overlay, +} + +#[derive(Default, Debug, Clone)] +pub struct LayerMatch { + pub namespace: Option, + pub layer: Option, +} + +#[derive(Debug, Clone)] +pub struct LayerRule { + pub match_: LayerMatch, + pub blur: Option, + pub blur_popups: Option, + pub ignore_alpha: Option, +} + +#[derive(Debug, Clone, Copy)] +pub struct BlurConfig { + pub passes: Option, + pub size: Option, +} + #[derive(Debug, Clone)] pub enum DrmDeviceMatch { Any(Vec), @@ -577,6 +605,8 @@ pub struct Config { pub max_action_depth: u64, pub client_rules: Vec, pub window_rules: Vec, + pub layer_rules: Vec, + pub blur: Option, pub pointer_revert_key: Option, pub use_hardware_cursor: Option, pub show_bar: Option, diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index 4c5e337b..766522b3 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -8,6 +8,7 @@ use { pub mod action; mod actions; +mod blur; mod capabilities; mod clean_logs_older_than; mod client_match; @@ -32,6 +33,8 @@ mod input; mod input_match; pub mod input_mode; pub mod keymap; +mod layer_match; +mod layer_rule; mod libei; mod log_level; pub mod mark_id; diff --git a/toml-config/src/config/parsers/blur.rs b/toml-config/src/config/parsers/blur.rs new file mode 100644 index 00000000..04aa1522 --- /dev/null +++ b/toml-config/src/config/parsers/blur.rs @@ -0,0 +1,45 @@ +use { + crate::{ + config::{ + BlurConfig, + context::Context, + extractor::{Extractor, ExtractorError, fltorint, int, opt}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + }, + toml::{ + toml_span::{DespanExt, Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum BlurConfigParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), +} + +pub struct BlurConfigParser<'a>(pub &'a Context<'a>); + +impl Parser for BlurConfigParser<'_> { + type Value = BlurConfig; + type Error = BlurConfigParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (passes_val, size_val) = + ext.extract((opt(int("passes")), opt(fltorint("size"))))?; + let passes = passes_val.despan().and_then(|v| u8::try_from(v).ok()); + let size = size_val.despan().map(|v| v as f32); + Ok(BlurConfig { passes, size }) + } +} diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index b9d34e74..b27eb938 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -23,6 +23,8 @@ use { input::InputsParser, input_mode::InputModesParser, keymap::KeymapParser, + blur::BlurConfigParser, + layer_rule::LayerRulesParser, libei::LibeiParser, log_level::LogLevelParser, output::OutputsParser, @@ -153,7 +155,9 @@ impl Parser for ConfigParser<'_> { fallback_output_mode_val, clean_logs_older_than_val, mouse_follows_focus, + layer_rules_val, ), + (blur_val,), ) = ext.extract(( ( opt(val("keymap")), @@ -213,7 +217,9 @@ impl Parser for ConfigParser<'_> { opt(val("fallback-output-mode")), opt(val("clean-logs-older-than")), recover(opt(bol("unstable-mouse-follows-focus"))), + opt(val("layers")), ), + (opt(val("blur")),), ))?; let mut keymap = None; if let Some(value) = keymap_val { @@ -495,6 +501,20 @@ impl Parser for ConfigParser<'_> { Err(e) => log::warn!("Could not parse the window rules: {}", self.0.error(e)), } } + let mut layer_rules = vec![]; + if let Some(value) = layer_rules_val { + match value.parse(&mut LayerRulesParser(self.0)) { + Ok(v) => layer_rules = v, + Err(e) => log::warn!("Could not parse the layer rules: {}", self.0.error(e)), + } + } + let mut blur = None; + if let Some(value) = blur_val { + match value.parse(&mut BlurConfigParser(self.0)) { + Ok(v) => blur = Some(v), + Err(e) => log::warn!("Could not parse the blur config: {}", self.0.error(e)), + } + } let mut pointer_revert_key = None; if let Some(value) = pointer_revert_key_str { match Keysym::from_str(value.value) { @@ -594,6 +614,8 @@ impl Parser for ConfigParser<'_> { max_action_depth, client_rules, window_rules, + layer_rules, + blur, pointer_revert_key, use_hardware_cursor: use_hardware_cursor.despan(), show_bar: show_bar.despan(), diff --git a/toml-config/src/config/parsers/layer_match.rs b/toml-config/src/config/parsers/layer_match.rs new file mode 100644 index 00000000..e285e788 --- /dev/null +++ b/toml-config/src/config/parsers/layer_match.rs @@ -0,0 +1,70 @@ +use { + crate::{ + config::{ + LayerKind, LayerMatch, + context::Context, + extractor::{Extractor, ExtractorError, opt, str, val}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + }, + toml::{ + toml_span::{DespanExt, Span, Spanned, SpannedExt}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum LayerMatchParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), + #[error("Unknown layer `{0}` (expected background, bottom, top, or overlay)")] + UnknownLayer(String), +} + +pub struct LayerMatchParser<'a>(pub &'a Context<'a>); + +impl Parser for LayerMatchParser<'_> { + type Value = LayerMatch; + type Error = LayerMatchParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (namespace, layer_val) = ext.extract((opt(str("namespace")), opt(val("layer"))))?; + let mut layer = None; + if let Some(value) = layer_val { + layer = Some(value.parse(&mut LayerKindParser)?); + } + Ok(LayerMatch { + namespace: namespace.despan_into(), + layer, + }) + } +} + +pub struct LayerKindParser; + +impl Parser for LayerKindParser { + type Value = LayerKind; + type Error = LayerMatchParserError; + const EXPECTED: &'static [DataType] = &[DataType::String]; + + fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { + let kind = match string { + "background" => LayerKind::Background, + "bottom" => LayerKind::Bottom, + "top" => LayerKind::Top, + "overlay" => LayerKind::Overlay, + _ => return Err(LayerMatchParserError::UnknownLayer(string.to_string()).spanned(span)), + }; + Ok(kind) + } +} diff --git a/toml-config/src/config/parsers/layer_rule.rs b/toml-config/src/config/parsers/layer_rule.rs new file mode 100644 index 00000000..32b2a9bc --- /dev/null +++ b/toml-config/src/config/parsers/layer_rule.rs @@ -0,0 +1,81 @@ +use { + crate::{ + config::{ + LayerMatch, LayerRule, + context::Context, + extractor::{Extractor, ExtractorError, bol, fltorint, opt, val}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + parsers::layer_match::{LayerMatchParser, LayerMatchParserError}, + }, + toml::{ + toml_span::{DespanExt, Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum LayerRuleParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), + #[error(transparent)] + Match(#[from] LayerMatchParserError), +} + +pub struct LayerRuleParser<'a>(pub &'a Context<'a>); + +impl Parser for LayerRuleParser<'_> { + type Value = LayerRule; + type Error = LayerRuleParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (match_val, blur, blur_popups, ignore_alpha) = ext.extract(( + opt(val("match")), + opt(bol("blur")), + opt(bol("blur-popups")), + opt(fltorint("ignore-alpha")), + ))?; + let match_ = match match_val { + None => LayerMatch::default(), + Some(m) => m.parse_map(&mut LayerMatchParser(self.0))?, + }; + let ignore_alpha = ignore_alpha.map(|s| s.value.clamp(0.0, 1.0) as f32); + Ok(LayerRule { + match_, + blur: blur.despan(), + blur_popups: blur_popups.despan(), + ignore_alpha, + }) + } +} + +pub struct LayerRulesParser<'a>(pub &'a Context<'a>); + +impl Parser for LayerRulesParser<'_> { + type Value = Vec; + type Error = LayerRuleParserError; + const EXPECTED: &'static [DataType] = &[DataType::Array]; + + fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { + let mut res = vec![]; + for el in array { + match el.parse(&mut LayerRuleParser(self.0)) { + Ok(o) => res.push(o), + Err(e) => { + log::warn!("Could not parse layer rule: {}", self.0.error(e)); + } + } + } + Ok(res) + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 4c95fd41..538aa815 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -14,8 +14,9 @@ use { crate::{ config::{ Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap, - ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch, - SimpleCommand, Status, Theme, WindowRule, parse_config, + BlurConfig, ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, LayerKind, + LayerRule, Output, + OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config, }, rules::{MatcherTemp, RuleMapper}, shortcuts::ModeState, @@ -37,6 +38,8 @@ use { is_reload, keyboard::Keymap, logging::{clean_logs_older_than, set_log_level}, + _set_blur_config, _set_layer_rules, + _private::{BlurConfigIpc, LayerKindIpc, LayerMatchIpc, LayerRuleIpc}, on_devices_enumerated, on_idle, on_unload, quit, reload, set_autotile, set_color_management_enabled, set_corner_radius, set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle, @@ -1470,6 +1473,8 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc) { + let default = BlurConfigIpc::default(); + let cfg = match blur { + Some(b) => BlurConfigIpc { + passes: b.passes.unwrap_or(default.passes).clamp(1, 8), + size: b.size.unwrap_or(default.size).max(0.0), + }, + None => default, + }; + _set_blur_config(cfg); +} + +fn push_layer_rules(rules: &[LayerRule]) { + let ipc: Vec = rules + .iter() + .map(|r| LayerRuleIpc { + match_: LayerMatchIpc { + namespace: r.match_.namespace.clone(), + layer: r.match_.layer.map(|k| match k { + LayerKind::Background => LayerKindIpc::Background, + LayerKind::Bottom => LayerKindIpc::Bottom, + LayerKind::Top => LayerKindIpc::Top, + LayerKind::Overlay => LayerKindIpc::Overlay, + }), + }, + blur: r.blur, + blur_popups: r.blur_popups, + ignore_alpha: r.ignore_alpha, + }) + .collect(); + _set_layer_rules(ipc); +} + fn create_command(exec: &Exec) -> Command { let mut command = Command::new(&exec.prog); for arg in &exec.args {