Add blur to vulkan and opengl render pipelines
This commit is contained in:
parent
6d3bff952e
commit
0701c4e4cf
41 changed files with 1990 additions and 47 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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") },
|
||||
|
|
|
|||
8
src/gfx_apis/gl/shaders/blur.vert.glsl
Normal file
8
src/gfx_apis/gl/shaders/blur.vert.glsl
Normal 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;
|
||||
}
|
||||
17
src/gfx_apis/gl/shaders/blur_composite.frag.glsl
Normal file
17
src/gfx_apis/gl/shaders/blur_composite.frag.glsl
Normal 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);
|
||||
}
|
||||
11
src/gfx_apis/gl/shaders/blur_composite.vert.glsl
Normal file
11
src/gfx_apis/gl/shaders/blur_composite.vert.glsl
Normal 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;
|
||||
}
|
||||
16
src/gfx_apis/gl/shaders/blur_down.frag.glsl
Normal file
16
src/gfx_apis/gl/shaders/blur_down.frag.glsl
Normal 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;
|
||||
}
|
||||
19
src/gfx_apis/gl/shaders/blur_up.frag.glsl
Normal file
19
src/gfx_apis/gl/shaders/blur_up.frag.glsl
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
544
src/gfx_apis/vulkan/blur.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 = ¤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;
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
22
src/gfx_apis/vulkan/shaders/blur_composite.frag
Normal file
22
src/gfx_apis/vulkan/shaders/blur_composite.frag
Normal 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);
|
||||
}
|
||||
38
src/gfx_apis/vulkan/shaders/blur_composite.vert
Normal file
38
src/gfx_apis/vulkan/shaders/blur_composite.vert
Normal 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);
|
||||
}
|
||||
BIN
src/gfx_apis/vulkan/shaders_bin/blur_composite.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur_composite.frag.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/blur_composite.vert.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur_composite.vert.spv
Normal file
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>> {
|
||||
|
|
|
|||
|
|
@ -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! {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
45
toml-config/src/config/parsers/blur.rs
Normal file
45
toml-config/src/config/parsers/blur.rs
Normal 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 })
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
70
toml-config/src/config/parsers/layer_match.rs
Normal file
70
toml-config/src/config/parsers/layer_match.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
81
toml-config/src/config/parsers/layer_rule.rs
Normal file
81
toml-config/src/config/parsers/layer_rule.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue