1
0
Fork 0
forked from wry/wry

Add blur to vulkan and opengl render pipelines

This commit is contained in:
entailz 2026-05-03 02:16:59 -07:00
parent 6d3bff952e
commit 0701c4e4cf
41 changed files with 1990 additions and 47 deletions

View file

@ -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",

View file

@ -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<String>,
pub layer: Option<LayerKindIpc>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LayerRuleIpc {
pub match_: LayerMatchIpc,
pub blur: Option<bool>,
pub blur_popups: Option<bool>,
pub ignore_alpha: Option<f32>,
}
#[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,
}
}
}

View file

@ -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<LayerRuleIpc>) {
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 })
}

View file

@ -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<LayerRuleIpc>,
},
SetBlurConfig {
config: BlurConfigIpc,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -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<crate::_private::LayerRuleIpc>) {
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()

View file

@ -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);

View file

@ -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(())
}

View file

@ -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<BlurMask>,
}
#[derive(Clone)]
pub struct BlurMask {
pub texture: Rc<dyn GfxTexture>,
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<ColorDescription>,
pub alpha_mode: AlphaMode,
pub discard_alpha: Option<f32>,
}
#[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<f32>,
}
#[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

View file

@ -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<FdSync> {
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::<GLuint>::with_capacity(passes as usize + 1);
let mut fbos = Vec::<GLuint>::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<EglImage>>, sync: &AcquireSync) {
let Some(sync_file) = sync.get_sync_file() else {
return;

View file

@ -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),

View file

@ -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<TexCopyType, StaticMap<TexSourceType, TexProg>>,
pub(crate) tex_external: Option<StaticMap<TexCopyType, StaticMap<TexSourceType, TexProg>>>,
pub(crate) tex_internal_discard: StaticMap<TexCopyType, TexProg>,
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<EglContext>, node: &Rc<CString>) -> Result<Self, RenderError> {
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") },

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -1,6 +1,7 @@
mod allocator;
mod alpha_modes;
mod blend_buffer;
mod blur;
mod bo_allocator;
mod buffer_cache;
mod command;

544
src/gfx_apis/vulkan/blur.rs Normal file
View file

@ -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<Self>,
width: u32,
height: u32,
format: ash::vk::Format,
) -> Result<Rc<VulkanImage>, 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<Self>,
buf: CommandBuffer,
target: &VulkanImage,
rect: [i32; 4],
passes: u8,
scratch_out: &mut Vec<Rc<VulkanImage>>,
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<Rc<VulkanImage>> =
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(())
}
}

View file

@ -54,6 +54,39 @@ impl VulkanDevice {
}))
}
pub(super) fn create_blur_composite_descriptor_set_layout(
self: &Rc<Self>,
sampler: &Rc<VulkanSampler>,
) -> Result<Rc<VulkanDescriptorSetLayout>, 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<Self>,
sampler: &Rc<VulkanSampler>,

View file

@ -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(

View file

@ -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<VulkanEotf, CopyHashMap<vk::Format, Rc<TexPipelines>>>,
pub(super) tex_descriptor_set_layouts: ArrayVec<Rc<VulkanDescriptorSetLayout>, 2>,
pub(super) out_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
pub(super) blur_composite_vert_shader: Rc<VulkanShader>,
pub(super) blur_composite_frag_shader: Rc<VulkanShader>,
pub(super) blur_composite_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
pub(super) blur_composite_pipelines: CopyHashMap<vk::Format, Rc<VulkanPipeline>>,
pub(super) defunct: Cell<bool>,
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
@ -126,6 +131,7 @@ pub struct VulkanRenderer {
pub(super) sampler_descriptor_buffer_cache: Rc<VulkanBufferCache>,
pub(super) resource_descriptor_buffer_cache: Rc<VulkanBufferCache>,
pub(super) blend_buffers: RefCell<AHashMap<(u32, u32), Weak<VulkanImage>>>,
pub(super) blur_scratch: RefCell<AHashMap<(u32, u32, vk::Format), Weak<VulkanImage>>>,
pub(super) shader_buffer_cache: Rc<VulkanBufferCache>,
pub(super) uniform_buffer_cache: Rc<VulkanBufferCache>,
pub(super) render_tls: Option<Rc<VulkanTimelineSemaphore>>,
@ -207,6 +213,7 @@ pub(super) struct Memory {
blend_buffer_eotf_args_address: Option<DeviceSize>,
blend_buffer_inv_eotf_args_address: Option<DeviceSize>,
fb_inv_eotf_args_address: Option<DeviceSize>,
blur_scratch: Vec<Rc<VulkanImage>>,
}
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<VulkanBlurMask>,
}
struct VulkanBlurMask {
tex: Rc<VulkanImage>,
source: crate::gfx_api::SampleRect,
threshold: f32,
}
struct VulkanTexOp {
@ -300,6 +320,7 @@ pub(super) struct PendingFrame {
_fb: Rc<VulkanImage>,
_bb: Option<Rc<VulkanImage>>,
_textures: Vec<UsedTexture>,
_blur_scratch: Vec<Rc<VulkanImage>>,
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
waiter: Cell<Option<SpawnedFuture<()>>>,
vulkan_sync: Option<VulkanSync>,
@ -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<Rc<VulkanPipeline>, 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<Rc<VulkanPipeline>, 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::<BlurCompositePushConstants>() 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<Self>,
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<Rc<VulkanImage>> = 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<Option<vk::Pipeline>> = Cell::new(None);
let cp = &current_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;

View file

@ -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<Self>,

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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<LayerPopupBlur> {
None
}
}
#[derive(Copy, Clone, Default)]
pub struct LayerPopupBlur {
pub blur: bool,
pub ignore_alpha: Option<f32>,
}
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<Rc<Client>> {

View file

@ -53,12 +53,15 @@ pub struct ZwlrLayerSurfaceV1 {
pub client: Rc<Client>,
pub surface: Rc<WlSurface>,
pub output: Rc<OutputGlobalOpt>,
pub _namespace: String,
pub namespace: String,
pub tracker: Tracker<Self>,
output_extents: Cell<Rect>,
pos: Cell<Rect>,
mapped: Cell<bool>,
layer: Cell<u32>,
pub blur: Cell<bool>,
pub blur_popups: Cell<bool>,
pub ignore_alpha: Cell<Option<f32>>,
requested_serial: NumCell<u32>,
size: Cell<(i32, i32)>,
anchor: Cell<u32>,
@ -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<crate::ifs::wl_surface::xdg_surface::xdg_popup::LayerPopupBlur> {
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! {

View file

@ -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(

View file

@ -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<f32>,
}
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<BlurMask>,
) {
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]

View file

@ -305,6 +305,8 @@ pub struct State {
pub clean_logs_older_than: Cell<Option<SystemTime>>,
pub hyprland_global_shortcuts:
CopyHashMap<(String, String), Rc<HyprlandGlobalShortcutV1>>,
pub layer_rules: RefCell<Vec<jay_config::_private::LayerRuleIpc>>,
pub blur_config: Cell<jay_config::_private::BlurConfigIpc>,
}
// impl Drop for State {

View file

@ -365,6 +365,34 @@ pub struct WindowMatch {
pub content_types: Option<ContentType>,
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum LayerKind {
Background,
Bottom,
Top,
Overlay,
}
#[derive(Default, Debug, Clone)]
pub struct LayerMatch {
pub namespace: Option<String>,
pub layer: Option<LayerKind>,
}
#[derive(Debug, Clone)]
pub struct LayerRule {
pub match_: LayerMatch,
pub blur: Option<bool>,
pub blur_popups: Option<bool>,
pub ignore_alpha: Option<f32>,
}
#[derive(Debug, Clone, Copy)]
pub struct BlurConfig {
pub passes: Option<u8>,
pub size: Option<f32>,
}
#[derive(Debug, Clone)]
pub enum DrmDeviceMatch {
Any(Vec<DrmDeviceMatch>),
@ -577,6 +605,8 @@ pub struct Config {
pub max_action_depth: u64,
pub client_rules: Vec<ClientRule>,
pub window_rules: Vec<WindowRule>,
pub layer_rules: Vec<LayerRule>,
pub blur: Option<BlurConfig>,
pub pointer_revert_key: Option<KeySym>,
pub use_hardware_cursor: Option<bool>,
pub show_bar: Option<bool>,

View file

@ -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;

View file

@ -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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
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 })
}
}

View file

@ -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(),

View file

@ -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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
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<Self> {
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)
}
}

View file

@ -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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
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<LayerRule>;
type Error = LayerRuleParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
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)
}
}

View file

@ -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<Persistent
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
let (window_rules, _) = state.create_rules(&config.window_rules);
persistent.window_rules.set(window_rules);
push_layer_rules(&config.layer_rules);
push_blur_config(config.blur);
state.set_status(&config.status);
persistent.actions.borrow_mut().clear();
for a in config.named_actions {
@ -1719,6 +1724,39 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
}
}
fn push_blur_config(blur: Option<BlurConfig>) {
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<LayerRuleIpc> = 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 {