Compare commits
4 commits
fa0ab2f1fa
...
e35dce433a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e35dce433a | ||
|
|
12adb678bb | ||
|
|
bb43c238e3 | ||
|
|
e2de688324 |
53 changed files with 1849 additions and 267 deletions
|
|
@ -25,6 +25,9 @@ pub const TREES: &[Tree] = &[Tree {
|
|||
"rounded_tex.vert",
|
||||
"blur_composite.vert",
|
||||
"blur_composite.frag",
|
||||
"blur.vert",
|
||||
"blur_down.frag",
|
||||
"blur_up.frag",
|
||||
"legacy/fill.frag",
|
||||
"legacy/fill.vert",
|
||||
"legacy/tex.vert",
|
||||
|
|
|
|||
|
|
@ -169,3 +169,55 @@ impl Default for BlurConfigIpc {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||
pub enum AnimationCurveIpc {
|
||||
Linear,
|
||||
EaseOut,
|
||||
EaseInOut,
|
||||
/// Standard CSS cubic-bezier(x1, y1, x2, y2). P0=(0,0), P3=(1,1) are fixed.
|
||||
Bezier {
|
||||
x1: f32,
|
||||
y1: f32,
|
||||
x2: f32,
|
||||
y2: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct AnimationsConfigIpc {
|
||||
pub enabled: bool,
|
||||
pub open_duration_ms: u32,
|
||||
pub open_curve: AnimationCurveIpc,
|
||||
pub close_duration_ms: u32,
|
||||
pub close_curve: AnimationCurveIpc,
|
||||
}
|
||||
|
||||
impl Default for AnimationsConfigIpc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
open_duration_ms: 200,
|
||||
open_curve: AnimationCurveIpc::EaseOut,
|
||||
close_duration_ms: 200,
|
||||
close_curve: AnimationCurveIpc::EaseOut,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub struct DamageVisualizationIpc {
|
||||
pub enabled: bool,
|
||||
pub color: crate::theme::Color,
|
||||
pub decay_millis: u64,
|
||||
}
|
||||
|
||||
impl Default for DamageVisualizationIpc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
color: crate::theme::Color::new_straight(255, 0, 0, 128),
|
||||
decay_millis: 2000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -880,6 +880,14 @@ impl ConfigClient {
|
|||
self.send(&ClientMessage::SetBlurConfig { config })
|
||||
}
|
||||
|
||||
pub fn set_damage_visualization(&self, config: crate::_private::DamageVisualizationIpc) {
|
||||
self.send(&ClientMessage::SetDamageVisualization { config })
|
||||
}
|
||||
|
||||
pub fn set_animations_config(&self, config: crate::_private::AnimationsConfigIpc) {
|
||||
self.send(&ClientMessage::SetAnimationsConfig { config })
|
||||
}
|
||||
|
||||
pub fn switch_to_vt(&self, vtnr: u32) {
|
||||
self.send(&ClientMessage::SwitchTo { vtnr })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use {
|
||||
crate::{
|
||||
_private::{
|
||||
BlurConfigIpc, ClientCriterionIpc, LayerRuleIpc, PollableId, WindowCriterionIpc,
|
||||
WireMode,
|
||||
BlurConfigIpc, ClientCriterionIpc, DamageVisualizationIpc, LayerRuleIpc, PollableId,
|
||||
WindowCriterionIpc, WireMode,
|
||||
},
|
||||
Axis, Direction, PciId, Workspace,
|
||||
client::{Client, ClientCapabilities, ClientMatcher},
|
||||
|
|
@ -925,6 +925,12 @@ pub enum ClientMessage<'a> {
|
|||
SetBlurConfig {
|
||||
config: BlurConfigIpc,
|
||||
},
|
||||
SetDamageVisualization {
|
||||
config: DamageVisualizationIpc,
|
||||
},
|
||||
SetAnimationsConfig {
|
||||
config: crate::_private::AnimationsConfigIpc,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -407,6 +407,34 @@ pub fn _set_blur_config(config: crate::_private::BlurConfigIpc) {
|
|||
get!().set_blur_config(config)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn _set_damage_visualization(config: crate::_private::DamageVisualizationIpc) {
|
||||
get!().set_damage_visualization(config)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn _set_animations_config(config: crate::_private::AnimationsConfigIpc) {
|
||||
get!().set_animations_config(config)
|
||||
}
|
||||
|
||||
/// Configures the damage region visualizer.
|
||||
///
|
||||
/// When enabled, every damaged screen region is overlaid with `color` and fades
|
||||
/// out over `decay` (producing a "blink" effect as new damage accumulates).
|
||||
/// Useful for debugging damage-tracked rendering paths.
|
||||
pub fn set_damage_visualization(
|
||||
enabled: bool,
|
||||
color: crate::theme::Color,
|
||||
decay: std::time::Duration,
|
||||
) {
|
||||
let decay_millis = decay.as_millis().min(u64::MAX as u128) as u64;
|
||||
_set_damage_visualization(crate::_private::DamageVisualizationIpc {
|
||||
enabled,
|
||||
color,
|
||||
decay_millis,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the current corner radius for window borders.
|
||||
pub fn get_corner_radius() -> f32 {
|
||||
get!(0.0).get_corner_radius()
|
||||
|
|
|
|||
193
src/animation.rs
Normal file
193
src/animation.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use {
|
||||
crate::{
|
||||
allocator::{BO_USE_RENDERING, BufferUsage},
|
||||
format::ARGB8888,
|
||||
gfx_api::{AcquireSync, GfxTexture, ReleaseSync, needs_render_usage},
|
||||
rect::Rect,
|
||||
renderer::Renderer,
|
||||
state::State,
|
||||
theme::Color,
|
||||
tree::{OutputNode, ToplevelNode, Transform},
|
||||
video::Modifier,
|
||||
},
|
||||
std::{cell::Cell, rc::Weak},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
/// A captured snapshot of a toplevel's last rendered state, used to drive the
|
||||
/// close animation after the toplevel itself has been torn down. Owns its own
|
||||
/// GPU texture so the source client buffers can be released immediately.
|
||||
pub struct Snapshot {
|
||||
pub texture: Rc<dyn GfxTexture>,
|
||||
/// The output the toplevel was on, used to schedule per-frame damage.
|
||||
pub output: Weak<OutputNode>,
|
||||
/// Logical absolute position the toplevel occupied, used to draw the
|
||||
/// snapshot into the correct screen region during the close animation.
|
||||
pub rect: Rect,
|
||||
/// Slide-out direction in logical pixels. The snapshot moves from (0, 0)
|
||||
/// at start to (slide_dx, slide_dy) at end.
|
||||
pub slide_dx: f32,
|
||||
pub slide_dy: f32,
|
||||
pub start_nsec: Cell<u64>,
|
||||
}
|
||||
|
||||
impl Snapshot {
|
||||
/// Returns the eased close-animation progress in [0, 1], or None if the
|
||||
/// animation has finished.
|
||||
pub fn close_progress(&self, state: &State) -> Option<f32> {
|
||||
let cfg = state.animations_config.get();
|
||||
if !cfg.enabled || cfg.close_duration_ms == 0 {
|
||||
return None;
|
||||
}
|
||||
let now = state.now_nsec();
|
||||
let elapsed = now.saturating_sub(self.start_nsec.get());
|
||||
let dur = (cfg.close_duration_ms as u64).saturating_mul(1_000_000);
|
||||
if elapsed >= dur {
|
||||
return None;
|
||||
}
|
||||
let t = (elapsed as f32) / (dur as f32);
|
||||
let eased = match cfg.close_curve {
|
||||
jay_config::_private::AnimationCurveIpc::Linear => t,
|
||||
jay_config::_private::AnimationCurveIpc::EaseOut => {
|
||||
let inv = 1.0 - t;
|
||||
1.0 - inv * inv * inv
|
||||
}
|
||||
jay_config::_private::AnimationCurveIpc::EaseInOut => {
|
||||
if t < 0.5 {
|
||||
4.0 * t * t * t
|
||||
} else {
|
||||
let f = -2.0 * t + 2.0;
|
||||
1.0 - f * f * f / 2.0
|
||||
}
|
||||
}
|
||||
jay_config::_private::AnimationCurveIpc::Bezier { x1, y1, x2, y2 } => {
|
||||
cubic_bezier_y_at_x(t, x1, y1, x2, y2)
|
||||
}
|
||||
};
|
||||
Some(eased.clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
fn cubic_bezier_y_at_x(x: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
|
||||
fn bx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||
let it = 1.0 - t;
|
||||
3.0 * it * it * t * x1 + 3.0 * it * t * t * x2 + t * t * t
|
||||
}
|
||||
fn dbx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||
let it = 1.0 - t;
|
||||
3.0 * it * it * x1 + 6.0 * it * t * (x2 - x1) + 3.0 * t * t * (1.0 - x2)
|
||||
}
|
||||
let mut t = x;
|
||||
for _ in 0..8 {
|
||||
let err = bx(t, x1, x2) - x;
|
||||
if err.abs() < 1e-4 {
|
||||
break;
|
||||
}
|
||||
let d = dbx(t, x1, x2);
|
||||
if d.abs() < 1e-6 {
|
||||
break;
|
||||
}
|
||||
t = (t - err / d).clamp(0.0, 1.0);
|
||||
}
|
||||
let it = 1.0 - t;
|
||||
3.0 * it * it * t * y1 + 3.0 * it * t * t * y2 + t * t * t
|
||||
}
|
||||
|
||||
/// Renders the toplevel into a private GPU texture and returns the texture.
|
||||
/// Used at unmap time to capture the last-rendered state, so a close animation
|
||||
/// can run after the toplevel itself has been destroyed. Returns None if the
|
||||
/// render context is unavailable, the toplevel has no workspace, or
|
||||
/// allocation/rendering fails.
|
||||
///
|
||||
/// Any open animation in flight on the toplevel is cleared before rendering so
|
||||
/// the snapshot is at full opacity / final position.
|
||||
pub fn capture_snapshot(state: &State, tl: &Rc<dyn ToplevelNode>) -> Option<Snapshot> {
|
||||
let ctx = state.render_ctx.get()?;
|
||||
let formats = ctx.formats();
|
||||
let format_info = formats.get(&ARGB8888.drm)?;
|
||||
let modifiers: Vec<Modifier> = format_info
|
||||
.write_modifiers
|
||||
.iter()
|
||||
.filter(|(m, _)| format_info.read_modifiers.contains(*m))
|
||||
.map(|(m, _)| *m)
|
||||
.collect();
|
||||
if modifiers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let data = tl.tl_data();
|
||||
data.anim_open_start_nsec.set(None);
|
||||
let workspace = data.workspace.get()?;
|
||||
let output = workspace.output.get();
|
||||
let scale = output.global.persistent.scale.get();
|
||||
let scalef = scale.to_f64();
|
||||
let tl_rect = tl.node_absolute_position();
|
||||
let pw = (tl_rect.width() as f64 * scalef).round() as i32;
|
||||
let ph = (tl_rect.height() as f64 * scalef).round() as i32;
|
||||
if pw <= 0 || ph <= 0 {
|
||||
return None;
|
||||
}
|
||||
let allocator = ctx.allocator();
|
||||
let mut usage = BO_USE_RENDERING;
|
||||
if !needs_render_usage(format_info.write_modifiers.values()) {
|
||||
usage = BufferUsage::none();
|
||||
}
|
||||
let bo = allocator
|
||||
.create_bo(&state.dma_buf_ids, pw, ph, ARGB8888, &modifiers, usage)
|
||||
.ok()?;
|
||||
let img = ctx.clone().dmabuf_img(bo.dmabuf()).ok()?;
|
||||
let fb = img.clone().to_framebuffer().ok()?;
|
||||
let mut ops = vec![];
|
||||
{
|
||||
let mut renderer = Renderer {
|
||||
base: fb.renderer_base(&mut ops, scale, Transform::None),
|
||||
state,
|
||||
logical_extents: tl_rect.at_point(0, 0),
|
||||
pixel_extents: Rect::new_saturating(0, 0, pw, ph),
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
current_anim_node: None,
|
||||
};
|
||||
tl.clone().node_render(&mut renderer, 0, 0, None);
|
||||
}
|
||||
let cd = state.color_manager.srgb_gamma22();
|
||||
fb.render(
|
||||
AcquireSync::Unnecessary,
|
||||
ReleaseSync::Implicit,
|
||||
cd,
|
||||
&ops,
|
||||
Some(&Color::TRANSPARENT),
|
||||
&cd.linear,
|
||||
None,
|
||||
cd,
|
||||
)
|
||||
.ok()?;
|
||||
let texture = img.to_texture().ok()?;
|
||||
|
||||
// Slide-out direction: same closest-edge logic as the open animation, but
|
||||
// the snapshot moves AWAY from its rect during close. Computed once here so
|
||||
// we don't need the toplevel's tile lookup anymore once it's torn down.
|
||||
let output_rect = output.global.pos.get();
|
||||
let dl = (tl_rect.x1() - output_rect.x1()).max(0) as f32;
|
||||
let dr = (output_rect.x2() - tl_rect.x2()).max(0) as f32;
|
||||
let dt = (tl_rect.y1() - output_rect.y1()).max(0) as f32;
|
||||
let db = (output_rect.y2() - tl_rect.y2()).max(0) as f32;
|
||||
let mind = dl.min(dr).min(dt).min(db);
|
||||
let (slide_dx, slide_dy) = if mind == dl {
|
||||
(-(tl_rect.width() as f32), 0.0)
|
||||
} else if mind == dr {
|
||||
(tl_rect.width() as f32, 0.0)
|
||||
} else if mind == dt {
|
||||
(0.0, -(tl_rect.height() as f32))
|
||||
} else {
|
||||
(0.0, tl_rect.height() as f32)
|
||||
};
|
||||
|
||||
Some(Snapshot {
|
||||
texture,
|
||||
output: Rc::downgrade(&output),
|
||||
rect: tl_rect,
|
||||
slide_dx,
|
||||
slide_dy,
|
||||
start_nsec: Cell::new(state.now_nsec()),
|
||||
})
|
||||
}
|
||||
|
|
@ -399,6 +399,10 @@ fn start_compositor2(
|
|||
hyprland_global_shortcuts: Default::default(),
|
||||
layer_rules: Default::default(),
|
||||
blur_config: Default::default(),
|
||||
blur_cache_epoch: Default::default(),
|
||||
animations_config: Default::default(),
|
||||
active_animations: Default::default(),
|
||||
close_snapshots: Default::default(),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
|
|||
|
|
@ -3537,6 +3537,20 @@ impl ConfigProxyHandler {
|
|||
ClientMessage::SetBlurConfig { config } => {
|
||||
self.state.blur_config.set(config);
|
||||
}
|
||||
ClientMessage::SetAnimationsConfig { config } => {
|
||||
self.state.animations_config.set(config);
|
||||
}
|
||||
ClientMessage::SetDamageVisualization { config } => {
|
||||
let [r, g, b, a] = config.color.to_u8_straight();
|
||||
let color = crate::theme::Color::from_srgba_straight(r, g, b, a);
|
||||
self.state.damage_visualizer.set_color(color);
|
||||
self.state
|
||||
.damage_visualizer
|
||||
.set_decay(std::time::Duration::from_millis(config.decay_millis));
|
||||
self.state
|
||||
.damage_visualizer
|
||||
.set_enabled(&self.state, config.enabled);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,12 +107,36 @@ pub enum GfxApiOpt {
|
|||
BlurBackdrop(BlurBackdrop),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct BlurBackdrop {
|
||||
pub rect: FramebufferRect,
|
||||
pub passes: u8,
|
||||
pub offset: f32,
|
||||
pub mask: Option<BlurMask>,
|
||||
pub cache: Option<Rc<std::cell::RefCell<Option<BlurCacheEntry>>>>,
|
||||
pub cache_epoch: u64,
|
||||
pub cache_pixel_rect: [i32; 4],
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BlurBackdrop {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BlurBackdrop")
|
||||
.field("rect", &self.rect)
|
||||
.field("passes", &self.passes)
|
||||
.field("offset", &self.offset)
|
||||
.field("mask", &self.mask)
|
||||
.field("cache_epoch", &self.cache_epoch)
|
||||
.field("cache_pixel_rect", &self.cache_pixel_rect)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlurCacheEntry {
|
||||
pub pixel_rect: [i32; 4],
|
||||
pub passes: u8,
|
||||
pub offset: f32,
|
||||
pub epoch: u64,
|
||||
pub image: Rc<dyn GfxTexture>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -120,6 +144,9 @@ pub struct BlurMask {
|
|||
pub texture: Rc<dyn GfxTexture>,
|
||||
pub source: SampleRect,
|
||||
pub threshold: f32,
|
||||
pub buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
pub acquire_sync: AcquireSync,
|
||||
pub release_sync: ReleaseSync,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BlurMask {
|
||||
|
|
@ -785,6 +812,7 @@ impl dyn GfxFramebuffer {
|
|||
},
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
current_anim_node: None,
|
||||
};
|
||||
cursor.render_hardware_cursor(&mut renderer);
|
||||
self.render(
|
||||
|
|
@ -1119,6 +1147,7 @@ pub fn create_render_pass(
|
|||
},
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
current_anim_node: None,
|
||||
};
|
||||
node.node_render(&mut renderer, 0, 0, None);
|
||||
if let Some(rect) = cursor_rect {
|
||||
|
|
@ -1193,6 +1222,9 @@ pub fn renderer_base<'a>(
|
|||
fb_width: width as _,
|
||||
fb_height: height as _,
|
||||
discard_alpha: None,
|
||||
alpha_mul: 1.0,
|
||||
translate_x: 0.0,
|
||||
translate_y: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ use {
|
|||
crate::gfx_apis::vulkan::{
|
||||
VulkanError,
|
||||
image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory},
|
||||
pipeline::VulkanPipeline,
|
||||
renderer::VulkanRenderer,
|
||||
shaders::BlurCompositePushConstants,
|
||||
shaders::{BlurCompositePushConstants, BlurPushConstants},
|
||||
},
|
||||
ash::vk::{
|
||||
AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BlitImageInfo2, CommandBuffer,
|
||||
|
|
@ -16,13 +17,14 @@ use {
|
|||
},
|
||||
gpu_alloc::UsageFlags,
|
||||
run_on_drop::on_drop,
|
||||
std::{cell::Cell, collections::hash_map::Entry, rc::Rc, slice},
|
||||
std::{cell::Cell, 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(),
|
||||
| ImageUsageFlags::SAMPLED.as_raw()
|
||||
| ImageUsageFlags::COLOR_ATTACHMENT.as_raw(),
|
||||
);
|
||||
|
||||
pub(super) struct BlurMaskRecord<'a> {
|
||||
|
|
@ -42,12 +44,17 @@ impl VulkanRenderer {
|
|||
) -> 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()
|
||||
{
|
||||
|
||||
if let Some(weak) = cached.get(&key) {
|
||||
if let Some(img) = weak.upgrade() {
|
||||
if Rc::strong_count(&img) == 1 {
|
||||
img.is_undefined.set(false);
|
||||
img.contents_are_undefined.set(false);
|
||||
return Ok(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let create_info = ImageCreateInfo::default()
|
||||
.image_type(ImageType::TYPE_2D)
|
||||
.format(format)
|
||||
|
|
@ -77,7 +84,6 @@ impl VulkanRenderer {
|
|||
.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)
|
||||
|
|
@ -96,8 +102,6 @@ impl VulkanRenderer {
|
|||
};
|
||||
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,
|
||||
|
|
@ -117,28 +121,30 @@ impl VulkanRenderer {
|
|||
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));
|
||||
}
|
||||
}
|
||||
cached.insert(key, 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).
|
||||
/// Records a dual-Kawase 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).
|
||||
///
|
||||
/// If `cached_blur` is Some, the cascade is skipped and that image is used
|
||||
/// directly as the blurred input to the composite. The mask must also be Some
|
||||
/// in that case, since cache+no-mask is just a no-op (blit-back of cached).
|
||||
/// On a cache miss, the level-0 scratch image (holding the blurred result)
|
||||
/// is returned via `out_blur_image` for the caller to store in the cache.
|
||||
pub(super) fn record_blur(
|
||||
self: &Rc<Self>,
|
||||
buf: CommandBuffer,
|
||||
target: &VulkanImage,
|
||||
rect: [i32; 4],
|
||||
passes: u8,
|
||||
offset: f32,
|
||||
scratch_out: &mut Vec<Rc<VulkanImage>>,
|
||||
mask: Option<&BlurMaskRecord<'_>>,
|
||||
cached_blur: Option<&Rc<VulkanImage>>,
|
||||
out_blur_image: &mut Option<Rc<VulkanImage>>,
|
||||
) -> Result<(), VulkanError> {
|
||||
let [x1, y1, x2, y2] = rect;
|
||||
let x1 = x1.max(0).min(target.width as i32);
|
||||
|
|
@ -150,7 +156,26 @@ impl VulkanRenderer {
|
|||
if w < 4 || h < 4 {
|
||||
return Ok(());
|
||||
}
|
||||
let passes = passes.clamp(1, 6) as u32;
|
||||
let passes = passes.clamp(1, 8) as u32;
|
||||
let offset = offset.max(0.0);
|
||||
|
||||
let dev = &self.device.device;
|
||||
|
||||
// Cache hit fast path: skip cascade, just composite from cached image.
|
||||
// The format check matters because BlurBarrier may run in either the
|
||||
// BlendBuffer pass (linear format) or the FrameBuffer pass (gamma)
|
||||
// depending on whether BB was elided. A cached image from one pass
|
||||
// has the wrong format for the other.
|
||||
if let (Some(cached), Some(mask)) = (cached_blur, mask)
|
||||
&& cached.width == w
|
||||
&& cached.height == h
|
||||
&& cached.format.vk_format == target.format.vk_format
|
||||
{
|
||||
self.record_blur_composite_only(buf, target, cached, mask, [x1, y1, x2, y2])?;
|
||||
// Hold cached image alive for this frame's GPU execution.
|
||||
scratch_out.push(cached.clone());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let format = target.format.vk_format;
|
||||
let mut levels: Vec<Rc<VulkanImage>> = Vec::with_capacity(passes as usize + 1);
|
||||
|
|
@ -163,7 +188,8 @@ impl VulkanRenderer {
|
|||
levels.push(self.acquire_blur_scratch(cw, ch, format)?);
|
||||
}
|
||||
|
||||
let dev = &self.device.device;
|
||||
// After cascade, levels[0] holds the blurred result. Stash it for caching.
|
||||
*out_blur_image = Some(levels[0].clone());
|
||||
let subres = ImageSubresourceLayers::default()
|
||||
.aspect_mask(ImageAspectFlags::COLOR)
|
||||
.layer_count(1)
|
||||
|
|
@ -203,7 +229,7 @@ impl VulkanRenderer {
|
|||
};
|
||||
|
||||
// Step 1: target COLOR_ATTACHMENT -> TRANSFER_SRC.
|
||||
// Step 1: levels[0] UNDEFINED -> TRANSFER_DST.
|
||||
// levels[0] -> TRANSFER_DST (discard prior contents).
|
||||
do_barriers(&[
|
||||
barrier(
|
||||
target.image,
|
||||
|
|
@ -224,6 +250,7 @@ impl VulkanRenderer {
|
|||
AccessFlags2::TRANSFER_WRITE,
|
||||
),
|
||||
]);
|
||||
levels[0].is_undefined.set(false);
|
||||
|
||||
// Step 2: blit target rect -> levels[0] full.
|
||||
let blit = ImageBlit2::default()
|
||||
|
|
@ -252,121 +279,9 @@ impl VulkanRenderer {
|
|||
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(
|
||||
// Step 3: levels[0] TRANSFER_DST -> SHADER_READ_ONLY for sampling in
|
||||
// the down pass.
|
||||
do_barriers(&[barrier(
|
||||
levels[0].image,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
|
|
@ -374,8 +289,135 @@ impl VulkanRenderer {
|
|||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
),
|
||||
barrier(
|
||||
)]);
|
||||
|
||||
let blur_down_pipeline = self.get_or_create_blur_down_pipeline(format)?;
|
||||
let blur_up_pipeline = self.get_or_create_blur_up_pipeline(format)?;
|
||||
|
||||
// Helper to run one blur pass: sample `src`, draw into `dst`. Caller
|
||||
// must have transitioned dst to COLOR_ATTACHMENT and src to
|
||||
// SHADER_READ_ONLY before this. Layouts after: dst stays in
|
||||
// COLOR_ATTACHMENT (caller transitions next).
|
||||
let run_pass = |pipeline: &VulkanPipeline,
|
||||
src: &VulkanImage,
|
||||
dst: &VulkanImage|
|
||||
-> Result<(), VulkanError> {
|
||||
let color_attachment = RenderingAttachmentInfo::default()
|
||||
.image_view(dst.texture_view)
|
||||
.image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.load_op(AttachmentLoadOp::DONT_CARE)
|
||||
.store_op(AttachmentStoreOp::STORE);
|
||||
let render_area = Rect2D {
|
||||
offset: Offset2D { x: 0, y: 0 },
|
||||
extent: Extent2D {
|
||||
width: dst.width,
|
||||
height: dst.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: dst.width as f32,
|
||||
height: dst.height as f32,
|
||||
min_depth: 0.0,
|
||||
max_depth: 1.0,
|
||||
};
|
||||
let scissor = render_area;
|
||||
let push = BlurPushConstants {
|
||||
halfpixel: [0.5 / src.width as f32, 0.5 / src.height as f32],
|
||||
offset,
|
||||
};
|
||||
let src_image_info = DescriptorImageInfo::default()
|
||||
.image_view(src.texture_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(&src_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::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
dev.cmd_end_rendering(buf);
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// Down passes: levels[i-1] (SHADER_READ_ONLY) -> levels[i] (COLOR_ATT).
|
||||
// Each iteration transitions the destination to COLOR_ATTACHMENT,
|
||||
// draws, then to SHADER_READ_ONLY for the next iteration's read.
|
||||
for i in 1..=passes as usize {
|
||||
do_barriers(&[barrier(
|
||||
levels[i].image,
|
||||
ImageLayout::UNDEFINED,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
PipelineStageFlags2::TOP_OF_PIPE,
|
||||
AccessFlags2::empty(),
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||
)]);
|
||||
levels[i].is_undefined.set(false);
|
||||
run_pass(&blur_down_pipeline, &levels[i - 1], &levels[i])?;
|
||||
do_barriers(&[barrier(
|
||||
levels[i].image,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
)]);
|
||||
}
|
||||
|
||||
// Up passes: levels[i+1] (SHADER_READ_ONLY) -> levels[i] (COLOR_ATT).
|
||||
for i in (0..passes as usize).rev() {
|
||||
do_barriers(&[barrier(
|
||||
levels[i].image,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||
)]);
|
||||
run_pass(&blur_up_pipeline, &levels[i + 1], &levels[i])?;
|
||||
do_barriers(&[barrier(
|
||||
levels[i].image,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
)]);
|
||||
}
|
||||
|
||||
// After cascade: levels[0] in SHADER_READ_ONLY, target in TRANSFER_SRC.
|
||||
|
||||
if let Some(mask) = mask {
|
||||
// Masked composite path: restore target to COLOR_ATTACHMENT and
|
||||
// draw the composite shader sampling levels[0] + mask.
|
||||
do_barriers(&[barrier(
|
||||
target.image,
|
||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
|
|
@ -383,8 +425,7 @@ impl VulkanRenderer {
|
|||
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)?;
|
||||
|
||||
|
|
@ -470,14 +511,15 @@ impl VulkanRenderer {
|
|||
dev.cmd_end_rendering(buf);
|
||||
}
|
||||
} else {
|
||||
// Final blit: levels[0] -> target rect.
|
||||
// Unmasked: transition levels[0] back to TRANSFER_SRC, target stays
|
||||
// in TRANSFER_SRC, retarget target to TRANSFER_DST, blit-back.
|
||||
do_barriers(&[
|
||||
barrier(
|
||||
levels[0].image,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_READ,
|
||||
),
|
||||
|
|
@ -516,9 +558,11 @@ impl VulkanRenderer {
|
|||
unsafe {
|
||||
dev.cmd_blit_image2(buf, &blit_info);
|
||||
}
|
||||
|
||||
// Restore target to COLOR_ATTACHMENT_OPTIMAL.
|
||||
do_barriers(&[barrier(
|
||||
// Restore target to COLOR_ATTACHMENT for the resumed render pass.
|
||||
// Also push levels[0] back to SHADER_READ_ONLY so its tracked layout
|
||||
// matches what the cache-hit fast path expects on next frame.
|
||||
do_barriers(&[
|
||||
barrier(
|
||||
target.image,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
|
|
@ -526,11 +570,146 @@ impl VulkanRenderer {
|
|||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
||||
)]);
|
||||
),
|
||||
barrier(
|
||||
levels[0].image,
|
||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_READ,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
// Hold the scratch images until the frame is submitted.
|
||||
scratch_out.extend(levels);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cache-hit fast path. Cached image is already in SHADER_READ_ONLY_OPTIMAL
|
||||
/// (the layout we leave it in after the previous frame's composite). We just
|
||||
/// re-bind the composite pipeline with the cached image as input and draw.
|
||||
fn record_blur_composite_only(
|
||||
self: &Rc<Self>,
|
||||
buf: CommandBuffer,
|
||||
target: &VulkanImage,
|
||||
cached: &Rc<VulkanImage>,
|
||||
mask: &BlurMaskRecord<'_>,
|
||||
rect: [i32; 4],
|
||||
) -> Result<(), VulkanError> {
|
||||
let [x1, y1, x2, y2] = rect;
|
||||
let w = (x2 - x1) as u32;
|
||||
let h = (y2 - y1) as u32;
|
||||
let dev = &self.device.device;
|
||||
// The caller (BlurBarrier handler) has already cmd_end_rendering'd the
|
||||
// pass that produced the underlying scene. Without an explicit barrier
|
||||
// here, the new render pass's LOAD reads can race with those prior
|
||||
// COLOR_ATTACHMENT_WRITEs and pull stale memory — which manifests as
|
||||
// per-frame flicker in the blurred region.
|
||||
let target_load_barrier = ImageMemoryBarrier2::default()
|
||||
.image(target.image)
|
||||
.old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.subresource_range(ImageSubresourceRange {
|
||||
aspect_mask: ImageAspectFlags::COLOR,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
})
|
||||
.src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||
.dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.dst_access_mask(
|
||||
AccessFlags2::COLOR_ATTACHMENT_READ | AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||
)
|
||||
.src_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED)
|
||||
.dst_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED);
|
||||
let dep = DependencyInfoKHR::default()
|
||||
.image_memory_barriers(slice::from_ref(&target_load_barrier));
|
||||
unsafe {
|
||||
dev.cmd_pipeline_barrier2(buf, &dep);
|
||||
}
|
||||
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,
|
||||
},
|
||||
};
|
||||
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(cached.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);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,31 @@ impl VulkanDevice {
|
|||
}))
|
||||
}
|
||||
|
||||
pub(super) fn create_blur_descriptor_set_layout(
|
||||
self: &Rc<Self>,
|
||||
sampler: &Rc<VulkanSampler>,
|
||||
) -> Result<Rc<VulkanDescriptorSetLayout>, VulkanError> {
|
||||
let immutable_sampler = [sampler.sampler];
|
||||
let binding = DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.stage_flags(ShaderStageFlags::FRAGMENT)
|
||||
.descriptor_count(1)
|
||||
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.immutable_samplers(&immutable_sampler);
|
||||
let create_info = DescriptorSetLayoutCreateInfo::default()
|
||||
.bindings(slice::from_ref(&binding))
|
||||
.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>,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ use {
|
|||
sampler::VulkanSampler,
|
||||
semaphore::VulkanSemaphore,
|
||||
shaders::{
|
||||
BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BlurCompositePushConstants,
|
||||
BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BLUR_DOWN_FRAG, BLUR_UP_FRAG, BLUR_VERT,
|
||||
BlurCompositePushConstants, BlurPushConstants,
|
||||
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,
|
||||
|
|
@ -123,6 +124,12 @@ pub struct VulkanRenderer {
|
|||
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) blur_vert_shader: Rc<VulkanShader>,
|
||||
pub(super) blur_down_frag_shader: Rc<VulkanShader>,
|
||||
pub(super) blur_up_frag_shader: Rc<VulkanShader>,
|
||||
pub(super) blur_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
|
||||
pub(super) blur_down_pipelines: CopyHashMap<vk::Format, Rc<VulkanPipeline>>,
|
||||
pub(super) blur_up_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>,
|
||||
|
|
@ -229,13 +236,20 @@ enum VulkanOp {
|
|||
struct VulkanBlurOp {
|
||||
rect: crate::gfx_api::FramebufferRect,
|
||||
passes: u8,
|
||||
offset: f32,
|
||||
mask: Option<VulkanBlurMask>,
|
||||
cache: Option<Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||
cache_epoch: u64,
|
||||
cache_pixel_rect: [i32; 4],
|
||||
}
|
||||
|
||||
struct VulkanBlurMask {
|
||||
tex: Rc<VulkanImage>,
|
||||
source: crate::gfx_api::SampleRect,
|
||||
threshold: f32,
|
||||
buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
acquire_sync: Option<AcquireSync>,
|
||||
release_sync: ReleaseSync,
|
||||
}
|
||||
|
||||
struct VulkanTexOp {
|
||||
|
|
@ -246,6 +260,7 @@ struct VulkanTexOp {
|
|||
acquire_sync: Option<AcquireSync>,
|
||||
release_sync: ReleaseSync,
|
||||
alpha: f32,
|
||||
discard_alpha: f32,
|
||||
source_type: TexSourceType,
|
||||
copy_type: TexCopyType,
|
||||
alpha_mode: AlphaMode,
|
||||
|
|
@ -286,6 +301,7 @@ struct VulkanRoundedTexOp {
|
|||
acquire_sync: Option<AcquireSync>,
|
||||
release_sync: ReleaseSync,
|
||||
alpha: f32,
|
||||
discard_alpha: f32,
|
||||
source_type: TexSourceType,
|
||||
copy_type: TexCopyType,
|
||||
alpha_mode: AlphaMode,
|
||||
|
|
@ -406,6 +422,10 @@ impl VulkanDevice {
|
|||
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 blur_vert_shader = self.create_shader(BLUR_VERT)?;
|
||||
let blur_down_frag_shader = self.create_shader(BLUR_DOWN_FRAG)?;
|
||||
let blur_up_frag_shader = self.create_shader(BLUR_UP_FRAG)?;
|
||||
let blur_descriptor_set_layout = self.create_blur_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
|
||||
|
|
@ -497,6 +517,12 @@ impl VulkanDevice {
|
|||
blur_composite_frag_shader,
|
||||
blur_composite_descriptor_set_layout,
|
||||
blur_composite_pipelines: Default::default(),
|
||||
blur_vert_shader,
|
||||
blur_down_frag_shader,
|
||||
blur_up_frag_shader,
|
||||
blur_descriptor_set_layout,
|
||||
blur_down_pipelines: Default::default(),
|
||||
blur_up_pipelines: Default::default(),
|
||||
defunct: Cell::new(false),
|
||||
pending_cpu_jobs: Default::default(),
|
||||
shm_allocator,
|
||||
|
|
@ -886,6 +912,128 @@ impl VulkanRenderer {
|
|||
}))
|
||||
}
|
||||
|
||||
pub(super) fn get_or_create_blur_down_pipeline(
|
||||
&self,
|
||||
format: vk::Format,
|
||||
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||
if let Some(pl) = self.blur_down_pipelines.get(&format) {
|
||||
return Ok(pl);
|
||||
}
|
||||
let pl = self.create_blur_pass_pipeline(format, &self.blur_down_frag_shader)?;
|
||||
self.blur_down_pipelines.set(format, pl.clone());
|
||||
Ok(pl)
|
||||
}
|
||||
|
||||
pub(super) fn get_or_create_blur_up_pipeline(
|
||||
&self,
|
||||
format: vk::Format,
|
||||
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||
if let Some(pl) = self.blur_up_pipelines.get(&format) {
|
||||
return Ok(pl);
|
||||
}
|
||||
let pl = self.create_blur_pass_pipeline(format, &self.blur_up_frag_shader)?;
|
||||
self.blur_up_pipelines.set(format, pl.clone());
|
||||
Ok(pl)
|
||||
}
|
||||
|
||||
fn create_blur_pass_pipeline(
|
||||
&self,
|
||||
format: vk::Format,
|
||||
frag: &Rc<VulkanShader>,
|
||||
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||
use ash::vk::{
|
||||
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::FRAGMENT)
|
||||
.offset(0)
|
||||
.size(size_of::<BlurPushConstants>() as u32);
|
||||
let set_layouts = [self.blur_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_vert_shader.module)
|
||||
.name(c"main"),
|
||||
PipelineShaderStageCreateInfo::default()
|
||||
.stage(ShaderStageFlags::FRAGMENT)
|
||||
.module(frag.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(false);
|
||||
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_vert_shader.clone(),
|
||||
_frag: frag.clone(),
|
||||
pipeline_layout,
|
||||
pipeline,
|
||||
_descriptor_set_layouts: {
|
||||
let mut v = ArrayVec::new();
|
||||
v.push(self.blur_descriptor_set_layout.clone());
|
||||
v
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn allocate_point(&self) -> u64 {
|
||||
self.last_point.fetch_add(1) + 1
|
||||
}
|
||||
|
|
@ -1263,6 +1411,7 @@ impl VulkanRenderer {
|
|||
acquire_sync: Some(ct.acquire_sync.clone()),
|
||||
release_sync: ct.release_sync,
|
||||
alpha: ct.alpha.unwrap_or_default(),
|
||||
discard_alpha: ct.discard_alpha.unwrap_or(-1.0),
|
||||
source_type,
|
||||
copy_type,
|
||||
alpha_mode: ct.alpha_mode,
|
||||
|
|
@ -1361,6 +1510,7 @@ impl VulkanRenderer {
|
|||
acquire_sync: Some(ct.acquire_sync.clone()),
|
||||
release_sync: ct.release_sync,
|
||||
alpha: ct.alpha.unwrap_or_default(),
|
||||
discard_alpha: ct.discard_alpha.unwrap_or(-1.0),
|
||||
source_type,
|
||||
copy_type,
|
||||
alpha_mode: ct.alpha_mode,
|
||||
|
|
@ -1376,9 +1526,6 @@ 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)?;
|
||||
|
|
@ -1393,15 +1540,35 @@ impl VulkanRenderer {
|
|||
tex,
|
||||
source: m.source,
|
||||
threshold: m.threshold,
|
||||
buffer_resv: m.buffer_resv.clone(),
|
||||
acquire_sync: Some(m.acquire_sync.clone()),
|
||||
release_sync: m.release_sync,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
memory.ops[RenderPass::FrameBuffer].push(VulkanOp::BlurBarrier(VulkanBlurOp {
|
||||
// Route to whichever pass actually contains the scene.
|
||||
// The BlendBuffer holds the linearly-composed scene
|
||||
// (background + workspace + translucent layers) and is
|
||||
// copied into the FrameBuffer only at the end of the FB
|
||||
// pass. Reading FB before that copy would sample an
|
||||
// empty target and produce a black blur. If BB has been
|
||||
// elided (no blended content this frame), fall back to
|
||||
// FB which then carries the full scene itself.
|
||||
let target_pass = if !memory.paint_regions[RenderPass::BlendBuffer].is_empty() {
|
||||
RenderPass::BlendBuffer
|
||||
} else {
|
||||
RenderPass::FrameBuffer
|
||||
};
|
||||
memory.ops[target_pass].push(VulkanOp::BlurBarrier(VulkanBlurOp {
|
||||
rect: b.rect,
|
||||
passes: b.passes,
|
||||
offset: b.offset,
|
||||
mask,
|
||||
cache: b.cache.clone(),
|
||||
cache_epoch: b.cache_epoch,
|
||||
cache_pixel_rect: b.cache_pixel_rect,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1586,7 +1753,7 @@ impl VulkanRenderer {
|
|||
release_sync,
|
||||
});
|
||||
} else if let VulkanOp::BlurBarrier(b) = cmd
|
||||
&& let Some(m) = &b.mask
|
||||
&& let Some(m) = &mut b.mask
|
||||
{
|
||||
let tex = &m.tex;
|
||||
if tex.execution_version.replace(execution) != execution {
|
||||
|
|
@ -1598,6 +1765,12 @@ impl VulkanRenderer {
|
|||
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
|
||||
memory.dmabuf_sample.push(tex.clone())
|
||||
}
|
||||
memory.textures.push(UsedTexture {
|
||||
tex: tex.clone(),
|
||||
resv: m.buffer_resv.take(),
|
||||
acquire_sync: m.acquire_sync.take().unwrap(),
|
||||
release_sync: m.release_sync,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1929,6 +2102,7 @@ impl VulkanRenderer {
|
|||
let push = TexPushConstants {
|
||||
vertices: c.range_address,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
};
|
||||
unsafe {
|
||||
db.cmd_set_descriptor_buffer_offsets(
|
||||
|
|
@ -1966,6 +2140,7 @@ impl VulkanRenderer {
|
|||
pos,
|
||||
tex_pos,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
|
|
@ -2045,6 +2220,7 @@ impl VulkanRenderer {
|
|||
let push = RoundedTexPushConstants {
|
||||
vertices: c.range_address,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
size_x: c.size[0],
|
||||
size_y: c.size[1],
|
||||
corner_radius_tl: c.corner_radius[0],
|
||||
|
|
@ -2088,6 +2264,7 @@ impl VulkanRenderer {
|
|||
pos: c.target,
|
||||
tex_pos: c.source,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
size_x: c.size[0],
|
||||
size_y: c.size[1],
|
||||
corner_radius_tl: c.corner_radius[0],
|
||||
|
|
@ -2109,10 +2286,8 @@ impl VulkanRenderer {
|
|||
}
|
||||
}
|
||||
VulkanOp::BlurBarrier(blur) => {
|
||||
// Blur is only meaningful in the FrameBuffer pass.
|
||||
if pass != RenderPass::FrameBuffer {
|
||||
continue;
|
||||
}
|
||||
// BlurBarrier is pushed to exactly one pass in convert_ops
|
||||
// (BB if present, else FB), so no per-pass gating is needed.
|
||||
// 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
|
||||
|
|
@ -2132,14 +2307,55 @@ impl VulkanRenderer {
|
|||
threshold: m.threshold,
|
||||
_phantom: std::marker::PhantomData,
|
||||
});
|
||||
|
||||
// Cache lookup: a hit lets us skip the entire blur cascade.
|
||||
// Only masked blurs are cached. The masked path leaves the
|
||||
// blurred scratch image in SHADER_READ_ONLY_OPTIMAL, which
|
||||
// is the layout required by the cache-hit composite path.
|
||||
let cached_blur: Option<Rc<VulkanImage>> = mask_record
|
||||
.as_ref()
|
||||
.and_then(|_| blur.cache.as_ref())
|
||||
.and_then(|slot| {
|
||||
let slot_borrow = slot.borrow();
|
||||
slot_borrow.as_ref().and_then(|entry| {
|
||||
if entry.epoch == blur.cache_epoch
|
||||
&& entry.passes == blur.passes
|
||||
&& entry.offset == blur.offset
|
||||
&& entry.pixel_rect == blur.cache_pixel_rect
|
||||
{
|
||||
entry.image.clone().into_vk(&self.device.device).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let mut produced_blur: Option<Rc<VulkanImage>> = None;
|
||||
self.record_blur(
|
||||
buf,
|
||||
target,
|
||||
rect_arr,
|
||||
blur.passes,
|
||||
blur.offset,
|
||||
&mut local_blur_scratch,
|
||||
mask_record.as_ref(),
|
||||
cached_blur.as_ref(),
|
||||
&mut produced_blur,
|
||||
)?;
|
||||
|
||||
// On a masked cache miss, store the freshly-blurred image
|
||||
// for the next frame to reuse.
|
||||
if let (Some(_), Some(slot), Some(image)) =
|
||||
(mask_record.as_ref(), blur.cache.as_ref(), produced_blur)
|
||||
{
|
||||
*slot.borrow_mut() = Some(crate::gfx_api::BlurCacheEntry {
|
||||
pixel_rect: blur.cache_pixel_rect,
|
||||
passes: blur.passes,
|
||||
offset: blur.offset,
|
||||
epoch: blur.cache_epoch,
|
||||
image,
|
||||
});
|
||||
}
|
||||
self.begin_rendering_load(buf, target);
|
||||
// Pipeline state is invalidated across the render-pass
|
||||
// break — force re-bind on next draw.
|
||||
|
|
@ -2679,6 +2895,7 @@ impl VulkanRenderer {
|
|||
let width = fb.width as f32;
|
||||
let height = fb.height as f32;
|
||||
let mut tag = 0;
|
||||
let mut blur_rects: Vec<Rect> = Vec::new();
|
||||
for opt in opts.iter().rev() {
|
||||
let (opaque, fb_rect) = match opt {
|
||||
GfxApiOpt::Sync => continue,
|
||||
|
|
@ -2706,7 +2923,10 @@ impl VulkanRenderer {
|
|||
(false, rf.rect)
|
||||
}
|
||||
GfxApiOpt::RoundedCopyTexture(ct) => (false, ct.target),
|
||||
GfxApiOpt::BlurBackdrop(_) => continue,
|
||||
GfxApiOpt::BlurBackdrop(b) => {
|
||||
blur_rects.push(b.rect.to_rect(width, height));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if opaque || bb.is_none() {
|
||||
tag |= 1;
|
||||
|
|
@ -2719,6 +2939,20 @@ impl VulkanRenderer {
|
|||
}
|
||||
memory.regions_2.push(rect.with_tag(tag));
|
||||
}
|
||||
// Force blur source rects into the effective damage region. The blur
|
||||
// cascade reads its source from BB (or FB), and both buffers persist
|
||||
// their contents across frames in undamaged regions. Without this,
|
||||
// a cache-miss cascade can sample a stale composite — including the
|
||||
// blur surface's own previously-drawn body — and re-blur it,
|
||||
// producing visible double-shadow artifacts at the blur boundary.
|
||||
let blur_region_owned = Region::from_rects2(&blur_rects);
|
||||
let expanded_region;
|
||||
let region: &Region = if blur_region_owned.is_empty() {
|
||||
region
|
||||
} else {
|
||||
expanded_region = region.union_cow(&blur_region_owned).into_owned();
|
||||
&expanded_region
|
||||
};
|
||||
let clear_region = if clear.is_some() {
|
||||
let opaque_region = Region::from_rects2(&memory.regions_1);
|
||||
region.subtract_cow(&opaque_region)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ pub const ROUNDED_TEX_VERT: &[u8] = include_bytes!("shaders_bin/rounded_tex.vert
|
|||
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 BLUR_VERT: &[u8] = include_bytes!("shaders_bin/blur.vert.spv");
|
||||
pub const BLUR_DOWN_FRAG: &[u8] = include_bytes!("shaders_bin/blur_down.frag.spv");
|
||||
pub const BLUR_UP_FRAG: &[u8] = include_bytes!("shaders_bin/blur_up.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] =
|
||||
|
|
@ -69,6 +72,7 @@ unsafe impl Packed for TexVertex {}
|
|||
pub struct TexPushConstants {
|
||||
pub vertices: DeviceAddress,
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for TexPushConstants {}
|
||||
|
|
@ -109,6 +113,7 @@ pub struct LegacyTexPushConstants {
|
|||
pub pos: [[f32; 2]; 4],
|
||||
pub tex_pos: [[f32; 2]; 4],
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for LegacyTexPushConstants {}
|
||||
|
|
@ -148,6 +153,7 @@ unsafe impl Packed for LegacyRoundedFillPushConstants {}
|
|||
pub struct RoundedTexPushConstants {
|
||||
pub vertices: DeviceAddress,
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
pub size_x: f32,
|
||||
pub size_y: f32,
|
||||
pub corner_radius_tl: f32,
|
||||
|
|
@ -165,6 +171,7 @@ pub struct LegacyRoundedTexPushConstants {
|
|||
pub pos: [[f32; 2]; 4],
|
||||
pub tex_pos: [[f32; 2]; 4],
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
pub size_x: f32,
|
||||
pub size_y: f32,
|
||||
pub corner_radius_tl: f32,
|
||||
|
|
@ -195,6 +202,15 @@ pub struct BlurCompositePushConstants {
|
|||
|
||||
unsafe impl Packed for BlurCompositePushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct BlurPushConstants {
|
||||
pub halfpixel: [f32; 2],
|
||||
pub offset: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for BlurPushConstants {}
|
||||
|
||||
impl VulkanDevice {
|
||||
pub(super) fn create_shader(
|
||||
self: &Rc<Self>,
|
||||
|
|
|
|||
26
src/gfx_apis/vulkan/shaders/blur.vert
Normal file
26
src/gfx_apis/vulkan/shaders/blur.vert
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) out vec2 v_texcoord;
|
||||
|
||||
void main() {
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0:
|
||||
pos = vec2( 1.0, -1.0);
|
||||
v_texcoord = vec2(1.0, 0.0);
|
||||
break;
|
||||
case 1:
|
||||
pos = vec2(-1.0, -1.0);
|
||||
v_texcoord = vec2(0.0, 0.0);
|
||||
break;
|
||||
case 2:
|
||||
pos = vec2( 1.0, 1.0);
|
||||
v_texcoord = vec2(1.0, 1.0);
|
||||
break;
|
||||
case 3:
|
||||
pos = vec2(-1.0, 1.0);
|
||||
v_texcoord = vec2(0.0, 1.0);
|
||||
break;
|
||||
}
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
21
src/gfx_apis/vulkan/shaders/blur_down.frag
Normal file
21
src/gfx_apis/vulkan/shaders/blur_down.frag
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D tex;
|
||||
|
||||
layout(push_constant, std430) uniform Data {
|
||||
vec2 halfpixel;
|
||||
float offset;
|
||||
} data;
|
||||
|
||||
layout(location = 0) in vec2 v_texcoord;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
vec2 hp = data.halfpixel * data.offset;
|
||||
vec4 sum = textureLod(tex, v_texcoord, 0.0) * 4.0;
|
||||
sum += textureLod(tex, v_texcoord - hp, 0.0);
|
||||
sum += textureLod(tex, v_texcoord + hp, 0.0);
|
||||
sum += textureLod(tex, v_texcoord + vec2(hp.x, -hp.y), 0.0);
|
||||
sum += textureLod(tex, v_texcoord - vec2(hp.x, -hp.y), 0.0);
|
||||
out_color = sum / 8.0;
|
||||
}
|
||||
24
src/gfx_apis/vulkan/shaders/blur_up.frag
Normal file
24
src/gfx_apis/vulkan/shaders/blur_up.frag
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D tex;
|
||||
|
||||
layout(push_constant, std430) uniform Data {
|
||||
vec2 halfpixel;
|
||||
float offset;
|
||||
} data;
|
||||
|
||||
layout(location = 0) in vec2 v_texcoord;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
vec2 hp = data.halfpixel * data.offset;
|
||||
vec4 sum = textureLod(tex, v_texcoord + vec2(-hp.x * 2.0, 0.0), 0.0);
|
||||
sum += textureLod(tex, v_texcoord + vec2(-hp.x, hp.y), 0.0) * 2.0;
|
||||
sum += textureLod(tex, v_texcoord + vec2(0.0, hp.y * 2.0), 0.0);
|
||||
sum += textureLod(tex, v_texcoord + vec2(hp.x, hp.y), 0.0) * 2.0;
|
||||
sum += textureLod(tex, v_texcoord + vec2(hp.x * 2.0, 0.0), 0.0);
|
||||
sum += textureLod(tex, v_texcoord + vec2(hp.x, -hp.y), 0.0) * 2.0;
|
||||
sum += textureLod(tex, v_texcoord + vec2(0.0, -hp.y * 2.0), 0.0);
|
||||
sum += textureLod(tex, v_texcoord + vec2(-hp.x, -hp.y), 0.0) * 2.0;
|
||||
out_color = sum / 12.0;
|
||||
}
|
||||
|
|
@ -2,11 +2,12 @@ layout(push_constant, std430) uniform Data {
|
|||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec2 tex_pos[4];
|
||||
layout(offset = 64) float mul;
|
||||
layout(offset = 68) float size_x;
|
||||
layout(offset = 72) float size_y;
|
||||
layout(offset = 76) float corner_radius_tl;
|
||||
layout(offset = 80) float corner_radius_tr;
|
||||
layout(offset = 84) float corner_radius_br;
|
||||
layout(offset = 88) float corner_radius_bl;
|
||||
layout(offset = 92) float scale;
|
||||
layout(offset = 68) float discard_threshold;
|
||||
layout(offset = 72) float size_x;
|
||||
layout(offset = 76) float size_y;
|
||||
layout(offset = 80) float corner_radius_tl;
|
||||
layout(offset = 84) float corner_radius_tr;
|
||||
layout(offset = 88) float corner_radius_br;
|
||||
layout(offset = 92) float corner_radius_bl;
|
||||
layout(offset = 96) float scale;
|
||||
} data;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ void main() {
|
|||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl);
|
||||
vec4 c = textureLod(tex, tex_pos, 0);
|
||||
if (c.a < data.discard_threshold) {
|
||||
discard;
|
||||
}
|
||||
if (has_alpha_multiplier) {
|
||||
if (src_has_alpha) {
|
||||
c *= data.mul;
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ layout(push_constant, std430) uniform Data {
|
|||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec2 tex_pos[4];
|
||||
layout(offset = 64) float mul;
|
||||
layout(offset = 68) float discard_threshold;
|
||||
} data;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ layout(location = 0) out vec4 out_color;
|
|||
|
||||
void main() {
|
||||
vec4 c = textureLod(tex, tex_pos, 0);
|
||||
if (c.a < data.discard_threshold) {
|
||||
discard;
|
||||
}
|
||||
if (has_alpha_multiplier) {
|
||||
if (src_has_alpha) {
|
||||
c *= data.mul;
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ layout(buffer_reference, buffer_reference_align = 8, std430) readonly buffer Ver
|
|||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) Vertices vertices;
|
||||
layout(offset = 8) float mul;
|
||||
layout(offset = 12) float size_x;
|
||||
layout(offset = 16) float size_y;
|
||||
layout(offset = 20) float corner_radius_tl;
|
||||
layout(offset = 24) float corner_radius_tr;
|
||||
layout(offset = 28) float corner_radius_br;
|
||||
layout(offset = 32) float corner_radius_bl;
|
||||
layout(offset = 36) float scale;
|
||||
layout(offset = 12) float discard_threshold;
|
||||
layout(offset = 16) float size_x;
|
||||
layout(offset = 20) float size_y;
|
||||
layout(offset = 24) float corner_radius_tl;
|
||||
layout(offset = 28) float corner_radius_tr;
|
||||
layout(offset = 32) float corner_radius_br;
|
||||
layout(offset = 36) float corner_radius_bl;
|
||||
layout(offset = 40) float scale;
|
||||
} data;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ void main() {
|
|||
vec2 size = vec2(data.size_x, data.size_y);
|
||||
vec4 corner_radius = vec4(data.corner_radius_tl, data.corner_radius_tr, data.corner_radius_br, data.corner_radius_bl);
|
||||
vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0);
|
||||
if (c.a < data.discard_threshold) {
|
||||
discard;
|
||||
}
|
||||
if (eotf != inv_eotf || has_matrix || alpha_mode != AM_PREMULTIPLIED_ELECTRICAL) {
|
||||
vec3 rgb = c.rgb;
|
||||
if (src_has_alpha && alpha_mode == AM_PREMULTIPLIED_ELECTRICAL) {
|
||||
|
|
|
|||
|
|
@ -12,4 +12,5 @@ layout(buffer_reference, buffer_reference_align = 8, std430) readonly buffer Ver
|
|||
layout(push_constant, std430) uniform Data {
|
||||
Vertices vertices;
|
||||
float mul;
|
||||
float discard_threshold;
|
||||
} data;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ layout(location = 0) out vec4 out_color;
|
|||
|
||||
void main() {
|
||||
vec4 c = textureLod(sampler2D(tex, sam), tex_pos, 0);
|
||||
if (c.a < data.discard_threshold) {
|
||||
discard;
|
||||
}
|
||||
if (eotf != inv_eotf || has_matrix || alpha_mode != AM_PREMULTIPLIED_ELECTRICAL) {
|
||||
vec3 rgb = c.rgb;
|
||||
if (src_has_alpha && alpha_mode == AM_PREMULTIPLIED_ELECTRICAL) {
|
||||
|
|
|
|||
BIN
src/gfx_apis/vulkan/shaders_bin/blur.vert.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur.vert.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/blur_down.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur_down.frag.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/blur_up.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur_up.frag.spv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,9 @@
|
|||
302a9f250bdc4f8e0e71a9f77c9a8a7aa55fd003bc91c2422a700c4abd83f54e src/gfx_apis/vulkan/shaders/alpha_modes.glsl
|
||||
65acbe7a6496279fa22f520ad2036d3e14a7cb1707c6a509ce7858adc4a2dcba src/gfx_apis/vulkan/shaders/blur.vert
|
||||
16ad6f1eb029ccce5e0204a7d79709b05a8a708133feaf8bb20a24371de25ed7 src/gfx_apis/vulkan/shaders/blur_composite.frag
|
||||
6399e23afa2e07c98b9fd1a4e853ea974a9958547ce65734846483bd7cbc8461 src/gfx_apis/vulkan/shaders/blur_composite.vert
|
||||
a04b2453c39efb018754fc25d45a369b5813359c55fad1c99020804cbb3a18e0 src/gfx_apis/vulkan/shaders/blur_down.frag
|
||||
f6d51f3b5410387d1474529c44e71bfdc31ceb80174ea6e3e4c2df30d03f11c3 src/gfx_apis/vulkan/shaders/blur_up.frag
|
||||
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
|
||||
|
|
@ -12,11 +15,11 @@ ad22a79e1a88a12daa40c0a2b953084c129a408297c8ca544d60e0b6001470b9 src/gfx_apis/vu
|
|||
b77838c0aac9ec90ae76cd0d94d3891d72d9a30b09ce77009afd9f4e567dd042 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.common.glsl
|
||||
fa39734aea1c96960f5dc95b999ae2fa5576ecf4b527fd70ee0f643c8ddcc452 src/gfx_apis/vulkan/shaders/legacy/rounded_fill.frag
|
||||
c1914cc00fb4827f65cd55bd0737d159fe44a098a3085a500822fc91cc2bfcad src/gfx_apis/vulkan/shaders/legacy/rounded_fill.vert
|
||||
bd249cf170b72cd833e92a7719e88da0a91e563956579707e693679b443d73d5 src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl
|
||||
28f3249e0d974a332b2926fb7565930627a093d6ac21ca17f2bf191740d299bd src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag
|
||||
0305f0bf2ab87de4280e32adfda21906304db595590baa0f024d4e5e67d80d9c src/gfx_apis/vulkan/shaders/legacy/rounded_tex.common.glsl
|
||||
02405debc59f254cd95f6b7f94df27438c952b22f357f411359898f430bcd770 src/gfx_apis/vulkan/shaders/legacy/rounded_tex.frag
|
||||
6ef0bde549dc163cd08f68d975071f5d74213c07ccc4a06b30c6f179b2f848ae src/gfx_apis/vulkan/shaders/legacy/rounded_tex.vert
|
||||
e0a8769dd7938dd02e66db9e9048ed6bef8f8c42671f2e2c7a7976a6d498f685 src/gfx_apis/vulkan/shaders/legacy/tex.common.glsl
|
||||
0e7c72ea11671065842c8b4ad4131a7df33b427dc0ea76bf5a896546f6636cb0 src/gfx_apis/vulkan/shaders/legacy/tex.frag
|
||||
f5bfdb445c501ab97a19c7d435996a03ed45d31e8e54e29143f1daad8fa60d5b src/gfx_apis/vulkan/shaders/legacy/tex.common.glsl
|
||||
3a9b36f72c82067e1892481054acb0948097d6c766e62e8bfad766fa2c2e3de6 src/gfx_apis/vulkan/shaders/legacy/tex.frag
|
||||
4402f7ccdbb9fb52fb6cda3aab13cf89e2980c79b541f8be0463efd64a5f98ed src/gfx_apis/vulkan/shaders/legacy/tex.vert
|
||||
3ba5d05c2b95099e5424b3ade5d1c31d431f5730b1d0b51a9fb5f8afc4ea14b4 src/gfx_apis/vulkan/shaders/out.common.glsl
|
||||
5069f619c7d722815a022e2d84720a2d8290af49a3ed49ea0cd26b52115cc39a src/gfx_apis/vulkan/shaders/out.frag
|
||||
|
|
@ -24,10 +27,10 @@ e0a8769dd7938dd02e66db9e9048ed6bef8f8c42671f2e2c7a7976a6d498f685 src/gfx_apis/vu
|
|||
9202d5c9fc4ce0d5f40ed147f245bd037728c9e060ea46a0f0a1767ca55e6c48 src/gfx_apis/vulkan/shaders/rounded_fill.common.glsl
|
||||
9085625d2afb1365685ae79a58108bf6566573ed94d9913397cf74dc6ef9b6e8 src/gfx_apis/vulkan/shaders/rounded_fill.frag
|
||||
7665319a706e514f125d80f51f10b643f01cdae54d8a6ea56c218f78de7c0ecb src/gfx_apis/vulkan/shaders/rounded_fill.vert
|
||||
dd100d048c0b380c913cffd7ac48fed3a341b3cb052302a11c369967f38aba9a src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl
|
||||
454f34754ea4102190821c2d168dedd8c6bf624f1712b6136d902428f801a1e9 src/gfx_apis/vulkan/shaders/rounded_tex.frag
|
||||
0fa53622bbee536bdf0b32438c276b9e5231e1fe5fac93ed395426da3893bd74 src/gfx_apis/vulkan/shaders/rounded_tex.common.glsl
|
||||
adeba99236ee7606170bfb48e62c0df11c71a83018d8a201cde760c4f569fe5e src/gfx_apis/vulkan/shaders/rounded_tex.frag
|
||||
21b18ba369b505b9aedb8cf2e7e31bc417f6704fd2daac353b0db52f9ae44c70 src/gfx_apis/vulkan/shaders/rounded_tex.vert
|
||||
e22d4d3318a350def8ef19c7b27dc6a308a84c2fe9d7c02b81107f72073cd481 src/gfx_apis/vulkan/shaders/tex.common.glsl
|
||||
1f196cee646a934072beb3e5648a5042c035953d9a0c26b0a22e330c2f8bb994 src/gfx_apis/vulkan/shaders/tex.frag
|
||||
6ebf70abd2a06cb8a14cea7022a19d5d4bc95b1ef5e5a7ca22ab4c5fa37b6244 src/gfx_apis/vulkan/shaders/tex.common.glsl
|
||||
fdfc60c64a22e7745dc82642ea23ef214dbd3b92d6a4f0ae1d75d33e89ae6a6a src/gfx_apis/vulkan/shaders/tex.frag
|
||||
423cf327c9fcc4070dbf75321c1224a1589b6cf3d2f1ea5e8bd0362e1a9f3aa1 src/gfx_apis/vulkan/shaders/tex.vert
|
||||
b982f7101c22931a33b32dce3408387f3392c0f0ad0ca5852da265b0d12856bb src/gfx_apis/vulkan/shaders/tex_set.glsl
|
||||
|
|
|
|||
|
|
@ -1582,7 +1582,9 @@ impl WlSeatGlobal {
|
|||
{
|
||||
con.disconnect(TextDisconnectReason::FocusLost);
|
||||
}
|
||||
if let Some(tis) = self.text_inputs.borrow().get(&surface.client.id) {
|
||||
if !surface.destroyed.get()
|
||||
&& let Some(tis) = self.text_inputs.borrow().get(&surface.client.id)
|
||||
{
|
||||
for ti in tis.lock().values() {
|
||||
ti.send_leave(surface);
|
||||
ti.send_done();
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ pub struct WlSurface {
|
|||
pub content_type: Cell<Option<ContentType>>,
|
||||
pub drm_feedback: CopyHashMap<ZwpLinuxDmabufFeedbackV1Id, Rc<ZwpLinuxDmabufFeedbackV1>>,
|
||||
syncobj_surface: CloneCell<Option<Rc<WpLinuxDrmSyncobjSurfaceV1>>>,
|
||||
destroyed: Cell<bool>,
|
||||
pub destroyed: Cell<bool>,
|
||||
commit_timeline: CommitTimeline,
|
||||
alpha_modifier: CloneCell<Option<Rc<WpAlphaModifierSurfaceV1>>>,
|
||||
alpha: Cell<Option<f32>>,
|
||||
|
|
@ -1019,6 +1019,7 @@ impl WlSurfaceRequestHandler for WlSurface {
|
|||
self.unset_dnd_icons();
|
||||
self.unset_cursors();
|
||||
self.ext.get().on_surface_destroy()?;
|
||||
self.destroyed.set(true);
|
||||
self.destroy_node();
|
||||
{
|
||||
let mut children = self.children.borrow_mut();
|
||||
|
|
@ -1029,6 +1030,19 @@ impl WlSurfaceRequestHandler for WlSurface {
|
|||
}
|
||||
*children = None;
|
||||
}
|
||||
// Capture a close-animation snapshot if the client is destroying the
|
||||
// surface while it still has a buffer (i.e. without a clean null-attach
|
||||
// commit first — typical for crash/disconnect paths).
|
||||
if self.buffer.is_some()
|
||||
&& let Some(tl) = self.toplevel.get()
|
||||
&& let Some(snap) = crate::animation::capture_snapshot(&self.client.state, &tl)
|
||||
{
|
||||
self.client
|
||||
.state
|
||||
.close_snapshots
|
||||
.borrow_mut()
|
||||
.push(Rc::new(snap));
|
||||
}
|
||||
self.buffer.set(None);
|
||||
self.reset_shm_textures();
|
||||
if let Some(xwayland_serial) = self.xwayland_serial.get() {
|
||||
|
|
@ -1041,7 +1055,6 @@ impl WlSurfaceRequestHandler for WlSurface {
|
|||
self.client.remove_obj(self)?;
|
||||
self.idle_inhibitors.clear();
|
||||
self.constraints.take();
|
||||
self.destroyed.set(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -1238,8 +1251,24 @@ impl WlSurface {
|
|||
let mut buffer_changed = false;
|
||||
let mut old_raw_size = None;
|
||||
let (mut dx, mut dy) = mem::take(&mut pending.offset);
|
||||
let mut buffer_presence_changed = false;
|
||||
if let Some(buffer_change) = pending.buffer.take() {
|
||||
buffer_changed = true;
|
||||
buffer_presence_changed = buffer_change.is_some() != self.buffer.is_some();
|
||||
// If the client just attached a null buffer to the main surface of
|
||||
// a mapped toplevel, capture a snapshot before we drop the buffer
|
||||
// so the close animation has something to render after teardown.
|
||||
if buffer_change.is_none()
|
||||
&& self.buffer.is_some()
|
||||
&& let Some(tl) = self.toplevel.get()
|
||||
&& let Some(snap) = crate::animation::capture_snapshot(&self.client.state, &tl)
|
||||
{
|
||||
self.client
|
||||
.state
|
||||
.close_snapshots
|
||||
.borrow_mut()
|
||||
.push(Rc::new(snap));
|
||||
}
|
||||
if let Some(buffer) = self.buffer.take() {
|
||||
old_raw_size = Some(buffer.buffer.buf.rect);
|
||||
}
|
||||
|
|
@ -1408,6 +1437,16 @@ impl WlSurface {
|
|||
};
|
||||
self.is_opaque.set(is_opaque);
|
||||
}
|
||||
if buffer_abs_pos_size_changed || buffer_presence_changed {
|
||||
// Pointer focus depends on whether this surface accepts input.
|
||||
// It just changed (size shrank/grew, or buffer went from null to
|
||||
// non-null or vice versa — the latter happens when a client
|
||||
// dismisses a subsurface by null-attaching while keeping its
|
||||
// wp_viewport destination). Force a re-evaluation so the pointer
|
||||
// stack doesn't keep a now-invisible surface focused until the
|
||||
// next motion event.
|
||||
self.client.state.tree_changed();
|
||||
}
|
||||
let mut tearing_changed = false;
|
||||
if let Some(tearing) = pending.tearing.take()
|
||||
&& self.tearing.replace(tearing) != tearing
|
||||
|
|
@ -1597,6 +1636,13 @@ impl WlSurface {
|
|||
}
|
||||
|
||||
fn accepts_input_at(&self, mut x: i32, mut y: i32) -> bool {
|
||||
// Per the wayland spec, a surface without a buffer is invisible and
|
||||
// cannot receive input. Without this check, a client that null-buffers
|
||||
// but keeps a wp_viewport destination set (as foot does for its
|
||||
// fractional-scaling subsurfaces) would keep an invisible hit-rect.
|
||||
if self.buffer.is_none() {
|
||||
return false;
|
||||
}
|
||||
let rect = self.buffer_abs_pos.get().at_point(0, 0);
|
||||
if !rect.contains(x, y) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use {
|
|||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
|
||||
NodeLocation, NodeVisitor, OutputNode, StackedNode,
|
||||
},
|
||||
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
||||
utils::{clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap},
|
||||
wire::{XdgPopupId, xdg_popup::*},
|
||||
},
|
||||
std::{
|
||||
|
|
@ -72,6 +72,7 @@ pub struct LayerPopupBlur {
|
|||
}
|
||||
|
||||
pub struct XdgPopup {
|
||||
pub blur_pre_rendered: Cell<bool>,
|
||||
pub id: XdgPopupId,
|
||||
node_id: PopupId,
|
||||
pub xdg: Rc<XdgSurface>,
|
||||
|
|
@ -79,6 +80,8 @@ pub struct XdgPopup {
|
|||
relative_position: Cell<Rect>,
|
||||
pos: RefCell<XdgPositioned>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub blur_cache: Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>,
|
||||
pub blur_cache_epoch: NumCell<u64>,
|
||||
seat_state: NodeSeatState,
|
||||
set_visible_prepared: Cell<bool>,
|
||||
jay_popup_ext: CloneCell<Option<Rc<JayPopupExtV1>>>,
|
||||
|
|
@ -104,8 +107,11 @@ impl XdgPopup {
|
|||
Ok(Self {
|
||||
id,
|
||||
node_id: xdg.surface.client.state.node_ids.next(),
|
||||
blur_pre_rendered: Cell::new(false),
|
||||
xdg: xdg.clone(),
|
||||
parent: Default::default(),
|
||||
blur_cache: Default::default(),
|
||||
blur_cache_epoch: Default::default(),
|
||||
relative_position: Cell::new(Default::default()),
|
||||
pos: RefCell::new(pos),
|
||||
tracker: Default::default(),
|
||||
|
|
@ -314,6 +320,9 @@ impl XdgPopupRequestHandler for XdgPopup {
|
|||
}
|
||||
|
||||
impl XdgPopup {
|
||||
pub fn layer_blur_settings(&self) -> Option<LayerPopupBlur> {
|
||||
self.parent.get()?.layer_blur_settings()
|
||||
}
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let surface = &self.xdg.surface;
|
||||
let extents = surface.extents.get();
|
||||
|
|
@ -418,33 +427,56 @@ impl Node for XdgPopup {
|
|||
}
|
||||
|
||||
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||
let settings = self.parent.get().and_then(|p| p.layer_blur_settings());
|
||||
let settings = self.layer_blur_settings();
|
||||
if let Some(s) = settings {
|
||||
if s.blur {
|
||||
if s.blur && !self.blur_pre_rendered.get() {
|
||||
// Only push blur if it wasn't already pushed in the pre-pass
|
||||
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 popup_blur_rect = if let Some(parent) = self.parent.get() {
|
||||
let parent_rect = parent.position();
|
||||
if parent_rect.contains_rect(&rect) {
|
||||
None
|
||||
} else {
|
||||
Some(rect)
|
||||
}
|
||||
} else {
|
||||
Some(rect)
|
||||
};
|
||||
if let Some(blur_rect) = popup_blur_rect {
|
||||
let scaled = renderer.base.scale_rect(blur_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();
|
||||
let release_sync = buffer.release_sync;
|
||||
Some(crate::gfx_api::BlurMask {
|
||||
texture,
|
||||
source,
|
||||
threshold,
|
||||
buffer_resv: Some(buffer),
|
||||
acquire_sync: crate::gfx_api::AcquireSync::Unnecessary,
|
||||
release_sync,
|
||||
})
|
||||
});
|
||||
renderer
|
||||
.base
|
||||
.push_blur_backdrop(scaled, cfg.passes, cfg.size, mask);
|
||||
renderer.base.push_blur_backdrop(
|
||||
scaled,
|
||||
cfg.passes,
|
||||
cfg.size,
|
||||
mask,
|
||||
Some(self.blur_cache.clone()),
|
||||
self.blur_cache_epoch.get(),
|
||||
);
|
||||
}
|
||||
renderer.base.discard_alpha = s.ignore_alpha;
|
||||
}
|
||||
// Always clear the flag after node_render regardless of path
|
||||
self.blur_pre_rendered.set(false);
|
||||
renderer.render_xdg_surface(&self.xdg, x, y, bounds);
|
||||
renderer.base.discard_alpha = None;
|
||||
} else {
|
||||
self.blur_pre_rendered.set(false);
|
||||
renderer.render_xdg_surface(&self.xdg, x, y, bounds);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -555,6 +555,7 @@ impl XdgToplevel {
|
|||
self.state.tree_changed();
|
||||
self.toplevel_data.mapped_source.trigger();
|
||||
self.toplevel_data.broadcast(self.clone());
|
||||
self.toplevel_data.start_open_animation();
|
||||
}
|
||||
self.toplevel_data
|
||||
.set_content_type(self.xdg.surface.content_type.get());
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ pub struct ZwlrLayerSurfaceV1 {
|
|||
pub client: Rc<Client>,
|
||||
pub surface: Rc<WlSurface>,
|
||||
pub output: Rc<OutputGlobalOpt>,
|
||||
pub blur_cache_epoch: NumCell<u64>,
|
||||
pub namespace: String,
|
||||
pub tracker: Tracker<Self>,
|
||||
output_extents: Cell<Rect>,
|
||||
|
|
@ -62,6 +63,7 @@ pub struct ZwlrLayerSurfaceV1 {
|
|||
pub blur: Cell<bool>,
|
||||
pub blur_popups: Cell<bool>,
|
||||
pub ignore_alpha: Cell<Option<f32>>,
|
||||
pub blur_cache: Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>,
|
||||
requested_serial: NumCell<u32>,
|
||||
size: Cell<(i32, i32)>,
|
||||
anchor: Cell<u32>,
|
||||
|
|
@ -158,6 +160,7 @@ impl ZwlrLayerSurfaceV1 {
|
|||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
blur_cache_epoch: Default::default(),
|
||||
node_id: shell.client.state.node_ids.next(),
|
||||
shell: shell.clone(),
|
||||
client: shell.client.clone(),
|
||||
|
|
@ -172,6 +175,7 @@ impl ZwlrLayerSurfaceV1 {
|
|||
blur: Cell::new(false),
|
||||
blur_popups: Cell::new(false),
|
||||
ignore_alpha: Cell::new(None),
|
||||
blur_cache: Default::default(),
|
||||
requested_serial: Default::default(),
|
||||
size: Cell::new((0, 0)),
|
||||
anchor: Cell::new(0),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ mod leaks;
|
|||
mod tracy;
|
||||
mod acceptor;
|
||||
mod allocator;
|
||||
mod animation;
|
||||
mod async_engine;
|
||||
mod backend;
|
||||
mod backends;
|
||||
|
|
|
|||
215
src/renderer.rs
215
src/renderer.rs
|
|
@ -5,7 +5,7 @@ use {
|
|||
ifs::wl_surface::{
|
||||
SurfaceBuffer, WlSurface,
|
||||
x_surface::xwindow::Xwindow,
|
||||
xdg_surface::{XdgSurface, xdg_toplevel::XdgToplevel},
|
||||
xdg_surface::{XdgSurface, xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel},
|
||||
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
|
||||
},
|
||||
rect::Rect,
|
||||
|
|
@ -14,8 +14,9 @@ use {
|
|||
state::State,
|
||||
theme::{Color, CornerRadius},
|
||||
tree::{
|
||||
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
||||
ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
|
||||
ContainerNode, DisplayNode, FloatNode, NodeId, OutputNode, PlaceholderNode,
|
||||
StackedNode, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||
tab_bar::TabBar,
|
||||
},
|
||||
},
|
||||
std::{ops::Deref, rc::Rc, slice},
|
||||
|
|
@ -30,9 +31,60 @@ pub struct Renderer<'a> {
|
|||
pub pixel_extents: Rect,
|
||||
pub stretch: Option<(i32, i32)>,
|
||||
pub corner_radius: Option<CornerRadius>,
|
||||
/// The toplevel whose open-animation transform is currently applied to the
|
||||
/// renderer base. Used to prevent double-applying when a parent (container,
|
||||
/// float) has already entered the animation scope before drawing its own
|
||||
/// per-child decorations.
|
||||
pub current_anim_node: Option<NodeId>,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct OpenAnimSaved {
|
||||
alpha_mul: f32,
|
||||
translate_x: f32,
|
||||
translate_y: f32,
|
||||
prev_node: Option<NodeId>,
|
||||
}
|
||||
|
||||
impl Renderer<'_> {
|
||||
pub fn render_layer_popup_blur_only(&mut self, popup: &XdgPopup, x: i32, y: i32) {
|
||||
let Some(settings) = popup.layer_blur_settings() else {
|
||||
return;
|
||||
};
|
||||
if !settings.blur {
|
||||
return;
|
||||
}
|
||||
let extents = popup.xdg.surface.extents.get();
|
||||
let geo = popup.xdg.geometry();
|
||||
let (gx, gy) = geo.translate(x, y);
|
||||
let rect = extents.move_(gx, gy);
|
||||
let scaled = self.base.scale_rect(rect);
|
||||
let cfg = self.state.blur_config.get();
|
||||
let mask = settings.ignore_alpha.and_then(|threshold| {
|
||||
let buffer = popup.xdg.surface.buffer.get()?;
|
||||
let texture = buffer.buffer.buf.get_texture(&popup.xdg.surface)?;
|
||||
let source = *popup.xdg.surface.buffer_points_norm.borrow();
|
||||
let release_sync = buffer.release_sync;
|
||||
Some(crate::gfx_api::BlurMask {
|
||||
texture,
|
||||
source,
|
||||
threshold,
|
||||
buffer_resv: Some(buffer),
|
||||
acquire_sync: AcquireSync::Unnecessary,
|
||||
release_sync,
|
||||
})
|
||||
});
|
||||
popup.blur_pre_rendered.set(true);
|
||||
self.base.push_blur_backdrop(
|
||||
scaled,
|
||||
cfg.passes,
|
||||
cfg.size,
|
||||
mask,
|
||||
Some(popup.blur_cache.clone()),
|
||||
popup.blur_cache_epoch.get(),
|
||||
);
|
||||
self.base.sync();
|
||||
}
|
||||
pub fn scale(&self) -> Scale {
|
||||
self.base.scale
|
||||
}
|
||||
|
|
@ -215,14 +267,28 @@ impl Renderer<'_> {
|
|||
};
|
||||
}
|
||||
render_stacked!(self.state.root.stacked);
|
||||
// Flush RoundedFillRect ops from container/float borders so they don't
|
||||
// sort after (and render on top of) layer-shell CopyTexture ops.
|
||||
self.base.sync();
|
||||
if fullscreen.is_none() {
|
||||
// Pre-pass: push blur backdrops for layer-shell popups before
|
||||
// the bar renders, so they sample the raw background rather than
|
||||
// the already-composited bar content.
|
||||
for stacked in self.state.root.stacked_above_layers.iter() {
|
||||
if stacked.node_visible() {
|
||||
let pos = stacked.node_absolute_position();
|
||||
if pos.intersects(&opos) {
|
||||
let (sx, sy) = opos.translate(pos.x1(), pos.y1());
|
||||
let stacked_rc: Rc<dyn StackedNode> = stacked.deref().clone();
|
||||
if let Some(popup) = stacked_rc.node_into_popup() {
|
||||
self.render_layer_popup_blur_only(&popup, sx, sy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
render_layer!(output.layers[2]);
|
||||
}
|
||||
render_layer!(output.layers[3]);
|
||||
render_stacked!(self.state.root.stacked_above_layers);
|
||||
self.render_close_snapshots(output, x, y);
|
||||
if let Some(ws) = output.workspace.get()
|
||||
&& ws.render_highlight.get() > 0
|
||||
{
|
||||
|
|
@ -407,6 +473,7 @@ impl Renderer<'_> {
|
|||
self.render_tab_bar(tb, x, y, container.width.get());
|
||||
}
|
||||
}
|
||||
let saved_anim = self.enter_open_anim(&*child.node);
|
||||
let mb = container.mono_body.get();
|
||||
if self.state.theme.sizes.gap.get() != 0 {
|
||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
||||
|
|
@ -487,6 +554,7 @@ impl Renderer<'_> {
|
|||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||
self.stretch = None;
|
||||
self.corner_radius = None;
|
||||
self.exit_open_anim(saved_anim);
|
||||
} else {
|
||||
let gap = self.state.theme.sizes.gap.get();
|
||||
let (srgb_srgb, bw, border_color, focused_border_color) = if gap != 0 {
|
||||
|
|
@ -504,6 +572,7 @@ impl Renderer<'_> {
|
|||
if body.x1() >= container.width.get() || body.y1() >= container.height.get() {
|
||||
break;
|
||||
}
|
||||
let saved_anim = self.enter_open_anim(&*child.node);
|
||||
if let Some(srgb_srgb) = srgb_srgb {
|
||||
let srgb = &srgb_srgb.linear;
|
||||
let c = if child.border_color_is_focused.get() {
|
||||
|
|
@ -574,6 +643,7 @@ impl Renderer<'_> {
|
|||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||
self.stretch = None;
|
||||
self.corner_radius = None;
|
||||
self.exit_open_anim(saved_anim);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -581,13 +651,135 @@ impl Renderer<'_> {
|
|||
}
|
||||
|
||||
pub fn render_xwindow(&mut self, tl: &Xwindow, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||
let saved = self.enter_open_anim(tl);
|
||||
let bounds = if tl.tl_data().anim_open_alpha().is_some() {
|
||||
None
|
||||
} else {
|
||||
bounds
|
||||
};
|
||||
self.render_surface(&tl.x.surface, x, y, bounds);
|
||||
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||
self.exit_open_anim(saved);
|
||||
}
|
||||
|
||||
pub fn render_xdg_toplevel(&mut self, tl: &XdgToplevel, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||
let saved = self.enter_open_anim(tl);
|
||||
let bounds = if tl.tl_data().anim_open_alpha().is_some() {
|
||||
None
|
||||
} else {
|
||||
bounds
|
||||
};
|
||||
self.render_xdg_surface(&tl.xdg, x, y, bounds);
|
||||
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||
self.exit_open_anim(saved);
|
||||
}
|
||||
|
||||
/// Enters open-animation scope for `tl`: applies its eased alpha + slide
|
||||
/// translate to the renderer base. If a parent has already entered scope
|
||||
/// for the same toplevel (so its borders/decorations slide too), this is a
|
||||
/// no-op and returns `None`. Pair every `Some` return with `exit_open_anim`.
|
||||
pub fn enter_open_anim(&mut self, tl: &dyn ToplevelNode) -> Option<OpenAnimSaved> {
|
||||
let data = tl.tl_data();
|
||||
let eased = data.anim_open_alpha()?;
|
||||
if self.current_anim_node == Some(data.node_id) {
|
||||
return None;
|
||||
}
|
||||
let saved = OpenAnimSaved {
|
||||
alpha_mul: self.base.alpha_mul,
|
||||
translate_x: self.base.translate_x,
|
||||
translate_y: self.base.translate_y,
|
||||
prev_node: self.current_anim_node,
|
||||
};
|
||||
self.current_anim_node = Some(data.node_id);
|
||||
self.base.alpha_mul *= eased;
|
||||
if let Some(ws) = data.workspace.get() {
|
||||
let tl_rect = tl.node_absolute_position();
|
||||
let output_rect = ws.output.get().global.pos.get();
|
||||
let dl = (tl_rect.x1() - output_rect.x1()).max(0) as f32;
|
||||
let dr = (output_rect.x2() - tl_rect.x2()).max(0) as f32;
|
||||
let dt = (tl_rect.y1() - output_rect.y1()).max(0) as f32;
|
||||
let db = (output_rect.y2() - tl_rect.y2()).max(0) as f32;
|
||||
let mind = dl.min(dr).min(dt).min(db);
|
||||
let (sx, sy) = if mind == dl {
|
||||
(-(tl_rect.width() as f32), 0.0)
|
||||
} else if mind == dr {
|
||||
(tl_rect.width() as f32, 0.0)
|
||||
} else if mind == dt {
|
||||
(0.0, -(tl_rect.height() as f32))
|
||||
} else {
|
||||
(0.0, tl_rect.height() as f32)
|
||||
};
|
||||
let factor = (1.0 - eased) * self.base.scalef as f32;
|
||||
self.base.translate_x += sx * factor;
|
||||
self.base.translate_y += sy * factor;
|
||||
}
|
||||
Some(saved)
|
||||
}
|
||||
|
||||
pub fn exit_open_anim(&mut self, saved: Option<OpenAnimSaved>) {
|
||||
if let Some(s) = saved {
|
||||
self.base.alpha_mul = s.alpha_mul;
|
||||
self.base.translate_x = s.translate_x;
|
||||
self.base.translate_y = s.translate_y;
|
||||
self.current_anim_node = s.prev_node;
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders any active close-animation snapshots that belong to this output.
|
||||
/// Each snapshot fades out and slides toward its closest output edge —
|
||||
/// mirroring the open animation in reverse. Finished snapshots stay in the
|
||||
/// list until `tick_animations` cleans them up.
|
||||
fn render_close_snapshots(&mut self, output: &OutputNode, x: i32, y: i32) {
|
||||
let snaps = self.state.close_snapshots.borrow();
|
||||
if snaps.is_empty() {
|
||||
return;
|
||||
}
|
||||
let output_pos = output.global.pos.get();
|
||||
for snap in snaps.iter() {
|
||||
let Some(snap_output) = snap.output.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
if !std::ptr::eq(&*snap_output, output) {
|
||||
continue;
|
||||
}
|
||||
let Some(progress) = snap.close_progress(self.state) else {
|
||||
continue;
|
||||
};
|
||||
let alpha = (1.0 - progress).clamp(0.0, 1.0);
|
||||
let prev_alpha = self.base.alpha_mul;
|
||||
let prev_tx = self.base.translate_x;
|
||||
let prev_ty = self.base.translate_y;
|
||||
self.base.alpha_mul *= alpha;
|
||||
self.base.translate_x += snap.slide_dx * progress * self.base.scalef as f32;
|
||||
self.base.translate_y += snap.slide_dy * progress * self.base.scalef as f32;
|
||||
let local_x = x + snap.rect.x1() - output_pos.x1();
|
||||
let local_y = y + snap.rect.y1() - output_pos.y1();
|
||||
let (sx, sy) = self.base.scale_point(local_x, local_y);
|
||||
let scalef = self.base.scalef;
|
||||
let tw = (snap.rect.width() as f64 * scalef).round() as i32;
|
||||
let th = (snap.rect.height() as f64 * scalef).round() as i32;
|
||||
let cd = self.state.color_manager.srgb_gamma22();
|
||||
self.base.render_texture(
|
||||
&snap.texture,
|
||||
None,
|
||||
sx,
|
||||
sy,
|
||||
None,
|
||||
Some((tw, th)),
|
||||
self.base.scale,
|
||||
None,
|
||||
None,
|
||||
AcquireSync::Unnecessary,
|
||||
ReleaseSync::Implicit,
|
||||
false,
|
||||
cd,
|
||||
RenderIntent::Perceptual,
|
||||
AlphaMode::PremultipliedElectrical,
|
||||
);
|
||||
self.base.alpha_mul = prev_alpha;
|
||||
self.base.translate_x = prev_tx;
|
||||
self.base.translate_y = prev_ty;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_xdg_surface(
|
||||
|
|
@ -804,6 +996,7 @@ impl Renderer<'_> {
|
|||
Some(c) => c,
|
||||
_ => return,
|
||||
};
|
||||
let saved_anim = self.enter_open_anim(&*child);
|
||||
let pos = floating.position.get();
|
||||
let theme = &self.state.theme;
|
||||
let bw = theme.sizes.border_width.get();
|
||||
|
|
@ -848,11 +1041,13 @@ impl Renderer<'_> {
|
|||
}
|
||||
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
|
||||
self.corner_radius = None;
|
||||
self.exit_open_anim(saved_anim);
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
@ -863,18 +1058,22 @@ impl Renderer<'_> {
|
|||
let buffer = surface.surface.buffer.get()?;
|
||||
let texture = buffer.buffer.buf.get_texture(&surface.surface)?;
|
||||
let source = *surface.surface.buffer_points_norm.borrow();
|
||||
let release_sync = buffer.release_sync;
|
||||
Some(crate::gfx_api::BlurMask {
|
||||
texture,
|
||||
source,
|
||||
threshold,
|
||||
buffer_resv: Some(buffer),
|
||||
acquire_sync: AcquireSync::Unnecessary,
|
||||
release_sync,
|
||||
})
|
||||
});
|
||||
let cache_epoch = surface.blur_cache_epoch.get();
|
||||
let cache = Some(surface.blur_cache.clone());
|
||||
self.base
|
||||
.push_blur_backdrop(scaled, cfg.passes, cfg.size, mask);
|
||||
.push_blur_backdrop(scaled, cfg.passes, cfg.size, mask, cache, cache_epoch);
|
||||
}
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ pub struct RendererBase<'a> {
|
|||
pub fb_width: f32,
|
||||
pub fb_height: f32,
|
||||
pub discard_alpha: Option<f32>,
|
||||
pub alpha_mul: f32,
|
||||
pub translate_x: f32,
|
||||
pub translate_y: f32,
|
||||
}
|
||||
|
||||
impl RendererBase<'_> {
|
||||
|
|
@ -33,6 +36,26 @@ impl RendererBase<'_> {
|
|||
self.scale
|
||||
}
|
||||
|
||||
fn apply_alpha_mul(&self, alpha: Option<f32>) -> Option<f32> {
|
||||
if self.alpha_mul >= 1.0 {
|
||||
alpha
|
||||
} else {
|
||||
Some(alpha.unwrap_or(1.0) * self.alpha_mul)
|
||||
}
|
||||
}
|
||||
|
||||
fn fb_rect(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> FramebufferRect {
|
||||
FramebufferRect::new(
|
||||
x1 + self.translate_x,
|
||||
y1 + self.translate_y,
|
||||
x2 + self.translate_x,
|
||||
y2 + self.translate_y,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) {
|
||||
if self.scaled {
|
||||
[x, y] = self.scale.pixel_size([x, y]);
|
||||
|
|
@ -123,17 +146,14 @@ impl RendererBase<'_> {
|
|||
true => bx,
|
||||
};
|
||||
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
||||
rect: FramebufferRect::new(
|
||||
rect: self.fb_rect(
|
||||
bx.x1() as f32,
|
||||
bx.y1() as f32,
|
||||
bx.x2() as f32,
|
||||
bx.y2() as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
),
|
||||
color: *color,
|
||||
alpha,
|
||||
alpha: self.apply_alpha_mul(alpha),
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
}));
|
||||
|
|
@ -166,17 +186,9 @@ impl RendererBase<'_> {
|
|||
for bx in boxes {
|
||||
let (x1, y1, x2, y2) = self.scale_rect_f(*bx);
|
||||
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
||||
rect: FramebufferRect::new(
|
||||
x1 + dx,
|
||||
y1 + dy,
|
||||
x2 + dx,
|
||||
y2 + dy,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
),
|
||||
rect: self.fb_rect(x1 + dx, y1 + dy, x2 + dx, y2 + dy),
|
||||
color: *color,
|
||||
alpha: None,
|
||||
alpha: self.apply_alpha_mul(None),
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
}));
|
||||
|
|
@ -227,21 +239,20 @@ impl RendererBase<'_> {
|
|||
return;
|
||||
}
|
||||
|
||||
let target = FramebufferRect::new(
|
||||
let target = self.fb_rect(
|
||||
target_x[0] as f32,
|
||||
target_y[0] as f32,
|
||||
target_x[1] as f32,
|
||||
target_y[1] as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
);
|
||||
|
||||
let new_alpha = self.apply_alpha_mul(alpha);
|
||||
let opaque = opaque && new_alpha == alpha;
|
||||
self.ops.push(GfxApiOpt::CopyTexture(CopyTexture {
|
||||
tex: texture.clone(),
|
||||
source: texcoord,
|
||||
target,
|
||||
alpha,
|
||||
alpha: new_alpha,
|
||||
buffer_resv,
|
||||
acquire_sync,
|
||||
release_sync,
|
||||
|
|
@ -296,17 +307,14 @@ impl RendererBase<'_> {
|
|||
let fitted = corner_radius.fit_to(width, height);
|
||||
let cr: [f32; 4] = fitted.into();
|
||||
self.ops.push(GfxApiOpt::RoundedFillRect(RoundedFillRect {
|
||||
rect: FramebufferRect::new(
|
||||
rect: self.fb_rect(
|
||||
rect.x1() as f32,
|
||||
rect.y1() as f32,
|
||||
rect.x2() as f32,
|
||||
rect.y2() as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
),
|
||||
color: *color,
|
||||
alpha,
|
||||
alpha: self.apply_alpha_mul(alpha),
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
size: [width, height],
|
||||
|
|
@ -358,14 +366,11 @@ impl RendererBase<'_> {
|
|||
return;
|
||||
}
|
||||
|
||||
let target = FramebufferRect::new(
|
||||
let target = self.fb_rect(
|
||||
target_x[0] as f32,
|
||||
target_y[0] as f32,
|
||||
target_x[1] as f32,
|
||||
target_y[1] as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
);
|
||||
|
||||
let width = (target_x[1] - target_x[0]) as f32;
|
||||
|
|
@ -379,7 +384,7 @@ impl RendererBase<'_> {
|
|||
tex: texture.clone(),
|
||||
source: texcoord,
|
||||
target,
|
||||
alpha,
|
||||
alpha: self.apply_alpha_mul(alpha),
|
||||
buffer_resv,
|
||||
acquire_sync,
|
||||
release_sync,
|
||||
|
|
@ -404,6 +409,8 @@ impl RendererBase<'_> {
|
|||
passes: u8,
|
||||
offset: f32,
|
||||
mask: Option<BlurMask>,
|
||||
cache: Option<Rc<std::cell::RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||
cache_epoch: u64,
|
||||
) {
|
||||
let target = FramebufferRect::new(
|
||||
rect.x1() as f32,
|
||||
|
|
@ -414,11 +421,15 @@ impl RendererBase<'_> {
|
|||
self.fb_width,
|
||||
self.fb_height,
|
||||
);
|
||||
let cache_pixel_rect = [rect.x1(), rect.y1(), rect.x2(), rect.y2()];
|
||||
self.ops.push(GfxApiOpt::BlurBackdrop(BlurBackdrop {
|
||||
rect: target,
|
||||
passes,
|
||||
offset,
|
||||
mask,
|
||||
cache,
|
||||
cache_epoch,
|
||||
cache_pixel_rect,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
src/state.rs
73
src/state.rs
|
|
@ -306,6 +306,10 @@ pub struct State {
|
|||
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>,
|
||||
pub blur_cache_epoch: NumCell<u64>,
|
||||
pub animations_config: Cell<jay_config::_private::AnimationsConfigIpc>,
|
||||
pub active_animations: RefCell<Vec<std::rc::Weak<dyn ToplevelNode>>>,
|
||||
pub close_snapshots: RefCell<Vec<Rc<crate::animation::Snapshot>>>,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
@ -1049,6 +1053,25 @@ impl State {
|
|||
if rect.is_empty() {
|
||||
return;
|
||||
}
|
||||
if !cursor {
|
||||
for output in self.root.outputs.lock().values() {
|
||||
for layer in &output.layers {
|
||||
for surface in layer.iter() {
|
||||
if surface.blur.get() && surface.node_absolute_position().intersects(&rect)
|
||||
{
|
||||
surface.blur_cache_epoch.fetch_add(1);
|
||||
}
|
||||
if surface.blur.get() && surface.blur_popups.get() {
|
||||
surface.for_each_popup(|popup| {
|
||||
if popup.node_absolute_position().intersects(&rect) {
|
||||
popup.blur_cache_epoch.fetch_add(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.damage_visualizer.add(rect);
|
||||
for output in self.root.outputs.lock().values() {
|
||||
if output.global.pos.get().intersects(&rect) {
|
||||
|
|
@ -1290,6 +1313,7 @@ impl State {
|
|||
},
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
current_anim_node: None,
|
||||
};
|
||||
let mut sample_rect = SampleRect::identity();
|
||||
sample_rect.buffer_transform = transform;
|
||||
|
|
@ -1464,6 +1488,55 @@ impl State {
|
|||
self.eng.now().msec()
|
||||
}
|
||||
|
||||
/// Walks the active-animations list, damages each toplevel's slide region
|
||||
/// (so the next frame re-renders it), and removes any whose animation is
|
||||
/// done. Also ticks close-animation snapshots: damages their output and
|
||||
/// drops finished ones. Intended to be called once per output present cycle.
|
||||
pub fn tick_animations(&self) {
|
||||
{
|
||||
let mut animations = self.active_animations.borrow_mut();
|
||||
if !animations.is_empty() {
|
||||
animations.retain(|weak| {
|
||||
let Some(tl) = weak.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
let data = tl.tl_data();
|
||||
if data.anim_open_alpha().is_none() {
|
||||
return false;
|
||||
}
|
||||
// Damage the entire output the toplevel is on: the slide
|
||||
// can render outside the toplevel's nominal rect, so the
|
||||
// narrow rect alone would leave the slid-out portion
|
||||
// unredrawn.
|
||||
if let Some(ws) = data.workspace.get() {
|
||||
self.damage(ws.output.get().global.pos.get());
|
||||
} else {
|
||||
self.damage(tl.node_absolute_position());
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
let mut snapshots = self.close_snapshots.borrow_mut();
|
||||
if snapshots.is_empty() {
|
||||
return;
|
||||
}
|
||||
snapshots.retain(|snap| {
|
||||
if snap.close_progress(self).is_none() {
|
||||
// Final damage so the snapshot's last-rendered position gets
|
||||
// repainted (clearing any leftover pixels).
|
||||
if let Some(output) = snap.output.upgrade() {
|
||||
self.damage(output.global.pos.get());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if let Some(output) = snap.output.upgrade() {
|
||||
self.damage(output.global.pos.get());
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
pub fn output_extents_changed(&self) {
|
||||
self.root.update_extents();
|
||||
for seat in self.globals.seats.lock().values() {
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ impl OutputNode {
|
|||
for listener in self.presentation_event.iter() {
|
||||
listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr);
|
||||
}
|
||||
self.state.tick_animations();
|
||||
if locked && let Some(lock) = self.state.lock.lock.get() {
|
||||
lock.check_locked()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -428,6 +428,7 @@ pub struct ToplevelData {
|
|||
pub property_changed_source: OnceCell<Rc<LazyEventSource>>,
|
||||
pub mapped_source: Rc<LazyEventSource>,
|
||||
pub unmapped_source: Rc<LazyEventSource>,
|
||||
pub anim_open_start_nsec: Cell<Option<u64>>,
|
||||
}
|
||||
|
||||
impl ToplevelData {
|
||||
|
|
@ -485,6 +486,7 @@ impl ToplevelData {
|
|||
property_changed_source: Default::default(),
|
||||
mapped_source: state.lazy_event_sources.create_source(),
|
||||
unmapped_source: state.lazy_event_sources.create_source(),
|
||||
anim_open_start_nsec: Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -531,6 +533,63 @@ impl ToplevelData {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the eased alpha multiplier for the open animation, or None if no
|
||||
/// animation is active. When the animation has finished, clears the start
|
||||
/// time and returns None.
|
||||
pub fn anim_open_alpha(&self) -> Option<f32> {
|
||||
let start = self.anim_open_start_nsec.get()?;
|
||||
let cfg = self.state.animations_config.get();
|
||||
if !cfg.enabled || cfg.open_duration_ms == 0 {
|
||||
self.anim_open_start_nsec.set(None);
|
||||
return None;
|
||||
}
|
||||
let now = self.state.now_nsec();
|
||||
let elapsed_ns = now.saturating_sub(start);
|
||||
let dur_ns = (cfg.open_duration_ms as u64).saturating_mul(1_000_000);
|
||||
if elapsed_ns >= dur_ns {
|
||||
self.anim_open_start_nsec.set(None);
|
||||
return None;
|
||||
}
|
||||
let t = (elapsed_ns as f32) / (dur_ns as f32);
|
||||
let eased = match cfg.open_curve {
|
||||
jay_config::_private::AnimationCurveIpc::Linear => t,
|
||||
jay_config::_private::AnimationCurveIpc::EaseOut => {
|
||||
let inv = 1.0 - t;
|
||||
1.0 - inv * inv * inv
|
||||
}
|
||||
jay_config::_private::AnimationCurveIpc::EaseInOut => {
|
||||
if t < 0.5 {
|
||||
4.0 * t * t * t
|
||||
} else {
|
||||
let f = -2.0 * t + 2.0;
|
||||
1.0 - f * f * f / 2.0
|
||||
}
|
||||
}
|
||||
jay_config::_private::AnimationCurveIpc::Bezier { x1, y1, x2, y2 } => {
|
||||
cubic_bezier_y_at_x(t, x1, y1, x2, y2)
|
||||
}
|
||||
};
|
||||
Some(eased.clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Starts the open animation if animations are enabled. Inserts the
|
||||
/// toplevel into the state's active-animations list so the present loop
|
||||
/// can drive redraws.
|
||||
pub fn start_open_animation(&self) {
|
||||
let cfg = self.state.animations_config.get();
|
||||
if !cfg.enabled || cfg.open_duration_ms == 0 {
|
||||
return;
|
||||
}
|
||||
if self.anim_open_start_nsec.get().is_some() {
|
||||
return;
|
||||
}
|
||||
self.anim_open_start_nsec.set(Some(self.state.now_nsec()));
|
||||
self.state
|
||||
.active_animations
|
||||
.borrow_mut()
|
||||
.push(self.slf.clone());
|
||||
}
|
||||
|
||||
pub fn property_changed(&self, change: TlMatcherChange) {
|
||||
self.trigger_property_source();
|
||||
let mgr = &self.state.tl_matcher_manager;
|
||||
|
|
@ -1108,3 +1167,31 @@ pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &
|
|||
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates a cubic Bezier easing curve `cubic-bezier(x1, y1, x2, y2)` at the
|
||||
/// given input time `x`. P0=(0,0) and P3=(1,1) are fixed. Uses Newton-Raphson
|
||||
/// to invert the x(t) parametric form, then evaluates y(t).
|
||||
fn cubic_bezier_y_at_x(x: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
|
||||
fn bx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||
let it = 1.0 - t;
|
||||
3.0 * it * it * t * x1 + 3.0 * it * t * t * x2 + t * t * t
|
||||
}
|
||||
fn dbx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||
let it = 1.0 - t;
|
||||
3.0 * it * it * x1 + 6.0 * it * t * (x2 - x1) + 3.0 * t * t * (1.0 - x2)
|
||||
}
|
||||
let mut t = x;
|
||||
for _ in 0..8 {
|
||||
let err = bx(t, x1, x2) - x;
|
||||
if err.abs() < 1e-4 {
|
||||
break;
|
||||
}
|
||||
let d = dbx(t, x1, x2);
|
||||
if d.abs() < 1e-6 {
|
||||
break;
|
||||
}
|
||||
t = (t - err / d).clamp(0.0, 1.0);
|
||||
}
|
||||
let it = 1.0 - t;
|
||||
3.0 * it * it * t * y1 + 3.0 * it * t * t * y2 + t * t * t
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,6 +393,30 @@ pub struct BlurConfig {
|
|||
pub size: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AnimationCurve {
|
||||
Linear,
|
||||
EaseOut,
|
||||
EaseInOut,
|
||||
Bezier { x1: f32, y1: f32, x2: f32, y2: f32 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AnimationsConfig {
|
||||
pub enabled: Option<bool>,
|
||||
pub open_duration_ms: Option<u32>,
|
||||
pub open_curve: Option<AnimationCurve>,
|
||||
pub close_duration_ms: Option<u32>,
|
||||
pub close_curve: Option<AnimationCurve>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DamageVisualization {
|
||||
pub enabled: Option<bool>,
|
||||
pub color: Option<jay_config::theme::Color>,
|
||||
pub decay_ms: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DrmDeviceMatch {
|
||||
Any(Vec<DrmDeviceMatch>),
|
||||
|
|
@ -607,6 +631,8 @@ pub struct Config {
|
|||
pub window_rules: Vec<WindowRule>,
|
||||
pub layer_rules: Vec<LayerRule>,
|
||||
pub blur: Option<BlurConfig>,
|
||||
pub damage_visualization: Option<DamageVisualization>,
|
||||
pub animations: Option<AnimationsConfig>,
|
||||
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 animations;
|
||||
mod blur;
|
||||
mod capabilities;
|
||||
mod clean_logs_older_than;
|
||||
|
|
@ -19,6 +20,7 @@ pub mod config;
|
|||
mod connector;
|
||||
mod connector_match;
|
||||
mod content_type;
|
||||
mod damage_visualization;
|
||||
mod drm_device;
|
||||
mod drm_device_match;
|
||||
mod env;
|
||||
|
|
|
|||
73
toml-config/src/config/parsers/animations.rs
Normal file
73
toml-config/src/config/parsers/animations.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
AnimationCurve, AnimationsConfig,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, bol, int, opt, recover, str},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AnimationsConfigParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error("unknown animation curve `{0}`; expected one of: linear, ease-out, ease-in-out")]
|
||||
UnknownCurve(String),
|
||||
}
|
||||
|
||||
pub struct AnimationsConfigParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for AnimationsConfigParser<'_> {
|
||||
type Value = AnimationsConfig;
|
||||
type Error = AnimationsConfigParserError;
|
||||
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 (enabled_val, open_duration_val, open_curve_val, close_duration_val, close_curve_val) =
|
||||
ext.extract((
|
||||
recover(opt(bol("enabled"))),
|
||||
recover(opt(int("open-duration-ms"))),
|
||||
opt(str("open-curve")),
|
||||
recover(opt(int("close-duration-ms"))),
|
||||
opt(str("close-curve")),
|
||||
))?;
|
||||
let enabled = enabled_val.despan();
|
||||
let open_duration_ms = open_duration_val.despan().and_then(|v| u32::try_from(v).ok());
|
||||
let close_duration_ms = close_duration_val.despan().and_then(|v| u32::try_from(v).ok());
|
||||
let parse_curve = |val: Option<Spanned<&str>>| match val {
|
||||
Some(s) => match s.value {
|
||||
"linear" => Ok(Some(AnimationCurve::Linear)),
|
||||
"ease-out" => Ok(Some(AnimationCurve::EaseOut)),
|
||||
"ease-in-out" => Ok(Some(AnimationCurve::EaseInOut)),
|
||||
other => {
|
||||
Err(AnimationsConfigParserError::UnknownCurve(other.to_string()).spanned(s.span))
|
||||
}
|
||||
},
|
||||
None => Ok(None),
|
||||
};
|
||||
let open_curve = parse_curve(open_curve_val)?;
|
||||
let close_curve = parse_curve(close_curve_val)?;
|
||||
Ok(AnimationsConfig {
|
||||
enabled,
|
||||
open_duration_ms,
|
||||
open_curve,
|
||||
close_duration_ms,
|
||||
close_curve,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,10 @@ use {
|
|||
parsers::{
|
||||
action::ActionParser,
|
||||
actions::ActionsParser,
|
||||
animations::AnimationsConfigParser,
|
||||
blur::BlurConfigParser,
|
||||
clean_logs_older_than::CleanLogsOlderThanParser,
|
||||
damage_visualization::DamageVisualizationParser,
|
||||
client_rule::ClientRulesParser,
|
||||
color_management::ColorManagementParser,
|
||||
connector::ConnectorsParser,
|
||||
|
|
@ -157,7 +159,7 @@ impl Parser for ConfigParser<'_> {
|
|||
mouse_follows_focus,
|
||||
layer_rules_val,
|
||||
),
|
||||
(blur_val,),
|
||||
(blur_val, damage_visualization_val, animations_val),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -219,7 +221,11 @@ impl Parser for ConfigParser<'_> {
|
|||
recover(opt(bol("unstable-mouse-follows-focus"))),
|
||||
opt(val("layers")),
|
||||
),
|
||||
(opt(val("blur")),),
|
||||
(
|
||||
opt(val("blur")),
|
||||
opt(val("damage-visualization")),
|
||||
opt(val("animations")),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
if let Some(value) = keymap_val {
|
||||
|
|
@ -515,6 +521,26 @@ impl Parser for ConfigParser<'_> {
|
|||
Err(e) => log::warn!("Could not parse the blur config: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
let mut damage_visualization = None;
|
||||
if let Some(value) = damage_visualization_val {
|
||||
match value.parse(&mut DamageVisualizationParser(self.0)) {
|
||||
Ok(v) => damage_visualization = Some(v),
|
||||
Err(e) => log::warn!(
|
||||
"Could not parse the damage-visualization config: {}",
|
||||
self.0.error(e)
|
||||
),
|
||||
}
|
||||
}
|
||||
let mut animations = None;
|
||||
if let Some(value) = animations_val {
|
||||
match value.parse(&mut AnimationsConfigParser(self.0)) {
|
||||
Ok(v) => animations = Some(v),
|
||||
Err(e) => log::warn!(
|
||||
"Could not parse the animations 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) {
|
||||
|
|
@ -616,6 +642,8 @@ impl Parser for ConfigParser<'_> {
|
|||
window_rules,
|
||||
layer_rules,
|
||||
blur,
|
||||
damage_visualization,
|
||||
animations,
|
||||
pointer_revert_key,
|
||||
use_hardware_cursor: use_hardware_cursor.despan(),
|
||||
show_bar: show_bar.despan(),
|
||||
|
|
|
|||
65
toml-config/src/config/parsers/damage_visualization.rs
Normal file
65
toml-config/src/config/parsers/damage_visualization.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
DamageVisualization,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, bol, int, opt, recover, str},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::color::{ColorParser, ColorParserError},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DamageVisualizationParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Color(#[from] ColorParserError),
|
||||
}
|
||||
|
||||
pub struct DamageVisualizationParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for DamageVisualizationParser<'_> {
|
||||
type Value = DamageVisualization;
|
||||
type Error = DamageVisualizationParserError;
|
||||
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 (enabled_val, color_val, decay_val) = ext.extract((
|
||||
recover(opt(bol("enabled"))),
|
||||
opt(str("color")),
|
||||
recover(opt(int("decay-ms"))),
|
||||
))?;
|
||||
let enabled = enabled_val.despan();
|
||||
let color = match color_val {
|
||||
Some(s) => match ColorParser.parse_string(s.span, s.value) {
|
||||
Ok(c) => Some(c),
|
||||
Err(e) => {
|
||||
return Err(DamageVisualizationParserError::Color(e.value)
|
||||
.spanned(s.span));
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let decay_ms = decay_val.despan().and_then(|v| u64::try_from(v).ok());
|
||||
Ok(DamageVisualization {
|
||||
enabled,
|
||||
color,
|
||||
decay_ms,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,8 @@ mod toml;
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
Action, BlurConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||
Action, AnimationCurve, AnimationsConfig, BlurConfig, ClientRule, Config,
|
||||
ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, LayerKind, LayerRule, Output,
|
||||
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
|
||||
},
|
||||
|
|
@ -23,8 +24,12 @@ use {
|
|||
ahash::{AHashMap, AHashSet},
|
||||
error_reporter::Report,
|
||||
jay_config::{
|
||||
_private::{BlurConfigIpc, LayerKindIpc, LayerMatchIpc, LayerRuleIpc},
|
||||
_set_blur_config, _set_layer_rules, Axis,
|
||||
_private::{
|
||||
AnimationCurveIpc, AnimationsConfigIpc, BlurConfigIpc, DamageVisualizationIpc,
|
||||
LayerKindIpc, LayerMatchIpc, LayerRuleIpc,
|
||||
},
|
||||
_set_animations_config, _set_blur_config, _set_damage_visualization, _set_layer_rules,
|
||||
Axis,
|
||||
client::Client,
|
||||
config, config_dir,
|
||||
exec::{Command, set_env, unset_env},
|
||||
|
|
@ -1471,6 +1476,8 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
|||
persistent.window_rules.set(window_rules);
|
||||
push_layer_rules(&config.layer_rules);
|
||||
push_blur_config(config.blur);
|
||||
push_damage_visualization(config.damage_visualization);
|
||||
push_animations_config(config.animations);
|
||||
state.set_status(&config.status);
|
||||
persistent.actions.borrow_mut().clear();
|
||||
for a in config.named_actions {
|
||||
|
|
@ -1720,6 +1727,33 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
|||
}
|
||||
}
|
||||
|
||||
fn push_animations_config(anim: Option<AnimationsConfig>) {
|
||||
let default = AnimationsConfigIpc::default();
|
||||
let to_ipc = |c: AnimationCurve| match c {
|
||||
AnimationCurve::Linear => AnimationCurveIpc::Linear,
|
||||
AnimationCurve::EaseOut => AnimationCurveIpc::EaseOut,
|
||||
AnimationCurve::EaseInOut => AnimationCurveIpc::EaseInOut,
|
||||
AnimationCurve::Bezier { x1, y1, x2, y2 } => AnimationCurveIpc::Bezier { x1, y1, x2, y2 },
|
||||
};
|
||||
let cfg = match anim {
|
||||
Some(a) => AnimationsConfigIpc {
|
||||
enabled: a.enabled.unwrap_or(default.enabled),
|
||||
open_duration_ms: a
|
||||
.open_duration_ms
|
||||
.unwrap_or(default.open_duration_ms)
|
||||
.clamp(0, 10_000),
|
||||
open_curve: to_ipc(a.open_curve.unwrap_or(AnimationCurve::EaseOut)),
|
||||
close_duration_ms: a
|
||||
.close_duration_ms
|
||||
.unwrap_or(default.close_duration_ms)
|
||||
.clamp(0, 10_000),
|
||||
close_curve: to_ipc(a.close_curve.unwrap_or(AnimationCurve::EaseOut)),
|
||||
},
|
||||
None => default,
|
||||
};
|
||||
_set_animations_config(cfg);
|
||||
}
|
||||
|
||||
fn push_blur_config(blur: Option<BlurConfig>) {
|
||||
let default = BlurConfigIpc::default();
|
||||
let cfg = match blur {
|
||||
|
|
@ -1732,6 +1766,19 @@ fn push_blur_config(blur: Option<BlurConfig>) {
|
|||
_set_blur_config(cfg);
|
||||
}
|
||||
|
||||
fn push_damage_visualization(dv: Option<crate::config::DamageVisualization>) {
|
||||
let default = DamageVisualizationIpc::default();
|
||||
let cfg = match dv {
|
||||
Some(d) => DamageVisualizationIpc {
|
||||
enabled: d.enabled.unwrap_or(default.enabled),
|
||||
color: d.color.unwrap_or(default.color),
|
||||
decay_millis: d.decay_ms.unwrap_or(default.decay_millis),
|
||||
},
|
||||
None => default,
|
||||
};
|
||||
_set_damage_visualization(cfg);
|
||||
}
|
||||
|
||||
fn push_layer_rules(rules: &[LayerRule]) {
|
||||
let ipc: Vec<LayerRuleIpc> = rules
|
||||
.iter()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue