diff --git a/algorithms/src/lib.rs b/algorithms/src/lib.rs index 76553ac9..8887c5b2 100644 --- a/algorithms/src/lib.rs +++ b/algorithms/src/lib.rs @@ -1,6 +1,7 @@ #![allow( clippy::mem_replace_with_default, clippy::comparison_chain, + clippy::collapsible_else_if, clippy::needless_lifetimes )] diff --git a/algorithms/src/rect.rs b/algorithms/src/rect.rs index a0cc6e28..5e720071 100644 --- a/algorithms/src/rect.rs +++ b/algorithms/src/rect.rs @@ -5,31 +5,72 @@ use { std::fmt::{Debug, Formatter}, }; +pub trait Tag: Copy + Eq + Ord + Debug + Default + Sized { + const IS_SIGNIFICANT: bool; + + fn constrain(self) -> Self; +} + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Default)] +pub struct NoTag; + +impl Tag for NoTag { + const IS_SIGNIFICANT: bool = false; + + #[inline(always)] + fn constrain(self) -> Self { + Self + } +} + +impl Tag for u32 { + const IS_SIGNIFICANT: bool = true; + + #[inline(always)] + fn constrain(self) -> Self { + self & 1 + } +} + #[derive(Copy, Clone, Eq, PartialEq, Default)] -pub struct RectRaw { +pub struct RectRaw +where + T: Tag, +{ pub x1: i32, pub y1: i32, pub x2: i32, pub y2: i32, + pub tag: T, } -impl Debug for RectRaw { +impl Debug for RectRaw +where + T: Tag, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Rect") + let mut debug = f.debug_struct("Rect"); + debug .field("x1", &self.x1) .field("y1", &self.y1) .field("x2", &self.x2) .field("y2", &self.y2) .field("width", &(self.x2 - self.x1)) - .field("height", &(self.y2 - self.y1)) - .finish() + .field("height", &(self.y2 - self.y1)); + if T::IS_SIGNIFICANT { + debug.field("tag", &self.tag); + } + debug.finish() } } -impl RectRaw { +impl RectRaw +where + T: Tag, +{ fn is_empty(&self) -> bool { self.x1 == self.x2 || self.y1 == self.y2 } } -type Container = SmallVec<[RectRaw; 1]>; +type Container = SmallVec<[RectRaw; 1]>; diff --git a/algorithms/src/rect/region.rs b/algorithms/src/rect/region.rs index 6cb8fa1c..1ed22bd5 100644 --- a/algorithms/src/rect/region.rs +++ b/algorithms/src/rect/region.rs @@ -1,44 +1,64 @@ use { crate::{ - rect::{Container, RectRaw}, + rect::{Container, NoTag, RectRaw, Tag}, windows::WindowsExt, }, - std::{cmp::Ordering, collections::BinaryHeap, mem, ops::Deref}, + std::{cmp::Ordering, collections::BinaryHeap, marker::PhantomData, mem, ops::Deref}, }; pub fn union(left: &Container, right: &Container) -> Container { - op::(left, right) + op::<_, _, _, Union>(left, right) } pub fn subtract(left: &Container, right: &Container) -> Container { - op::(left, right) + op::<_, _, _, Subtract>(left, right) } -struct Bands<'a> { - rects: &'a [RectRaw], +pub fn intersect(left: &Container, right: &Container) -> Container { + op::<_, _, _, Intersect>(left, right) +} + +pub fn intersect_tagged(left: &Container, right: &Container) -> Container { + op::<_, _, _, Intersect>(left, right) +} + +struct Bands<'a, T> +where + T: Tag, +{ + rects: &'a [RectRaw], } #[derive(Copy, Clone)] -struct Band<'a> { - rects: &'a [RectRaw], +struct Band<'a, T> +where + T: Tag, +{ + rects: &'a [RectRaw], y1: i32, y2: i32, } -impl<'a> Band<'a> { - fn can_merge_with(&self, next: &Band) -> bool { +impl<'a, T> Band<'a, T> +where + T: Tag, +{ + fn can_merge_with(&self, next: &Band<'_, T>) -> bool { next.rects.len() == self.rects.len() && next.y1 == self.y2 && next .rects .iter() .zip(self.rects.iter()) - .all(|(a, b)| (a.x1, a.x2) == (b.x1, b.x2)) + .all(|(a, b)| (a.x1, a.x2, a.tag) == (b.x1, b.x2, b.tag)) } } -impl<'a> Iterator for Bands<'a> { - type Item = Band<'a>; +impl<'a, T> Iterator for Bands<'a, T> +where + T: Tag, +{ + type Item = Band<'a, T>; fn next(&mut self) -> Option { if self.rects.is_empty() { @@ -62,7 +82,10 @@ impl<'a> Iterator for Bands<'a> { } #[inline] -pub fn extents(a: &[RectRaw]) -> RectRaw { +pub fn extents(a: &[RectRaw]) -> RectRaw +where + T: Tag, +{ let mut a = a.iter(); let mut res = match a.next() { Some(a) => *a, @@ -74,10 +97,21 @@ pub fn extents(a: &[RectRaw]) -> RectRaw { res.x2 = res.x2.max(a.x2); res.y2 = res.y2.max(a.y2); } - res + RectRaw { + x1: res.x1, + y1: res.y1, + x2: res.x2, + y2: res.y2, + tag: NoTag, + } } -fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { +fn op>(a: &[RectRaw], b: &[RectRaw]) -> Container +where + T1: Tag, + T2: Tag, + T3: Tag, +{ let mut res = Container::new(); let mut prev_band_y2 = 0; @@ -100,7 +134,7 @@ fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { } macro_rules! append_nonoverlapping { - ($append_opt:expr, $a:expr, $a_opt:expr, $a_bands:expr, $b:expr) => {{ + ($append_opt:expr, $map:ident, $a:expr, $a_opt:expr, $a_bands:expr, $b:expr) => {{ if $append_opt { let y2 = $a.y2.min($b.y1); cur_band_start = res.len(); @@ -111,6 +145,7 @@ fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { y1: $a.y1, x2: rect.x2, y2, + tag: O::$map(rect.tag), }); } fixup_new_band!($a.y1, y2); @@ -125,9 +160,9 @@ fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { while let (Some(a), Some(b)) = (&mut a_opt, &mut b_opt) { if a.y1 < b.y1 { - append_nonoverlapping!(O::APPEND_NON_A, a, a_opt, a_bands, b); + append_nonoverlapping!(O::APPEND_NON_A, map_t2_to_t1, a, a_opt, a_bands, b); } else if b.y1 < a.y1 { - append_nonoverlapping!(O::APPEND_NON_B, b, b_opt, b_bands, a); + append_nonoverlapping!(O::APPEND_NON_B, map_t3_to_t1, b, b_opt, b_bands, a); } else { let y2 = a.y2.min(b.y2); cur_band_start = res.len(); @@ -149,7 +184,7 @@ fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { } macro_rules! push_trailing { - ($a_opt:expr, $a_bands:expr) => {{ + ($a_opt:expr, $a_bands:expr, $map:ident) => {{ while let Some(a) = $a_opt { cur_band_start = res.len(); res.reserve(a.rects.len()); @@ -159,6 +194,7 @@ fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { y1: a.y1, x2: rect.x2, y2: a.y2, + tag: O::$map(rect.tag), }); } fixup_new_band!(a.y1, a.y2); @@ -168,25 +204,28 @@ fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { } if O::APPEND_NON_A { - push_trailing!(a_opt, a_bands); + push_trailing!(a_opt, a_bands, map_t2_to_t1); } if O::APPEND_NON_B { - push_trailing!(b_opt, b_bands); + push_trailing!(b_opt, b_bands, map_t3_to_t1); } res.shrink_to_fit(); res } -fn coalesce(new: &mut Container, a: usize, b: usize, y2: i32) -> bool { +fn coalesce(new: &mut Container, a: usize, b: usize, y2: i32) -> bool +where + T: Tag, +{ if new.len() - b != b - a { return false; } let slice_a = &new[a..b]; let slice_b = &new[b..]; for (a, b) in slice_a.iter().zip(slice_b.iter()) { - if (a.x1, a.x2) != (b.x1, b.x2) { + if (a.x1, a.x2, a.tag) != (b.x1, b.x2, b.tag) { return false; } } @@ -197,16 +236,25 @@ fn coalesce(new: &mut Container, a: usize, b: usize, y2: i32) -> bool { true } -trait Op { +trait Op +where + T1: Tag, + T2: Tag, + T3: Tag, +{ const APPEND_NON_A: bool; const APPEND_NON_B: bool; - fn handle_band(new: &mut Container, a: &[RectRaw], b: &[RectRaw], y1: i32, y2: i32); + fn handle_band(new: &mut Container, a: &[RectRaw], b: &[RectRaw], y1: i32, y2: i32); + + fn map_t2_to_t1(tag: T2) -> T1; + + fn map_t3_to_t1(tag: T3) -> T1; } struct Union; -impl Op for Union { +impl Op for Union { const APPEND_NON_A: bool = true; const APPEND_NON_B: bool = true; @@ -216,7 +264,13 @@ impl Op for Union { macro_rules! push { () => { - new.push(RectRaw { x1, y1, x2, y2 }); + new.push(RectRaw { + x1, + y1, + x2, + y2, + tag: NoTag, + }); }; } @@ -272,11 +326,21 @@ impl Op for Union { push!(); } + + #[inline(always)] + fn map_t2_to_t1(_tag: NoTag) -> NoTag { + NoTag + } + + #[inline(always)] + fn map_t3_to_t1(_tag: NoTag) -> NoTag { + NoTag + } } struct Subtract; -impl Op for Subtract { +impl Op for Subtract { const APPEND_NON_A: bool = true; const APPEND_NON_B: bool = false; @@ -291,6 +355,7 @@ impl Op for Subtract { y1, x2: $x2, y2, + tag: NoTag, }); }; } @@ -337,33 +402,145 @@ impl Op for Subtract { pull!(); } } + + #[inline(always)] + fn map_t2_to_t1(_tag: NoTag) -> NoTag { + NoTag + } + + #[inline(always)] + fn map_t3_to_t1(_tag: NoTag) -> NoTag { + NoTag + } +} + +struct Intersect(PhantomData); + +impl Op for Intersect +where + T: Tag, +{ + const APPEND_NON_A: bool = false; + const APPEND_NON_B: bool = false; + + fn handle_band(new: &mut Container, a: &[RectRaw], b: &[RectRaw], y1: i32, y2: i32) { + let mut x1; + let mut x2; + let mut tag; + + macro_rules! push { + ($x2:expr) => { + new.push(RectRaw { + x1, + y1, + x2: $x2, + y2, + tag, + }); + }; + } + + let mut a_iter = a.iter(); + let mut b_iter = b.iter(); + + macro_rules! pull { + () => { + match a_iter.next() { + Some(n) => { + x1 = n.x1; + x2 = n.x2; + tag = n.tag; + } + _ => return, + } + }; + } + + pull!(); + + let mut b_opt = b_iter.next(); + + while let Some(b) = b_opt { + if b.x2 <= x1 { + b_opt = b_iter.next(); + } else if b.x1 >= x2 { + pull!(); + } else { + x1 = x1.max(b.x1); + if x2 <= b.x2 { + push!(x2); + pull!(); + } else { + push!(b.x2); + b_opt = b_iter.next(); + } + } + } + } + + #[inline(always)] + fn map_t2_to_t1(_tag: T) -> T { + unreachable!() + } + + #[inline(always)] + fn map_t3_to_t1(_tag: NoTag) -> T { + unreachable!() + } } pub fn rects_to_bands(rects_tmp: &[RectRaw]) -> Container { + rects_to_bands_(rects_tmp) +} + +pub fn rects_to_bands_tagged(rects_tmp: &[RectRaw]) -> Container { + rects_to_bands_(rects_tmp) +} + +#[inline(always)] +fn rects_to_bands_(rects_tmp: &[RectRaw]) -> Container +where + T: Tag, +{ #[derive(Copy, Clone)] - struct W(RectRaw); - impl Eq for W {} - impl PartialEq for W { + struct W(RectRaw) + where + T: Tag; + impl Eq for W where T: Tag {} + impl PartialEq for W + where + T: Tag, + { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } - impl PartialOrd for W { + impl PartialOrd for W + where + T: Tag, + { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } - impl Ord for W { + impl Ord for W + where + T: Tag, + { fn cmp(&self, other: &Self) -> Ordering { self.0 .y1 .cmp(&other.0.y1) .then_with(|| self.0.x1.cmp(&other.0.x1)) + .then_with(|| self.0.tag.cmp(&other.0.tag)) .reverse() } } - impl Deref for W { - type Target = RectRaw; + impl Deref for W + where + T: Tag, + { + type Target = RectRaw; fn deref(&self) -> &Self::Target { &self.0 } @@ -411,17 +588,60 @@ pub fn rects_to_bands(rects_tmp: &[RectRaw]) -> Container { check_rect!(rect); let mut x1 = rect.x1; let mut x2 = rect.x2; + let mut tag: T = rect.tag; while let Some(mut rect) = rects.peek().copied() { check_rect!(rect); - if rect.x1 > x2 { - res.push(RectRaw { x1, x2, y1, y2 }); + if rect.x1 > x2 || (rect.tag != tag && rect.x1 == x2) { + res.push(RectRaw { + x1, + x2, + y1, + y2, + tag: tag.constrain(), + }); x1 = rect.x1; x2 = rect.x2; + tag = rect.tag; } else { - x2 = x2.max(rect.x2); + if rect.tag == tag { + x2 = x2.max(rect.x2); + } else if rect.tag > tag { + if rect.x2 > x2 { + rect.0.x1 = x2; + rect.0.y1 = y1; + rect.0.y2 = y2; + rects.push(rect); + } + } else { + if x2 > rect.x2 { + rects.push(W(RectRaw { + x1: rect.x2, + y1, + x2, + y2, + tag, + })); + } + res.push(RectRaw { + x1, + y1, + x2: rect.x1, + y2, + tag: tag.constrain(), + }); + x1 = rect.x1; + x2 = rect.x2; + tag = rect.tag; + } } } - res.push(RectRaw { x1, x2, y1, y2 }); + res.push(RectRaw { + x1, + x2, + y1, + y2, + tag: tag.constrain(), + }); } break; } diff --git a/build/vulkan.rs b/build/vulkan.rs index 52c306bb..fb649b57 100644 --- a/build/vulkan.rs +++ b/build/vulkan.rs @@ -13,6 +13,8 @@ pub fn main() -> anyhow::Result<()> { compile_simple("fill.vert")?; compile_simple("tex.vert")?; compile_simple("tex.frag")?; + compile_simple("out.vert")?; + compile_simple("out.frag")?; Ok(()) } diff --git a/release-notes.md b/release-notes.md index 799375d2..126f6398 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,6 +1,14 @@ # Unreleased - Various bugfixes. +- The vulkan renderer now only renders in damaged areas. This has exposed several places + where the damage tracking was incorrect. There might be additional damage tracking bugs. + Such bugs manifest through flickering or through areas getting stuck with an old image. + If you encounter such an issue, please open a bug. +- The vulkan renderer now performs blending in linear space. A green window with 50% + opacity on top of a red window will produce a perfectly yellow image instead of a muddy + yellow. The blend buffer is only used for those areas of the screen where blending is + observable. This should have no impact on performance in the common case. # 1.9.1 (2025-02-13) diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index d8bddce2..e89574f1 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -570,7 +570,7 @@ impl MetalConnector { match opt { GfxApiOpt::Sync => {} GfxApiOpt::FillRect(fr) => { - if fr.color == Color::SOLID_BLACK { + if fr.effective_color() == Color::SOLID_BLACK { // Black fills can be ignored because this is the CRTC background color. if fr.rect.is_covering() { // If fill covers the entire screen, we don't have to look further. @@ -749,6 +749,7 @@ impl MetalConnector { ReleaseSync::Explicit, &latched.pass, &latched.damage, + buffer.blend_buffer.as_ref(), ) .map_err(MetalError::RenderFrame)?; sync_file = buffer.copy_to_dev(sf)?; diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index a071a83a..c49c68c0 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -18,8 +18,8 @@ use { edid::{CtaDataBlock, Descriptor, EdidExtension}, format::{ARGB8888, Format, XRGB8888}, gfx_api::{ - AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, SyncFile, - needs_render_usage, + AcquireSync, GfxBlendBuffer, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, + SyncFile, needs_render_usage, }, ifs::{ wl_output::OutputId, @@ -2607,6 +2607,15 @@ impl MetalBackend { ctx: &MetalRenderContext, cursor: bool, ) -> Result<[RenderBuffer; N], MetalError> { + let mut blend_buffer = None; + if !cursor { + match ctx.gfx.acquire_blend_buffer(width, height) { + Ok(bb) => blend_buffer = Some(bb), + Err(e) => { + log::warn!("Could not create blend buffer: {}", ErrorFmt(e)); + } + } + } let mut damage_queue = ArrayVec::from(DamageQueue::new::()); let mut create = || { self.create_scanout_buffer( @@ -2618,6 +2627,7 @@ impl MetalBackend { ctx, cursor, damage_queue.pop().unwrap(), + blend_buffer.clone(), ) }; let mut array = ArrayVec::<_, N>::new(); @@ -2640,6 +2650,7 @@ impl MetalBackend { render_ctx: &MetalRenderContext, cursor: bool, damage_queue: DamageQueue, + blend_buffer: Option>, ) -> Result { let ctx = dev.ctx.get(); let dev_gfx_formats = ctx.gfx.formats(); @@ -2771,6 +2782,7 @@ impl MetalBackend { damage_queue, dev_bo, _render_bo: render_bo, + blend_buffer, dev_fb, dev_tex, render_tex, @@ -2996,6 +3008,7 @@ pub struct RenderBuffer { pub damage_queue: DamageQueue, pub dev_bo: GbmBo, pub _render_bo: Option, + pub blend_buffer: Option>, // ctx = dev // buffer location = dev pub dev_fb: Rc, diff --git a/src/backends/x.rs b/src/backends/x.rs index 48ae4284..6bfd3aae 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -755,6 +755,7 @@ impl XBackend { ReleaseSync::Implicit, &image.tex.get(), true, + None, ); if let Err(e) = res { log::error!("Could not render screen: {}", ErrorFmt(e)); diff --git a/src/cursor.rs b/src/cursor.rs index 4f093f93..faae24f7 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -391,6 +391,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -414,6 +415,7 @@ impl Cursor for StaticCursor { None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -455,6 +457,7 @@ impl Cursor for AnimatedCursor { None, AcquireSync::None, ReleaseSync::None, + false, ); } } diff --git a/src/format.rs b/src/format.rs index 03dec08d..b4f7c3dc 100644 --- a/src/format.rs +++ b/src/format.rs @@ -408,7 +408,7 @@ static XBGR16161616: &Format = &Format { ..default(ConfigFormat::XBGR16161616) }; -static ABGR16161616F: &Format = &Format { +pub static ABGR16161616F: &Format = &Format { name: "abgr16161616f", vk_format: vk::Format::R16G16B16A16_SFLOAT, drm: fourcc_code('A', 'B', '4', 'H'), diff --git a/src/gfx_api.rs b/src/gfx_api.rs index ca7cadac..4577f8a3 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -116,7 +116,7 @@ impl SampleRect { } } -#[derive(Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct FramebufferRect { pub x1: f32, pub x2: f32, @@ -165,12 +165,45 @@ impl FramebufferRect { pub fn is_covering(&self) -> bool { self.x1 == -1.0 && self.y1 == -1.0 && self.x2 == 1.0 && self.y2 == 1.0 } + + pub fn to_rect(&self, width: f32, height: f32) -> Rect { + let mut x1 = self.x1; + let mut x2 = self.x2; + let mut y1 = self.y1; + let mut y2 = self.y2; + (x1, y1, x2, y2) = match self.output_transform { + Transform::None => (x1, y1, x2, y2), + Transform::Rotate90 => (y1, -x2, y2, -x1), + Transform::Rotate180 => (-x2, -y2, -x1, -y1), + Transform::Rotate270 => (-y2, x1, -y1, x2), + Transform::Flip => (-x2, y1, -x1, y2), + Transform::FlipRotate90 => (y1, x1, y2, x2), + Transform::FlipRotate180 => (x1, -y2, x2, -y1), + Transform::FlipRotate270 => (-y2, -x2, -y1, -x1), + }; + let x1 = ((x1 + 1.0) / 2.0 * width).round() as i32; + let x2 = ((x2 + 1.0) / 2.0 * width).round() as i32; + let y1 = ((y1 + 1.0) / 2.0 * height).round() as i32; + let y2 = ((y2 + 1.0) / 2.0 * height).round() as i32; + Rect::new(x1, y1, x2, y2).unwrap_or_default() + } } #[derive(Debug)] pub struct FillRect { pub rect: FramebufferRect, pub color: Color, + pub alpha: Option, +} + +impl FillRect { + pub fn effective_color(&self) -> Color { + let mut color = self.color; + if let Some(alpha) = self.alpha { + color = color * alpha; + } + color + } } pub struct CopyTexture { @@ -181,6 +214,7 @@ pub struct CopyTexture { pub acquire_sync: AcquireSync, pub release_sync: ReleaseSync, pub alpha: Option, + pub opaque: bool, } #[derive(Clone, Debug)] @@ -254,6 +288,10 @@ pub enum ResetStatus { Other(u32), } +pub trait GfxBlendBuffer: Debug { + fn into_any(self: Rc) -> Rc; +} + pub trait GfxFramebuffer: Debug { fn physical_size(&self) -> (i32, i32); @@ -264,9 +302,15 @@ pub trait GfxFramebuffer: Debug { ops: &[GfxApiOpt], clear: Option<&Color>, region: &Region, + blend_buffer: Option<&Rc>, ) -> Result, GfxError>; fn format(&self) -> &'static Format; + + fn full_region(&self) -> Region { + let (width, height) = self.physical_size(); + Region::new2(Rect::new_sized_unchecked(0, 0, width, height)) + } } pub trait GfxInternalFramebuffer: GfxFramebuffer { @@ -291,14 +335,16 @@ impl dyn GfxFramebuffer { release_sync: ReleaseSync, ops: &[GfxApiOpt], clear: Option<&Color>, + blend_buffer: Option<&Rc>, ) -> Result, GfxError> { - self.clone() - .render_with_region(acquire_sync, release_sync, ops, clear, &self.full_region()) - } - - fn full_region(&self) -> Region { - let (width, height) = self.physical_size(); - Region::new2(Rect::new_sized_unchecked(0, 0, width, height)) + self.clone().render_with_region( + acquire_sync, + release_sync, + ops, + clear, + &self.full_region(), + blend_buffer, + ) } pub fn clear( @@ -318,7 +364,13 @@ impl dyn GfxFramebuffer { b: f32, a: f32, ) -> Result, GfxError> { - self.render(acquire_sync, release_sync, &[], Some(&Color { r, g, b, a })) + self.render( + acquire_sync, + release_sync, + &[], + Some(&Color { r, g, b, a }), + None, + ) } pub fn logical_size(&self, transform: Transform) -> (i32, i32) { @@ -360,9 +412,10 @@ impl dyn GfxFramebuffer { resv.cloned(), acquire_sync, release_sync, + false, ); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); - self.render(fb_acquire_sync, fb_release_sync, &ops, clear) + self.render(fb_acquire_sync, fb_release_sync, &ops, clear, None) } pub fn render_custom( @@ -371,12 +424,13 @@ impl dyn GfxFramebuffer { release_sync: ReleaseSync, scale: Scale, clear: Option<&Color>, + blend_buffer: Option<&Rc>, f: &mut dyn FnMut(&mut RendererBase), ) -> Result, GfxError> { let mut ops = vec![]; let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); - self.render(acquire_sync, release_sync, &ops, clear) + self.render(acquire_sync, release_sync, &ops, clear, blend_buffer) } pub fn create_render_pass( @@ -413,6 +467,7 @@ impl dyn GfxFramebuffer { release_sync: ReleaseSync, pass: &GfxRenderPass, region: &Region, + blend_buffer: Option<&Rc>, ) -> Result, GfxError> { self.clone().render_with_region( acquire_sync, @@ -420,6 +475,7 @@ impl dyn GfxFramebuffer { &pass.ops, pass.clear.as_ref(), region, + blend_buffer, ) } @@ -433,6 +489,7 @@ impl dyn GfxFramebuffer { scale: Scale, render_hardware_cursor: bool, fill_black_in_grace_period: bool, + blend_buffer: Option<&Rc>, ) -> Result, GfxError> { self.render_node( acquire_sync, @@ -446,6 +503,7 @@ impl dyn GfxFramebuffer { node.has_fullscreen(), fill_black_in_grace_period, node.global.persistent.transform.get(), + blend_buffer, ) } @@ -462,6 +520,7 @@ impl dyn GfxFramebuffer { black_background: bool, fill_black_in_grace_period: bool, transform: Transform, + blend_buffer: Option<&Rc>, ) -> Result, GfxError> { let pass = self.create_render_pass( node, @@ -475,7 +534,13 @@ impl dyn GfxFramebuffer { transform, None, ); - self.perform_render_pass(acquire_sync, release_sync, &pass, &self.full_region()) + self.perform_render_pass( + acquire_sync, + release_sync, + &pass, + &self.full_region(), + blend_buffer, + ) } pub fn render_hardware_cursor( @@ -498,7 +563,13 @@ impl dyn GfxFramebuffer { }, }; cursor.render_hardware_cursor(&mut renderer); - self.render(acquire_sync, release_sync, &ops, Some(&Color::TRANSPARENT)) + self.render( + acquire_sync, + release_sync, + &ops, + Some(&Color::TRANSPARENT), + None, + ) } } @@ -665,6 +736,12 @@ pub trait GfxContext: Debug { } Rc::new(Dummy(size)) } + + fn acquire_blend_buffer( + &self, + width: i32, + height: i32, + ) -> Result, GfxError>; } #[derive(Clone, Debug)] diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index bbf7ce77..f10acb32 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -197,12 +197,14 @@ enum RenderError { UnsupportedShmFormat(&'static str), #[error("Could not access the client memory")] AccessFailed(#[source] Box), + #[error("OpenGL does not support blend buffers")] + NoBlendBuffer, } #[derive(Default)] struct GfxGlState { triangles: RefCell>, - fill_rect: VecStorage<&'static FillRect>, + fill_rect: VecStorage, copy_tex: VecStorage<&'static CopyTexture>, } @@ -233,7 +235,11 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { } } GfxApiOpt::FillRect(f) => { - fill_rect.push(f); + fill_rect.push(FillRect { + rect: f.rect, + color: f.effective_color(), + alpha: None, + }); i += 1; } GfxApiOpt::CopyTexture(c) => { @@ -249,7 +255,7 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option { triangles.clear(); let mut color = None; while i < fill_rect.len() { - let fr = fill_rect[i]; + let fr = &fill_rect[i]; match color { None => color = Some(fr.color), Some(c) if c == fr.color => {} diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 59dbbfe4..cd824c05 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -4,8 +4,8 @@ use { cpu_worker::CpuWorker, format::{Format, XRGB8888}, gfx_api::{ - AsyncShmGfxTexture, BufferResvUser, GfxContext, GfxError, GfxFormat, GfxFramebuffer, - GfxImage, GfxInternalFramebuffer, ResetStatus, ShmGfxTexture, + AsyncShmGfxTexture, BufferResvUser, GfxBlendBuffer, GfxContext, GfxError, GfxFormat, + GfxFramebuffer, GfxImage, GfxInternalFramebuffer, ResetStatus, ShmGfxTexture, }, gfx_apis::gl::{ GfxGlState, RenderError, Texture, @@ -339,4 +339,12 @@ impl GfxContext for GlRenderContext { fn sync_obj_ctx(&self) -> Option<&Rc> { Some(&self.sync_ctx) } + + fn acquire_blend_buffer( + &self, + _width: i32, + _height: i32, + ) -> Result, GfxError> { + Err(GfxError(Box::new(RenderError::NoBlendBuffer))) + } } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index edd03187..7c0c968d 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -2,9 +2,9 @@ use { crate::{ format::Format, gfx_api::{ - AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxError, GfxFramebuffer, - GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer, ReleaseSync, ShmMemory, - SyncFile, + AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxBlendBuffer, GfxError, + GfxFramebuffer, GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer, + ReleaseSync, ShmMemory, SyncFile, }, gfx_apis::gl::{ RenderError, @@ -106,6 +106,7 @@ impl GfxFramebuffer for Framebuffer { ops: &[GfxApiOpt], clear: Option<&Color>, _region: &Region, + _blend_buffer: Option<&Rc>, ) -> Result, GfxError> { (*self) .render(acquire_sync, ops, clear) diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index d48e9526..24b13165 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -1,4 +1,5 @@ mod allocator; +mod blend_buffer; mod bo_allocator; mod command; mod descriptor; @@ -24,9 +25,9 @@ use { cpu_worker::{CpuWorker, jobs::read_write::ReadWriteJobError}, format::Format, gfx_api::{ - AsyncShmGfxTexture, GfxContext, GfxError, GfxFormat, GfxImage, GfxInternalFramebuffer, - GfxStagingBuffer, ResetStatus, STAGING_DOWNLOAD, STAGING_UPLOAD, ShmGfxTexture, - StagingBufferUsecase, + AsyncShmGfxTexture, GfxBlendBuffer, GfxContext, GfxError, GfxFormat, GfxImage, + GfxInternalFramebuffer, GfxStagingBuffer, ResetStatus, STAGING_DOWNLOAD, + STAGING_UPLOAD, ShmGfxTexture, StagingBufferUsecase, }, gfx_apis::vulkan::{ image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, @@ -204,6 +205,8 @@ pub enum VulkanError { UndefinedContents, #[error("The framebuffer is being used by the transfer queue")] BusyInTransfer, + #[error("Driver does not support descriptor buffers")] + NoDescriptorBuffer, } impl From for GfxError { @@ -270,6 +273,7 @@ impl GfxContext for Context { let old = old.into_texture().into_vk(&self.0.device.device); let shm = match &old.ty { VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Blend(_) => unreachable!(), VulkanImageMemory::Internal(shm) => shm, }; if old.width as i32 == width @@ -350,6 +354,15 @@ impl GfxContext for Context { .device .create_staging_shell(size as u64, upload, download) } + + fn acquire_blend_buffer( + &self, + width: i32, + height: i32, + ) -> Result, GfxError> { + let buffer = self.0.acquire_blend_buffer(width, height)?; + Ok(buffer) + } } impl Drop for Context { diff --git a/src/gfx_apis/vulkan/blend_buffer.rs b/src/gfx_apis/vulkan/blend_buffer.rs new file mode 100644 index 00000000..298c6527 --- /dev/null +++ b/src/gfx_apis/vulkan/blend_buffer.rs @@ -0,0 +1,139 @@ +use { + crate::{ + gfx_api::GfxBlendBuffer, + gfx_apis::vulkan::{ + VulkanError, + format::{BLEND_FORMAT, BLEND_USAGE}, + image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory}, + renderer::VulkanRenderer, + }, + utils::on_drop::OnDrop, + }, + ash::vk::{ + DescriptorDataEXT, DescriptorGetInfoEXT, DescriptorImageInfo, DescriptorType, Extent3D, + ImageAspectFlags, ImageCreateInfo, ImageLayout, ImageSubresourceRange, ImageTiling, + ImageType, ImageViewCreateInfo, ImageViewType, SampleCountFlags, SharingMode, + }, + gpu_alloc::UsageFlags, + std::{any::Any, cell::Cell, collections::hash_map::Entry, rc::Rc}, +}; + +impl VulkanRenderer { + pub fn acquire_blend_buffer( + self: &Rc, + width: i32, + height: i32, + ) -> Result, VulkanError> { + let Some(db) = &self.device.descriptor_buffer else { + return Err(VulkanError::NoDescriptorBuffer); + }; + if width <= 0 || height <= 0 { + return Err(VulkanError::NonPositiveImageSize); + } + let width = width as u32; + let height = height as u32; + let cached = &mut *self.blend_buffers.borrow_mut(); + let cached = cached.entry((width, height)); + if let Entry::Occupied(entry) = &cached { + if let Some(buffer) = entry.get().upgrade() { + return Ok(buffer); + } + } + let limits = self.device.blend_limits; + if width > limits.max_width || height > limits.max_height { + return Err(VulkanError::ImageTooLarge); + } + let create_info = ImageCreateInfo::default() + .image_type(ImageType::TYPE_2D) + .format(BLEND_FORMAT.vk_format) + .mip_levels(1) + .array_layers(1) + .tiling(ImageTiling::OPTIMAL) + .samples(SampleCountFlags::TYPE_1) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .extent(Extent3D { + width, + height, + depth: 1, + }) + .usage(BLEND_USAGE); + let image = unsafe { self.device.device.create_image(&create_info, None) }; + let image = image.map_err(VulkanError::CreateImage)?; + let destroy_image = OnDrop(|| unsafe { self.device.device.destroy_image(image, None) }); + let memory_requirements = + unsafe { self.device.device.get_image_memory_requirements(image) }; + let allocation = + self.allocator + .alloc(&memory_requirements, UsageFlags::FAST_DEVICE_ACCESS, false)?; + let res = unsafe { + self.device + .device + .bind_image_memory(image, allocation.memory, allocation.offset) + }; + res.map_err(VulkanError::BindImageMemory)?; + let image_view_create_info = ImageViewCreateInfo::default() + .image(image) + .format(BLEND_FORMAT.vk_format) + .view_type(ImageViewType::TYPE_2D) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let view = unsafe { + self.device + .device + .create_image_view(&image_view_create_info, None) + }; + let view = view.map_err(VulkanError::CreateImageView)?; + destroy_image.forget(); + let sampled_image_descriptor = { + let mut buf = vec![0; self.device.sampled_image_descriptor_size].into_boxed_slice(); + let image_info = DescriptorImageInfo::default() + .image_view(view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let info = DescriptorGetInfoEXT::default() + .ty(DescriptorType::SAMPLED_IMAGE) + .data(DescriptorDataEXT { + p_sampled_image: &image_info, + }); + unsafe { + db.get_descriptor(&info, &mut buf); + } + buf + }; + let img = Rc::new(VulkanImage { + renderer: self.clone(), + format: BLEND_FORMAT, + width, + height, + stride: 0, + texture_view: view, + render_view: None, + image, + is_undefined: Cell::new(true), + contents_are_undefined: Cell::new(true), + queue_state: Cell::new(QueueState::Acquired { + family: QueueFamily::Gfx, + }), + ty: VulkanImageMemory::Blend(allocation), + bridge: None, + shader_read_only_optimal_descriptor: Box::new([]), + sampled_image_descriptor, + descriptor_buffer_version: Default::default(), + descriptor_buffer_offset: Default::default(), + execution_version: Default::default(), + }); + cached.insert_entry(Rc::downgrade(&img)); + Ok(img) + } +} + +impl GfxBlendBuffer for VulkanImage { + fn into_any(self: Rc) -> Rc { + self + } +} diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs index 64807b38..8ccb725a 100644 --- a/src/gfx_apis/vulkan/descriptor.rs +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -1,9 +1,12 @@ use { crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice, sampler::VulkanSampler}, arrayvec::ArrayVec, - ash::vk::{ - DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags, - DescriptorSetLayoutCreateInfo, DescriptorType, DeviceSize, ShaderStageFlags, + ash::{ + ext::descriptor_buffer, + vk::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags, + DescriptorSetLayoutCreateInfo, DescriptorType, DeviceSize, ShaderStageFlags, + }, }, std::{rc::Rc, slice}, }; @@ -14,7 +17,6 @@ pub(super) struct VulkanDescriptorSetLayout { pub(super) size: DeviceSize, pub(super) offsets: ArrayVec, pub(super) _sampler: Option>, - pub(super) has_sampler: bool, } impl Drop for VulkanDescriptorSetLayout { @@ -28,7 +30,7 @@ impl Drop for VulkanDescriptorSetLayout { } impl VulkanDevice { - pub(super) fn create_descriptor_set_layout( + pub(super) fn create_tex_descriptor_set_layout( self: &Rc, sampler: &Rc, ) -> Result, VulkanError> { @@ -52,9 +54,7 @@ impl VulkanDevice { let mut size = 0; let mut offsets = ArrayVec::new(); if let Some(db) = &self.descriptor_buffer { - size = unsafe { db.get_descriptor_set_layout_size(layout) }; - size = - (size + self.descriptor_buffer_offset_mask) & !self.descriptor_buffer_offset_mask; + size = self.get_descriptor_set_size(db, layout); unsafe { offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0)); } @@ -65,7 +65,43 @@ impl VulkanDevice { size, offsets, _sampler: Some(sampler.clone()), - has_sampler: true, })) } + + pub(super) fn create_out_descriptor_set_layout( + self: &Rc, + db: &descriptor_buffer::Device, + ) -> Result, VulkanError> { + let binding = DescriptorSetLayoutBinding::default() + .stage_flags(ShaderStageFlags::FRAGMENT) + .descriptor_count(1) + .descriptor_type(DescriptorType::SAMPLED_IMAGE); + let create_info = DescriptorSetLayoutCreateInfo::default() + .bindings(slice::from_ref(&binding)) + .flags(DescriptorSetLayoutCreateFlags::DESCRIPTOR_BUFFER_EXT); + let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; + let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; + let size = self.get_descriptor_set_size(db, layout); + let mut offsets = ArrayVec::new(); + unsafe { + offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0)); + } + Ok(Rc::new(VulkanDescriptorSetLayout { + device: self.clone(), + layout, + size, + offsets, + _sampler: None, + })) + } + + fn get_descriptor_set_size( + &self, + db: &descriptor_buffer::Device, + layout: DescriptorSetLayout, + ) -> DeviceSize { + let mut size = unsafe { db.get_descriptor_set_layout_size(layout) }; + size = (size + self.descriptor_buffer_offset_mask) & !self.descriptor_buffer_offset_mask; + size + } } diff --git a/src/gfx_apis/vulkan/descriptor_buffer.rs b/src/gfx_apis/vulkan/descriptor_buffer.rs index 4bdb18a9..5f72d7a0 100644 --- a/src/gfx_apis/vulkan/descriptor_buffer.rs +++ b/src/gfx_apis/vulkan/descriptor_buffer.rs @@ -36,8 +36,8 @@ pub struct VulkanDescriptorBufferUnused { pub address: DeviceAddress, } +#[derive(Default)] pub struct VulkanDescriptorBufferWriter { - set_size: usize, buffer: Vec, } @@ -49,13 +49,13 @@ impl VulkanDescriptorBufferCache { pub fn new( device: &Rc, allocator: &Rc, - layout: &VulkanDescriptorSetLayout, + has_sampler: bool, ) -> Self { Self { device: device.clone(), allocator: allocator.clone(), buffers: Default::default(), - has_sampler: layout.has_sampler, + has_sampler, } } @@ -164,13 +164,6 @@ impl Drop for VulkanDescriptorBufferUnused { } impl VulkanDescriptorBufferWriter { - pub fn new(layout: &VulkanDescriptorSetLayout) -> Self { - Self { - set_size: layout.size as usize, - buffer: Default::default(), - } - } - pub fn clear(&mut self) { self.buffer.clear(); } @@ -179,10 +172,13 @@ impl VulkanDescriptorBufferWriter { self.buffer.len() as DeviceSize } - pub fn add_set(&mut self) -> VulkanDescriptorBufferSetWriter<'_> { + pub fn add_set( + &mut self, + layout: &VulkanDescriptorSetLayout, + ) -> VulkanDescriptorBufferSetWriter<'_> { let buffer = &mut self.buffer; let lo = buffer.len(); - buffer.resize(lo + self.set_size, 0); + buffer.resize(lo + layout.size as usize, 0); VulkanDescriptorBufferSetWriter { set: &mut buffer[lo..], } diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index 0bffca3c..248995f9 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -3,7 +3,7 @@ use { format::XRGB8888, gfx_apis::vulkan::{ VulkanError, - format::VulkanFormat, + format::{VulkanBlendBufferLimits, VulkanFormat}, instance::{ API_VERSION, ApiVersionDisplay, Extensions, VulkanInstance, map_extension_properties, @@ -63,6 +63,7 @@ pub struct VulkanDevice { pub(super) image_drm_format_modifier: image_drm_format_modifier::Device, pub(super) descriptor_buffer: Option, pub(super) formats: AHashMap, + pub(super) blend_limits: VulkanBlendBufferLimits, pub(super) memory_types: ArrayVec, pub(super) graphics_queue: Queue, pub(super) graphics_queue_idx: u32, @@ -71,6 +72,7 @@ pub struct VulkanDevice { pub(super) transfer_granularity_mask: (u32, u32), pub(super) descriptor_buffer_offset_mask: DeviceSize, pub(super) combined_image_sampler_descriptor_size: usize, + pub(super) sampled_image_descriptor_size: usize, } impl Drop for VulkanDevice { @@ -272,6 +274,9 @@ impl VulkanInstance { } } let supports_descriptor_buffer = extensions.contains_key(descriptor_buffer::NAME); + if !supports_descriptor_buffer { + log::warn!("Vulkan device does not support descriptor buffers"); + } let (graphics_queue_family_idx, transfer_queue_family) = self.find_queues(phy_dev)?; let mut distinct_transfer_queue_family_idx = None; let mut transfer_granularity_mask = (0, 0); @@ -334,6 +339,7 @@ impl VulkanInstance { Err(e) => return Err(VulkanError::CreateDevice(e)), }; let destroy_device = OnDrop(|| unsafe { device.destroy_device(None) }); + let blend_limits = self.load_blend_format_limits(phy_dev)?; let formats = self.load_formats(phy_dev)?; let supports_xrgb8888 = formats .get(&XRGB8888.drm) @@ -361,6 +367,7 @@ impl VulkanInstance { .then(|| descriptor_buffer::Device::new(&self.instance, &device)); let mut descriptor_buffer_offset_mask = 0; let mut combined_image_sampler_descriptor_size = 0; + let mut sampled_image_descriptor_size = 0; if supports_descriptor_buffer { let mut descriptor_buffer_props = PhysicalDeviceDescriptorBufferPropertiesEXT::default(); @@ -377,6 +384,7 @@ impl VulkanInstance { - 1; combined_image_sampler_descriptor_size = descriptor_buffer_props.combined_image_sampler_descriptor_size; + sampled_image_descriptor_size = descriptor_buffer_props.sampled_image_descriptor_size; } let memory_properties = unsafe { self.instance.get_physical_device_memory_properties(phy_dev) }; @@ -415,6 +423,8 @@ impl VulkanInstance { transfer_granularity_mask, descriptor_buffer_offset_mask, combined_image_sampler_descriptor_size, + sampled_image_descriptor_size, + blend_limits, })) } } diff --git a/src/gfx_apis/vulkan/format.rs b/src/gfx_apis/vulkan/format.rs index 73fca716..d8395254 100644 --- a/src/gfx_apis/vulkan/format.rs +++ b/src/gfx_apis/vulkan/format.rs @@ -1,6 +1,6 @@ use { crate::{ - format::{FORMATS, Format}, + format::{ABGR16161616F, FORMATS, Format}, gfx_apis::vulkan::{VulkanError, instance::VulkanInstance}, video::{LINEAR_MODIFIER, Modifier}, }, @@ -38,18 +38,24 @@ pub struct VulkanModifier { pub render_needs_bridge: bool, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub struct VulkanModifierLimits { pub max_width: u32, pub max_height: u32, pub exportable: bool, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct VulkanInternalFormat { pub limits: VulkanModifierLimits, } +#[derive(Copy, Clone, Debug)] +pub struct VulkanBlendBufferLimits { + pub max_width: u32, + pub max_height: u32, +} + const FRAMEBUFFER_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(), @@ -79,6 +85,16 @@ const TRANSFER_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( const SHM_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw(TRANSFER_USAGE.as_raw() | TEX_USAGE.as_raw()); +pub const BLEND_FORMAT: &Format = ABGR16161616F; +const BLEND_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() + | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw() + | FormatFeatureFlags::SAMPLED_IMAGE.as_raw(), +); +pub const BLEND_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( + ImageUsageFlags::COLOR_ATTACHMENT.as_raw() | ImageUsageFlags::SAMPLED.as_raw(), +); + impl VulkanInstance { pub(super) fn load_formats( &self, @@ -126,6 +142,29 @@ impl VulkanInstance { Ok(()) } + pub fn load_blend_format_limits( + &self, + phy_dev: PhysicalDevice, + ) -> Result { + let format_properties = unsafe { + self.instance + .get_physical_device_format_properties(phy_dev, BLEND_FORMAT.vk_format) + }; + let l = self + .load_internal_format( + phy_dev, + BLEND_FORMAT, + &format_properties, + BLEND_FEATURES, + BLEND_USAGE, + )? + .unwrap_or_default(); + Ok(VulkanBlendBufferLimits { + max_width: l.limits.max_width, + max_height: l.limits.max_height, + }) + } + fn load_shm_format( &self, phy_dev: PhysicalDevice, diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index c0746361..ec82f78a 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -3,9 +3,9 @@ use { format::Format, gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, - AsyncShmGfxTextureTransferCancellable, GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, - GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, PendingShmTransfer, ReleaseSync, - ShmGfxTexture, ShmMemory, SyncFile, + AsyncShmGfxTextureTransferCancellable, GfxApiOpt, GfxBlendBuffer, GfxError, + GfxFramebuffer, GfxImage, GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, + PendingShmTransfer, ReleaseSync, ShmGfxTexture, ShmMemory, SyncFile, }, gfx_apis::vulkan::{ VulkanError, allocator::VulkanAllocation, device::VulkanDevice, @@ -64,6 +64,7 @@ pub struct VulkanImage { pub(super) ty: VulkanImageMemory, pub(super) bridge: Option, pub(super) shader_read_only_optimal_descriptor: Box<[u8]>, + pub(super) sampled_image_descriptor: Box<[u8]>, pub(super) descriptor_buffer_version: Cell, pub(super) descriptor_buffer_offset: Cell, pub(super) execution_version: Cell, @@ -102,6 +103,7 @@ pub enum QueueTransfer { pub enum VulkanImageMemory { DmaBuf(VulkanDmaBufImage), Internal(VulkanShmImage), + Blend(#[expect(dead_code)] VulkanAllocation), } pub struct VulkanDmaBufImage { @@ -451,6 +453,7 @@ impl VulkanDmaBufImageTemplate { shader_read_only_optimal_descriptor: self .renderer .sampler_read_only_descriptor(texture_view), + sampled_image_descriptor: Box::new([]), descriptor_buffer_version: Cell::new(0), descriptor_buffer_offset: Cell::new(0), execution_version: Cell::new(0), @@ -539,9 +542,30 @@ impl GfxFramebuffer for VulkanImage { ops: &[GfxApiOpt], clear: Option<&Color>, region: &Region, + blend_buffer: Option<&Rc>, ) -> Result, GfxError> { + let mut blend_buffer = + blend_buffer.map(|b| b.clone().into_vk(&self.renderer.device.device)); + if let Some(bb) = &blend_buffer { + if bb.size() != self.size() { + log::error!( + "Blend buffer has invalid size: {:?} != {:?}", + bb.size(), + self.size() + ); + blend_buffer = None; + } + } self.renderer - .execute(&self, acquire_sync, release_sync, ops, clear, region) + .execute( + &self, + acquire_sync, + release_sync, + ops, + clear, + region, + blend_buffer, + ) .map_err(|e| e.into()) } @@ -609,6 +633,7 @@ impl GfxTexture for VulkanImage { match &self.ty { VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf), VulkanImageMemory::Internal(_) => None, + VulkanImageMemory::Blend(_) => None, } } diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs index 44e42dbd..f4a4e4f9 100644 --- a/src/gfx_apis/vulkan/pipeline.rs +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -39,6 +39,7 @@ pub(super) struct PipelineCreateInfo { pub(super) blend: bool, pub(super) src_has_alpha: bool, pub(super) has_alpha_mult: bool, + pub(super) with_linear_output: bool, pub(super) frag_descriptor_set_layout: Option>, } @@ -76,8 +77,8 @@ impl VulkanDevice { }; let destroy_layout = OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) }); - let mut frag_spec_data = ArrayVec::<_, 8>::new(); - let mut frag_spec_entries = ArrayVec::<_, 2>::new(); + let mut frag_spec_data = ArrayVec::<_, { 3 * 4 }>::new(); + let mut frag_spec_entries = ArrayVec::<_, 3>::new(); let mut frag_spec_entry = |data: &[u8]| { let entry = SpecializationMapEntry::default() .constant_id(frag_spec_entries.len() as _) @@ -88,6 +89,7 @@ impl VulkanDevice { }; frag_spec_entry(&(info.src_has_alpha as u32).to_ne_bytes()); frag_spec_entry(&(info.has_alpha_mult as u32).to_ne_bytes()); + frag_spec_entry(&(info.with_linear_output as u32).to_ne_bytes()); let frag_spec = SpecializationInfo::default() .map_entries(&frag_spec_entries) .data(&frag_spec_data); diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 15e9a8bd..3a8b5725 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -4,8 +4,8 @@ use { cpu_worker::PendingJob, format::XRGB8888, gfx_api::{ - AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxTexture, - GfxWriteModifier, ReleaseSync, SyncFile, + AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat, + GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile, }, gfx_apis::vulkan::{ VulkanError, @@ -22,12 +22,12 @@ use { sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ - FILL_FRAG, FILL_VERT, FillPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, - VulkanShader, + FILL_FRAG, FILL_VERT, FillPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants, + TEX_FRAG, TEX_VERT, TexPushConstants, VulkanShader, }, }, io_uring::IoUring, - rect::Region, + rect::{Rect, Region}, theme::Color, utils::{ copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, once::Once, @@ -36,6 +36,7 @@ use { video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file}, }, ahash::AHashMap, + arrayvec::ArrayVec, ash::{ Device, vk::{ @@ -51,12 +52,15 @@ use { }, }, isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt}, + jay_algorithms::rect::Tag, linearize::{Linearize, StaticMap, static_map}, std::{ + borrow::Cow, cell::{Cell, RefCell}, + collections::hash_map::Entry, fmt::{Debug, Formatter}, mem, ptr, - rc::Rc, + rc::{Rc, Weak}, slice, }, uapi::OwnedFd, @@ -65,7 +69,8 @@ use { pub struct VulkanRenderer { pub(super) formats: Rc>, pub(super) device: Rc, - pub(super) pipelines: CopyHashMap>, + pub(super) pipelines: StaticMap>>, + pub(super) out_pipelines: RefCell>>, pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) transfer_command_buffers: Option, pub(super) wait_semaphores: Stack>, @@ -81,13 +86,17 @@ pub struct VulkanRenderer { pub(super) fill_frag_shader: Rc, pub(super) tex_vert_shader: Rc, pub(super) tex_frag_shader: Rc, + pub(super) out_vert_shader: Rc, + pub(super) out_frag_shader: Rc, pub(super) tex_descriptor_set_layout: Rc, + pub(super) out_descriptor_set_layout: Option>, pub(super) defunct: Cell, pub(super) pending_cpu_jobs: CopyHashMap, pub(super) shm_allocator: Rc, pub(super) sampler: Rc, - pub(super) tex_sampler_descriptor_buffer_cache: Rc, - pub(super) tex_descriptor_buffer_writer: RefCell, + pub(super) sampler_descriptor_buffer_cache: Rc, + pub(super) resource_descriptor_buffer_cache: Rc, + pub(super) blend_buffers: RefCell>>, } pub(super) struct CachedCommandBuffers { @@ -139,14 +148,23 @@ pub(super) struct Memory { wait_semaphore_infos: Vec>, release_fence: Option>, release_sync_file: Option, - descriptor_buffer: Option, - paint_regions: Vec, - clear_rects: Vec, + descriptor_buffers: ArrayVec, + paint_regions: StaticMap>, + clear_rects: StaticMap>, image_copy_regions: Vec>, + sampler_descriptor_buffer_writer: VulkanDescriptorBufferWriter, + resource_descriptor_buffer_writer: VulkanDescriptorBufferWriter, + regions_1: Vec, + regions_2: Vec>, +} + +#[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)] +pub(super) enum RenderPass { + BlendBuffer, + FrameBuffer, } struct PaintRegion { - rect: Rect2D, x1: f32, y1: f32, x2: f32, @@ -158,15 +176,16 @@ pub(super) struct PendingFrame { renderer: Rc, cmd: Cell>>, _fb: Rc, + _bb: Option>, _textures: Vec, wait_semaphores: Cell>>, waiter: Cell>>, _release_fence: Option>, - _descriptor_buffer: Option, + _descriptor_buffers: ArrayVec, } pub(super) struct VulkanFormatPipelines { - pub(super) fill: Rc, + pub(super) fill: StaticMap>, pub(super) tex: StaticMap>>, } @@ -179,7 +198,14 @@ impl VulkanDevice { let fill_vert_shader = self.create_shader(FILL_VERT)?; let fill_frag_shader = self.create_shader(FILL_FRAG)?; let sampler = self.create_sampler()?; - let tex_descriptor_set_layout = self.create_descriptor_set_layout(&sampler)?; + let tex_descriptor_set_layout = self.create_tex_descriptor_set_layout(&sampler)?; + let out_descriptor_set_layout = self + .descriptor_buffer + .as_ref() + .map(|db| self.create_out_descriptor_set_layout(db)) + .transpose()?; + let out_vert_shader = self.create_shader(OUT_VERT)?; + let out_frag_shader = self.create_shader(OUT_FRAG)?; let tex_vert_shader = self.create_shader(TEX_VERT)?; let tex_frag_shader = self.create_shader(TEX_FRAG)?; let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; @@ -220,18 +246,15 @@ impl VulkanDevice { .collect(); let allocator = self.create_allocator()?; let shm_allocator = self.create_threaded_allocator()?; - let tex_descriptor_buffer_cache = Rc::new(VulkanDescriptorBufferCache::new( - self, - &allocator, - &tex_descriptor_set_layout, - )); - let tex_descriptor_buffer_writer = RefCell::new(VulkanDescriptorBufferWriter::new( - &tex_descriptor_set_layout, - )); + let sampler_descriptor_buffer_cache = + Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, true)); + let resource_descriptor_buffer_cache = + Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, false)); let render = Rc::new(VulkanRenderer { formats: Rc::new(formats), device: self.clone(), pipelines: Default::default(), + out_pipelines: Default::default(), gfx_command_buffers, transfer_command_buffers, wait_semaphores: Default::default(), @@ -247,15 +270,19 @@ impl VulkanDevice { fill_frag_shader, tex_vert_shader, tex_frag_shader, + out_vert_shader, + out_frag_shader, tex_descriptor_set_layout, + out_descriptor_set_layout, defunct: Cell::new(false), pending_cpu_jobs: Default::default(), shm_allocator, sampler, - tex_sampler_descriptor_buffer_cache: tex_descriptor_buffer_cache, - tex_descriptor_buffer_writer, + sampler_descriptor_buffer_cache, + resource_descriptor_buffer_cache, + blend_buffers: Default::default(), }); - render.get_or_create_pipelines(XRGB8888.vk_format)?; + render.get_or_create_pipelines(XRGB8888.vk_format, RenderPass::FrameBuffer)?; Ok(render) } } @@ -264,21 +291,28 @@ impl VulkanRenderer { fn get_or_create_pipelines( &self, format: vk::Format, + pass: RenderPass, ) -> Result, VulkanError> { - if let Some(pl) = self.pipelines.get(&format) { + let with_linear_output = pass == RenderPass::BlendBuffer; + let pipelines = &self.pipelines[pass]; + if let Some(pl) = pipelines.get(&format) { return Ok(pl); } - let fill = self - .device - .create_pipeline::(PipelineCreateInfo { - format, - vert: self.fill_vert_shader.clone(), - frag: self.fill_frag_shader.clone(), - blend: true, - src_has_alpha: true, - has_alpha_mult: false, - frag_descriptor_set_layout: None, - })?; + let create_fill_pipeline = |src_has_alpha| { + self.device + .create_pipeline::(PipelineCreateInfo { + format, + vert: self.fill_vert_shader.clone(), + frag: self.fill_frag_shader.clone(), + blend: src_has_alpha, + src_has_alpha, + has_alpha_mult: false, + with_linear_output, + frag_descriptor_set_layout: None, + }) + }; + let fill_opaque = create_fill_pipeline(false)?; + let fill_alpha = create_fill_pipeline(true)?; let create_tex_pipeline = |src_has_alpha, has_alpha_mult| { self.device .create_pipeline::(PipelineCreateInfo { @@ -288,6 +322,7 @@ impl VulkanRenderer { blend: src_has_alpha || has_alpha_mult, src_has_alpha, has_alpha_mult, + with_linear_output, frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()), }) }; @@ -295,8 +330,11 @@ impl VulkanRenderer { let tex_alpha = create_tex_pipeline(true, false)?; let tex_mult_opaque = create_tex_pipeline(false, true)?; let tex_mult_alpha = create_tex_pipeline(true, true)?; - let pipelines = Rc::new(VulkanFormatPipelines { - fill, + let format_pipelines = Rc::new(VulkanFormatPipelines { + fill: static_map! { + TexSourceType::HasAlpha => fill_alpha.clone(), + TexSourceType::Opaque => fill_opaque.clone(), + }, tex: static_map! { TexCopyType::Identity => static_map! { TexSourceType::HasAlpha => tex_alpha.clone(), @@ -308,27 +346,38 @@ impl VulkanRenderer { }, }, }); - self.pipelines.set(format, pipelines.clone()); - Ok(pipelines) + pipelines.set(format, format_pipelines.clone()); + Ok(format_pipelines) } pub(super) fn allocate_point(&self) -> u64 { self.last_point.fetch_add(1) + 1 } - fn create_descriptor_buffer( + fn create_descriptor_buffers( &self, buf: CommandBuffer, opts: &[GfxApiOpt], + bb: Option<&VulkanImage>, ) -> Result<(), VulkanError> { let Some(db) = &self.device.descriptor_buffer else { return Ok(()); }; - zone!("create_descriptor_buffer"); + zone!("create_descriptor_buffers"); let version = self.allocate_point(); let memory = &mut *self.memory.borrow_mut(); - let writer = &mut *self.tex_descriptor_buffer_writer.borrow_mut(); - writer.clear(); + memory.descriptor_buffers.clear(); + let sampler_writer = &mut memory.sampler_descriptor_buffer_writer; + sampler_writer.clear(); + let resource_writer = &mut memory.resource_descriptor_buffer_writer; + resource_writer.clear(); + if let Some(bb) = bb { + let layout = self.out_descriptor_set_layout.as_ref().unwrap(); + let offset = resource_writer.next_offset(); + bb.descriptor_buffer_offset.set(offset); + let mut writer = resource_writer.add_set(layout); + writer.write(layout.offsets[0], &bb.sampled_image_descriptor); + } for cmd in opts { let GfxApiOpt::CopyTexture(c) = cmd else { continue; @@ -337,27 +386,32 @@ impl VulkanRenderer { if tex.descriptor_buffer_version.replace(version) == version { continue; } - let offset = writer.next_offset(); + let offset = sampler_writer.next_offset(); tex.descriptor_buffer_offset.set(offset); - let mut writer = writer.add_set(); + let mut writer = sampler_writer.add_set(&self.tex_descriptor_set_layout); writer.write( self.tex_descriptor_set_layout.offsets[0], &tex.shader_read_only_optimal_descriptor, ); } - let buffer = self - .tex_sampler_descriptor_buffer_cache - .allocate(writer.len() as DeviceSize)?; - buffer.buffer.allocation.upload(|ptr, _| unsafe { - ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len()) - })?; - let info = DescriptorBufferBindingInfoEXT::default() - .usage(self.tex_sampler_descriptor_buffer_cache.usage()) - .address(buffer.buffer.address); - unsafe { - db.cmd_bind_descriptor_buffers(buf, slice::from_ref(&info)); + let mut infos = ArrayVec::<_, 2>::new(); + for (writer, cache) in [ + (&sampler_writer, &self.sampler_descriptor_buffer_cache), + (&resource_writer, &self.resource_descriptor_buffer_cache), + ] { + let buffer = cache.allocate(writer.len() as DeviceSize)?; + buffer.buffer.allocation.upload(|ptr, _| unsafe { + ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len()) + })?; + let info = DescriptorBufferBindingInfoEXT::default() + .usage(cache.usage()) + .address(buffer.buffer.address); + infos.push(info); + memory.descriptor_buffers.push(buffer); + } + unsafe { + db.cmd_bind_descriptor_buffers(buf, &infos); } - memory.descriptor_buffer = Some(buffer); Ok(()) } @@ -500,42 +554,61 @@ impl VulkanRenderer { Ok(()) } - fn begin_rendering(&self, buf: CommandBuffer, fb: &VulkanImage, clear: Option<&Color>) { + fn begin_rendering( + &self, + buf: CommandBuffer, + target: &VulkanImage, + clear: Option<&Color>, + pass: RenderPass, + ) { zone!("begin_rendering"); let memory = &mut *self.memory.borrow_mut(); - let clear_value = clear.map(|clear| ClearValue { - color: ClearColorValue { - float32: clear.to_array_srgb(), - }, - }); - let load_clear = memory.paint_regions.len() == 1 && { - let rect = &memory.paint_regions[0].rect; - rect.offset.x == 0 - && rect.offset.y == 0 - && rect.extent.width == fb.width - && rect.extent.height == fb.height - }; - let (load_clear, manual_clear) = match load_clear { - false => (None, clear_value), - true => (clear_value, None), - }; - let rendering_attachment_info = { - let mut rai = RenderingAttachmentInfo::default() - .image_view(fb.render_view.unwrap_or(fb.texture_view)) - .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) - .load_op(AttachmentLoadOp::LOAD) - .store_op(AttachmentStoreOp::STORE); - if let Some(clear) = load_clear { - rai = rai.clear_value(clear).load_op(AttachmentLoadOp::CLEAR); + let mut load_clear = None; + let mut manual_clear = None; + let clear_rects = &memory.clear_rects[pass]; + if let Some(clear) = clear { + if clear_rects.is_not_empty() { + let clear_value = ClearValue { + color: ClearColorValue { + float32: match pass { + RenderPass::BlendBuffer => clear.to_array_linear(None), + RenderPass::FrameBuffer => clear.to_array_srgb(None), + }, + }, + }; + let use_load_clear = clear_rects.len() == 1 && { + let rect = &clear_rects[0].rect; + rect.offset.x == 0 + && rect.offset.y == 0 + && rect.extent.width == target.width + && rect.extent.height == target.height + }; + if use_load_clear { + load_clear = Some(clear_value); + } else { + manual_clear = Some(clear_value); + } } - rai + } + let mut rendering_attachment_info = RenderingAttachmentInfo::default() + .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .image_view(target.render_view.unwrap_or(target.texture_view)) + .store_op(AttachmentStoreOp::STORE); + let load_op = if let Some(clear) = load_clear { + rendering_attachment_info = rendering_attachment_info.clear_value(clear); + AttachmentLoadOp::CLEAR + } else if pass == RenderPass::BlendBuffer { + AttachmentLoadOp::DONT_CARE + } else { + AttachmentLoadOp::LOAD }; + rendering_attachment_info = rendering_attachment_info.load_op(load_op); let rendering_info = RenderingInfo::default() .render_area(Rect2D { offset: Default::default(), extent: Extent2D { - width: fb.width, - height: fb.height, + width: target.width, + height: target.height, }, }) .layer_count(1) @@ -543,26 +616,16 @@ impl VulkanRenderer { unsafe { self.device.device.cmd_begin_rendering(buf, &rendering_info); } - if memory.paint_regions.is_not_empty() { + if clear_rects.is_not_empty() { if let Some(clear) = manual_clear { let clear_attachment = ClearAttachment::default() .color_attachment(0) .clear_value(clear) .aspect_mask(ImageAspectFlags::COLOR); - memory.clear_rects.clear(); - for region in &memory.paint_regions { - memory.clear_rects.push(ClearRect { - rect: region.rect, - base_array_layer: 0, - layer_count: 1, - }); - } unsafe { - self.device.device.cmd_clear_attachments( - buf, - &[clear_attachment], - &memory.clear_rects, - ); + self.device + .device + .cmd_clear_attachments(buf, &[clear_attachment], clear_rects); } } } @@ -598,12 +661,14 @@ impl VulkanRenderer { fn record_draws( &self, buf: CommandBuffer, - fb: &VulkanImage, + target: &VulkanImage, opts: &[GfxApiOpt], + pass: RenderPass, ) -> Result<(), VulkanError> { zone!("record_draws"); let memory = &*self.memory.borrow(); - let pipelines = self.get_or_create_pipelines(fb.format.vk_format)?; + let paint_regions = &memory.paint_regions[pass]; + let pipelines = self.get_or_create_pipelines(target.format.vk_format, pass)?; let dev = &self.device.device; let mut current_pipeline = None; let mut bind = |pipeline: &VulkanPipeline| { @@ -620,19 +685,27 @@ impl VulkanRenderer { GfxApiOpt::FillRect(r) => { let push = FillPushConstants { pos: r.rect.to_points(), - color: r.color.to_array_srgb(), + color: match pass { + RenderPass::BlendBuffer => r.color.to_array_linear(r.alpha), + RenderPass::FrameBuffer => r.color.to_array_srgb(r.alpha), + }, }; - for region in &memory.paint_regions { + let source_type = match push.color[3] < 1.0 { + true => TexSourceType::HasAlpha, + false => TexSourceType::Opaque, + }; + let pipeline = &pipelines.fill[source_type]; + for region in paint_regions { let mut push = push; let draw = region.constrain(&mut push.pos, None); if !draw { continue; } - bind(&pipelines.fill); + bind(pipeline); unsafe { dev.cmd_push_constants( buf, - pipelines.fill.pipeline_layout, + pipeline.pipeline_layout, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, 0, uapi::as_bytes(&push), @@ -656,7 +729,7 @@ impl VulkanRenderer { true => TexCopyType::Multiply, false => TexCopyType::Identity, }; - let source_type = match tex.format.has_alpha { + let source_type = match tex.format.has_alpha && !c.opaque { true => TexSourceType::HasAlpha, false => TexSourceType::Opaque, }; @@ -670,7 +743,7 @@ impl VulkanRenderer { .image_view(tex.texture_view) .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); let init = Once::default(); - for region in &memory.paint_regions { + for region in paint_regions { let mut push = push; let draw = region.constrain(&mut push.pos, Some(&mut push.tex_pos)); if !draw { @@ -717,6 +790,112 @@ impl VulkanRenderer { Ok(()) } + fn blend_buffer_initial_barrier(&self, buf: CommandBuffer, bb: &VulkanImage) { + zone!("blend_buffer_initial_barrier"); + let memory = &mut *self.memory.borrow_mut(); + memory.image_barriers.clear(); + let barrier = image_barrier() + .image(bb.image) + .old_layout(if bb.is_undefined.get() { + ImageLayout::UNDEFINED + } else { + ImageLayout::SHADER_READ_ONLY_OPTIMAL + }) + .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .src_access_mask(AccessFlags2::SHADER_READ) + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE); + memory.image_barriers.push(barrier); + let dep_info = DependencyInfoKHR::default().image_memory_barriers(&memory.image_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn blend_buffer_copy( + &self, + buf: CommandBuffer, + fb: &VulkanImage, + bb: &VulkanImage, + ) -> Result<(), VulkanError> { + zone!("blend_buffer_copy"); + let memory = &*self.memory.borrow(); + let db = self.device.descriptor_buffer.as_ref().unwrap(); + let pipeline = match self.out_pipelines.borrow_mut().entry(fb.format.vk_format) { + Entry::Occupied(pipeline) => pipeline.get().clone(), + Entry::Vacant(e) => { + let layout = self.out_descriptor_set_layout.as_ref().unwrap(); + let out = self + .device + .create_pipeline::(PipelineCreateInfo { + format: fb.format.vk_format, + vert: self.out_vert_shader.clone(), + frag: self.out_frag_shader.clone(), + blend: false, + src_has_alpha: true, + has_alpha_mult: false, + with_linear_output: true, + frag_descriptor_set_layout: Some(layout.clone()), + })?; + e.insert(out.clone()); + out + } + }; + let dev = &self.device.device; + unsafe { + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + db.cmd_set_descriptor_buffer_offsets( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &[1], + &[bb.descriptor_buffer_offset.get()], + ); + } + for region in &memory.paint_regions[RenderPass::BlendBuffer] { + let push = OutPushConstants { + pos: [ + [region.x2, region.y1], + [region.x1, region.y1], + [region.x2, region.y2], + [region.x1, region.y2], + ], + }; + unsafe { + 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); + } + } + Ok(()) + } + + fn blend_buffer_final_barrier(&self, buf: CommandBuffer, bb: &VulkanImage) { + zone!("blend_buffer_final_barrier"); + let image_barrier = image_barrier() + .image(bb.image) + .old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER); + let dependency_info = + DependencyInfoKHR::default().image_memory_barriers(slice::from_ref(&image_barrier)); + unsafe { + self.device + .device + .cmd_pipeline_barrier2(buf, &dependency_info); + } + } + fn end_rendering(&self, buf: CommandBuffer) { zone!("end_rendering"); unsafe { @@ -724,7 +903,7 @@ impl VulkanRenderer { } } - fn copy_bridge_to_dmabuf(&self, buf: CommandBuffer, fb: &VulkanImage) { + fn copy_bridge_to_dmabuf(&self, buf: CommandBuffer, fb: &VulkanImage, region: &Region) { zone!("copy_bridge_to_dmabuf"); let Some(bridge) = &fb.bridge else { return; @@ -766,15 +945,14 @@ impl VulkanRenderer { .base_array_layer(0) .mip_level(0); memory.image_copy_regions.clear(); - for region in &memory.paint_regions { - let offset = Offset3D { - x: region.rect.offset.x, - y: region.rect.offset.y, - z: 0, + for rect in region.rects() { + let Some([x1, y1, x2, y2]) = constrain_to_fb(fb, rect) else { + continue; }; + let offset = Offset3D { x: x1, y: y1, z: 0 }; let extent = Extent3D { - width: region.rect.extent.width, - height: region.rect.extent.height, + width: (x2 - x1) as _, + height: (y2 - y1) as _, depth: 1, }; memory.image_copy_regions.push( @@ -786,14 +964,16 @@ impl VulkanRenderer { .extent(extent), ); } - let copy_image_info = CopyImageInfo2::default() - .src_image(fb.image) - .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) - .dst_image(bridge.dmabuf_image) - .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) - .regions(&memory.image_copy_regions); - unsafe { - self.device.device.cmd_copy_image2(buf, ©_image_info); + if memory.image_copy_regions.is_not_empty() { + let copy_image_info = CopyImageInfo2::default() + .src_image(fb.image) + .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .dst_image(bridge.dmabuf_image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .regions(&memory.image_copy_regions); + unsafe { + self.device.device.cmd_copy_image2(buf, ©_image_info); + } } } @@ -999,7 +1179,10 @@ impl VulkanRenderer { Ok(()) } - fn store_layouts(&self, fb: &VulkanImage) { + fn store_layouts(&self, fb: &VulkanImage, bb: Option<&VulkanImage>) { + if let Some(bb) = bb { + bb.is_undefined.set(false); + } fb.is_undefined.set(false); fb.contents_are_undefined.set(false); fb.queue_state.set(QueueState::Acquired { @@ -1013,7 +1196,12 @@ impl VulkanRenderer { } } - fn create_pending_frame(self: &Rc, buf: Rc, fb: &Rc) { + fn create_pending_frame( + self: &Rc, + buf: Rc, + fb: &Rc, + bb: Option>, + ) { zone!("create_pending_frame"); let point = self.allocate_point(); let mut memory = self.memory.borrow_mut(); @@ -1022,11 +1210,12 @@ impl VulkanRenderer { renderer: self.clone(), cmd: Cell::new(Some(buf)), _fb: fb.clone(), + _bb: bb, _textures: mem::take(&mut memory.textures), wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)), waiter: Cell::new(None), _release_fence: memory.release_fence.take(), - _descriptor_buffer: memory.descriptor_buffer.take(), + _descriptor_buffers: mem::take(&mut memory.descriptor_buffers), }); self.pending_frames.set(frame.point, frame.clone()); let future = self.eng.spawn( @@ -1049,9 +1238,18 @@ impl VulkanRenderer { opts: &[GfxApiOpt], clear: Option<&Color>, region: &Region, + blend_buffer: Option>, ) -> Result, VulkanError> { zone!("execute"); - let res = self.try_execute(fb, fb_acquire_sync, fb_release_sync, opts, clear, region); + let res = self.try_execute( + fb, + fb_acquire_sync, + fb_release_sync, + opts, + clear, + region, + blend_buffer, + ); let sync_file = { let mut memory = self.memory.borrow_mut(); memory.textures.clear(); @@ -1059,7 +1257,7 @@ impl VulkanRenderer { memory.queue_transfer.clear(); memory.wait_semaphores.clear(); memory.release_fence.take(); - memory.descriptor_buffer.take(); + memory.descriptor_buffers.clear(); memory.release_sync_file.take() }; res.map(|_| sync_file) @@ -1074,37 +1272,124 @@ impl VulkanRenderer { Ok(semaphore) } - fn create_paint_regions(&self, fb: &VulkanImage, region: &Region) { + fn create_regions( + &self, + fb: &VulkanImage, + opts: &[GfxApiOpt], + clear: Option<&Color>, + region: &Region, + bb: Option<&VulkanImage>, + ) { + zone!("create_paint_regions"); let memory = &mut *self.memory.borrow_mut(); - memory.paint_regions.clear(); - for rect in region.rects() { - let x1 = rect.x1().max(0); - let y1 = rect.y1().max(0); - let x2 = rect.x2(); - let y2 = rect.y2(); - if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { - continue; + memory.regions_1.clear(); + memory.regions_2.clear(); + let width = fb.width as f32; + let height = fb.height as f32; + let mut tag = 0; + for opt in opts.iter().rev() { + let (opaque, fb_rect) = match opt { + GfxApiOpt::Sync => continue, + GfxApiOpt::FillRect(f) => (f.effective_color().a >= 1.0, f.rect), + GfxApiOpt::CopyTexture(c) => { + let opaque = 'opaque: { + if let Some(a) = c.alpha { + if a < 1.0 { + break 'opaque false; + } + } + if !c.opaque { + let tex = c.tex.as_vk(&self.device.device); + if tex.format.has_alpha { + break 'opaque false; + } + } + true + }; + (opaque, c.target) + } + }; + if opaque || bb.is_none() { + tag |= 1; + } else { + tag += tag & 1; } - let x2 = x2.min(fb.width as i32); - let y2 = y2.min(fb.height as i32); - let to_fb = |c: i32, max: u32| 2.0 * (c as f32 / max as f32) - 1.0; - memory.paint_regions.push(PaintRegion { - rect: Rect2D { - offset: Offset2D { - x: x1 as _, - y: y1 as _, - }, - extent: Extent2D { - width: (x2 - x1) as u32, - height: (y2 - y1) as u32, - }, - }, + let rect = fb_rect.to_rect(width, height); + if opaque && clear.is_some() { + memory.regions_1.push(rect); + } + memory.regions_2.push(rect.with_tag(tag)); + } + let clear_region = if clear.is_some() { + let opaque_region = Region::from_rects2(&memory.regions_1); + region.subtract_cow(&opaque_region) + } else { + Cow::Owned(Region::default()) + }; + let tagged_region = Region::from_rects_tagged(&memory.regions_2).intersect_tagged(region); + memory.regions_1.clear(); + memory.paint_regions[RenderPass::BlendBuffer].clear(); + memory.paint_regions[RenderPass::FrameBuffer].clear(); + let to_fb = |c: i32, max: u32| 2.0 * (c as f32 / max as f32) - 1.0; + for rect in tagged_region.rects() { + if rect.tag() == 0 && clear.is_some() { + memory.regions_1.push(rect.untag()); + } + let Some([x1, y1, x2, y2]) = constrain_to_fb(fb, rect) else { + continue; + }; + let region = match rect.tag() { + 0 => &mut memory.paint_regions[RenderPass::BlendBuffer], + _ => &mut memory.paint_regions[RenderPass::FrameBuffer], + }; + region.push(PaintRegion { x1: to_fb(x1, fb.width), x2: to_fb(x2, fb.width), y1: to_fb(y1, fb.height), y2: to_fb(y2, fb.height), }); } + let blend_clear = clear_region.intersect(&Region::from_rects2(&memory.regions_1)); + let opaque_clear = clear_region.subtract_cow(&blend_clear); + // if bb.is_none() { + // log::info!("blend_clear = {:?}", blend_clear); + // log::info!("opaque_clear = {:?}", opaque_clear); + // } + for (pass, clear_region) in [ + (RenderPass::BlendBuffer, &blend_clear), + (RenderPass::FrameBuffer, &opaque_clear), + ] { + memory.clear_rects[pass].clear(); + for rect in clear_region.rects() { + let Some([x1, y1, x2, y2]) = constrain_to_fb(fb, rect) else { + continue; + }; + memory.clear_rects[pass].push(ClearRect { + rect: Rect2D { + offset: Offset2D { + x: x1 as _, + y: y1 as _, + }, + extent: Extent2D { + width: (x2 - x1) as u32, + height: (y2 - y1) as u32, + }, + }, + base_array_layer: 0, + layer_count: 1, + }); + } + } + } + + fn elide_blend_buffer(&self, blend_buffer: &mut Option>) { + if blend_buffer.is_none() { + return; + } + let memory = &*self.memory.borrow(); + if memory.paint_regions[RenderPass::BlendBuffer].is_empty() { + *blend_buffer = None; + } } fn try_execute( @@ -1115,26 +1400,43 @@ impl VulkanRenderer { opts: &[GfxApiOpt], clear: Option<&Color>, region: &Region, + mut blend_buffer: Option>, ) -> Result<(), VulkanError> { self.check_defunct()?; - self.create_paint_regions(fb, region); + self.create_regions(fb, opts, clear, region, blend_buffer.as_deref()); + self.elide_blend_buffer(&mut blend_buffer); + let bb = blend_buffer.as_deref(); let buf = self.gfx_command_buffers.allocate()?; self.collect_memory(opts); self.begin_command_buffer(buf.buffer)?; - self.create_descriptor_buffer(buf.buffer, opts)?; + self.create_descriptor_buffers(buf.buffer, opts, bb)?; self.initial_barriers(buf.buffer, fb)?; - self.begin_rendering(buf.buffer, fb, clear); self.set_viewport(buf.buffer, fb); - self.record_draws(buf.buffer, fb, opts)?; - self.end_rendering(buf.buffer); - self.copy_bridge_to_dmabuf(buf.buffer, fb); + if let Some(bb) = bb { + zone!("blend buffer pass"); + self.blend_buffer_initial_barrier(buf.buffer, bb); + self.begin_rendering(buf.buffer, bb, clear, RenderPass::BlendBuffer); + self.record_draws(buf.buffer, bb, opts, RenderPass::BlendBuffer)?; + self.end_rendering(buf.buffer); + self.blend_buffer_final_barrier(buf.buffer, bb); + } + { + zone!("frame buffer pass"); + self.begin_rendering(buf.buffer, fb, clear, RenderPass::FrameBuffer); + self.record_draws(buf.buffer, fb, opts, RenderPass::FrameBuffer)?; + if let Some(bb) = bb { + self.blend_buffer_copy(buf.buffer, fb, bb)?; + } + self.end_rendering(buf.buffer); + } + self.copy_bridge_to_dmabuf(buf.buffer, fb, region); self.final_barriers(buf.buffer, fb); self.end_command_buffer(buf.buffer)?; self.create_wait_semaphores(fb, &fb_acquire_sync)?; self.submit(buf.buffer)?; self.import_release_semaphore(fb, fb_release_sync); - self.store_layouts(fb); - self.create_pending_frame(buf, fb); + self.store_layouts(fb, bb); + self.create_pending_frame(buf, fb, blend_buffer); Ok(()) } @@ -1206,6 +1508,17 @@ impl dyn GfxTexture { } } +impl dyn GfxBlendBuffer { + pub(super) fn into_vk(self: Rc, device: &Device) -> Rc { + let img: Rc = self + .into_any() + .downcast() + .expect("Non-vulkan blend buffer passed into vulkan"); + img.assert_device(device); + img + } +} + pub(super) fn image_barrier() -> ImageMemoryBarrier2<'static> { ImageMemoryBarrier2::default().subresource_range( ImageSubresourceRange::default() @@ -1291,3 +1604,19 @@ impl PaintRegion { true } } + +fn constrain_to_fb(fb: &VulkanImage, rect: &Rect) -> Option<[i32; 4]> +where + T: Tag, +{ + let x1 = rect.x1().max(0); + let y1 = rect.y1().max(0); + let x2 = rect.x2(); + let y2 = rect.y2(); + if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { + return None; + } + let x2 = x2.min(fb.width as i32); + let y2 = y2.min(fb.height as i32); + Some([x1, y1, x2, y2]) +} diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs index a17117fb..4e019e37 100644 --- a/src/gfx_apis/vulkan/shaders.rs +++ b/src/gfx_apis/vulkan/shaders.rs @@ -9,6 +9,8 @@ pub const FILL_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.vert pub const FILL_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.frag.spv")); pub const TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.vert.spv")); pub const TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.frag.spv")); +pub const OUT_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.vert.spv")); +pub const OUT_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.frag.spv")); pub struct VulkanShader { pub(super) device: Rc, @@ -34,6 +36,14 @@ pub struct TexPushConstants { unsafe impl Packed for TexPushConstants {} +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct OutPushConstants { + pub pos: [[f32; 2]; 4], +} + +unsafe impl Packed for OutPushConstants {} + impl VulkanDevice { pub(super) fn create_shader( self: &Rc, diff --git a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl index b2be1f76..751b18bf 100644 --- a/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl +++ b/src/gfx_apis/vulkan/shaders/frag_spec_const.glsl @@ -1,2 +1,8 @@ +#ifndef FRAG_SPEC_CONST_GLSL +#define FRAG_SPEC_CONST_GLSL + layout(constant_id = 0) const bool src_has_alpha = false; layout(constant_id = 1) const bool has_alpha_multiplier = false; +layout(constant_id = 2) const bool color_management = false; + +#endif diff --git a/src/gfx_apis/vulkan/shaders/out.common.glsl b/src/gfx_apis/vulkan/shaders/out.common.glsl new file mode 100644 index 00000000..23a3579d --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/out.common.glsl @@ -0,0 +1,3 @@ +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; +} data; diff --git a/src/gfx_apis/vulkan/shaders/out.frag b/src/gfx_apis/vulkan/shaders/out.frag new file mode 100644 index 00000000..8401e5be --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/out.frag @@ -0,0 +1,17 @@ +#version 450 +#extension GL_EXT_samplerless_texture_functions : require + +#include "frag_spec_const.glsl" +#include "transfer_functions.glsl" +#include "out.common.glsl" + +layout(set = 0, binding = 0) uniform texture2D in_color; +layout(location = 0) out vec4 out_color; + +void main() { + vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0); + c.rgb /= mix(c.a, 1.0, c.a == 0.0); + c.rgb = oetf_srgb(c.rgb); + c.rgb *= c.a; + out_color = c; +} diff --git a/src/gfx_apis/vulkan/shaders/out.vert b/src/gfx_apis/vulkan/shaders/out.vert new file mode 100644 index 00000000..4bad8d2f --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/out.vert @@ -0,0 +1,16 @@ +#version 450 +//#extension GL_EXT_debug_printf : enable + +#include "out.common.glsl" + +void main() { + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; break; + case 1: pos = data.pos[1]; break; + case 2: pos = data.pos[2]; break; + case 3: pos = data.pos[3]; break; + } + gl_Position = vec4(pos, 0.0, 1.0); +// debugPrintfEXT("X gl_Position = %v4f, pos = %v2f", gl_Position, pos); +} diff --git a/src/gfx_apis/vulkan/shaders/tex.frag b/src/gfx_apis/vulkan/shaders/tex.frag index 2d5a29ad..9d9476fd 100644 --- a/src/gfx_apis/vulkan/shaders/tex.frag +++ b/src/gfx_apis/vulkan/shaders/tex.frag @@ -1,6 +1,7 @@ #version 450 #include "frag_spec_const.glsl" +#include "transfer_functions.glsl" #include "tex.common.glsl" layout(set = 0, binding = 0) uniform sampler2D tex; @@ -8,13 +9,22 @@ layout(location = 0) in vec2 tex_pos; layout(location = 0) out vec4 out_color; void main() { + vec4 c = textureLod(tex, tex_pos, 0); + if (color_management) { + if (src_has_alpha) { + c.rgb /= mix(c.a, 1.0, c.a == 0.0); + } + c.rgb = eotf_srgb(c.rgb); + if (src_has_alpha) { + c.rgb *= c.a; + } + } if (has_alpha_multiplier) { if (src_has_alpha) { - out_color = textureLod(tex, tex_pos, 0) * data.mul; + c *= data.mul; } else { - out_color = vec4(textureLod(tex, tex_pos, 0).rgb * data.mul, data.mul); + c = vec4(c.rgb * data.mul, data.mul); } - } else { - out_color = textureLod(tex, tex_pos, 0); } + out_color = c; } diff --git a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl new file mode 100644 index 00000000..d72332a0 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl @@ -0,0 +1,21 @@ +#ifndef TRANSFER_FUNCTIONS_GLSL +#define TRANSFER_FUNCTIONS_GLSL + +vec3 eotf_srgb(vec3 c) { + return mix( + c * vec3(1.0 / 12.92), + pow((c + vec3(0.055)) / vec3(1.055), vec3(2.4)), + greaterThan(c, vec3(0.04045)) + ); +} + +vec3 oetf_srgb(vec3 c) { + c = clamp(c, 0.0, 1.0); + return mix( + c * vec3(12.92), + vec3(1.055) * pow(c, vec3(1/2.4)) - vec3(0.055), + greaterThan(c, vec3(0.0031308)) + ); +} + +#endif diff --git a/src/gfx_apis/vulkan/shm_image.rs b/src/gfx_apis/vulkan/shm_image.rs index b405a9a0..987c9dcb 100644 --- a/src/gfx_apis/vulkan/shm_image.rs +++ b/src/gfx_apis/vulkan/shm_image.rs @@ -460,12 +460,14 @@ impl VulkanRenderer { ty: VulkanImageMemory::Internal(shm), bridge: None, shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view), + sampled_image_descriptor: Box::new([]), descriptor_buffer_version: Cell::new(0), descriptor_buffer_offset: Cell::new(0), execution_version: Cell::new(0), }); let shm = match &img.ty { VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Blend(_) => unreachable!(), VulkanImageMemory::Internal(s) => s, }; if data.is_not_empty() { diff --git a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs index 3eb3f7af..ce4ee503 100644 --- a/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs +++ b/src/ifs/ext_image_copy/ext_image_copy_capture_frame_v1.rs @@ -245,6 +245,7 @@ impl ExtImageCopyCaptureFrameV1 { true, false, jay_config::video::Transform::None, + None, ) }); } diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index a436ec68..bde24705 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -202,6 +202,7 @@ impl JayScreencast { false, false, Transform::None, + None, ); match res { Ok(_) => { diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index e63c953f..8f7b4cf2 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -278,7 +278,7 @@ pub struct WlSurface { role: Cell, pending: RefCell>, input_region: CloneCell>>, - opaque_region: Cell>>, + opaque_region: CloneCell>>, buffer_points: RefCell, pub buffer_points_norm: RefCell, damage_matrix: Cell, @@ -331,6 +331,7 @@ pub struct WlSurface { clear_fifo_on_vblank: Cell, commit_timer: CloneCell>>, before_latch_listener: EventListener, + is_opaque: Cell, } impl Debug for WlSurface { @@ -668,6 +669,7 @@ impl WlSurface { clear_fifo_on_vblank: Default::default(), commit_timer: Default::default(), before_latch_listener: EventListener::new(slf.clone()), + is_opaque: Cell::new(false), } } @@ -1195,6 +1197,7 @@ impl WlSurface { } } let transform_changed = viewport_changed || scale_changed || buffer_transform_changed; + let mut buffer_abs_pos_size_changed = false; if buffer_changed || transform_changed { let mut buffer_points = self.buffer_points.borrow_mut(); let mut buffer_points_norm = self.buffer_points_norm.borrow_mut(); @@ -1288,6 +1291,7 @@ impl WlSurface { .set(buffer_abs_pos.with_size(width, height).unwrap()); max_surface_size = (width.max(old_width), height.max(old_height)); damage_full = true; + buffer_abs_pos_size_changed = true; } } let has_new_frame_requests = pending.frame_request.is_not_empty(); @@ -1304,15 +1308,25 @@ impl WlSurface { mem::swap(fbs.deref_mut(), &mut pending.presentation_feedback); fbs.is_not_empty() }; + let mut opaque_region_changed = false; { if let Some(region) = pending.input_region.take() { self.input_region.set(region); self.client.state.tree_changed(); } if let Some(region) = pending.opaque_region.take() { + opaque_region_changed = true; self.opaque_region.set(region); } } + if opaque_region_changed || buffer_abs_pos_size_changed { + let pos = self.buffer_abs_pos.get().at_point(0, 0); + let is_opaque = match self.opaque_region.get() { + None => false, + Some(o) => o.contains_rect(&pos), + }; + self.is_opaque.set(is_opaque); + } let mut tearing_changed = false; if let Some(tearing) = pending.tearing.take() { if self.tearing.replace(tearing) != tearing { @@ -1636,6 +1650,14 @@ impl WlSurface { pub fn alpha(&self) -> Option { self.alpha.get() } + + pub fn opaque(&self) -> bool { + self.is_opaque.get() + } + + pub fn opaque_region(&self) -> Option> { + self.opaque_region.get() + } } object_base! { diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 1cd262af..6d5be8e7 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -5,9 +5,10 @@ use { format::{ARGB8888, Format, XRGB8888}, gfx_api::{ AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FillRect, - FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, - GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, GfxWriteModifier, - PendingShmTransfer, ReleaseSync, ResetStatus, ShmGfxTexture, ShmMemory, SyncFile, + FramebufferRect, GfxApiOpt, GfxBlendBuffer, GfxContext, GfxError, GfxFormat, + GfxFramebuffer, GfxImage, GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, + GfxWriteModifier, PendingShmTransfer, ReleaseSync, ResetStatus, ShmGfxTexture, + ShmMemory, SyncFile, }, rect::{Rect, Region}, theme::Color, @@ -37,6 +38,8 @@ enum TestGfxError { ImportDmaBuf(#[source] AllocatorError), #[error("Could not access the client memory")] AccessFailed(#[source] Box), + #[error("Text API does not support blend buffers")] + NoBlendBuffer, } impl From for GfxError { @@ -189,6 +192,14 @@ impl GfxContext for TestGfxCtx { fn sync_obj_ctx(&self) -> Option<&Rc> { None } + + fn acquire_blend_buffer( + &self, + _width: i32, + _height: i32, + ) -> Result, GfxError> { + Err(GfxError(Box::new(TestGfxError::NoBlendBuffer))) + } } enum TestGfxImage { @@ -383,6 +394,7 @@ impl GfxFramebuffer for TestGfxFb { ops: &[GfxApiOpt], clear: Option<&Color>, _region: &Region, + _blend_buffer: Option<&Rc>, ) -> Result, GfxError> { let fb_points = |width: i32, height: i32, rect: &FramebufferRect| { let points = rect.to_points(); @@ -441,11 +453,12 @@ impl GfxFramebuffer for TestGfxFb { } }; let fill_rect = |f: &FillRect, staging: &mut [Color]| { + let color = f.effective_color(); let (x1, y1, x2, y2) = fb_points(width, height, &f.rect); for y in y1..y2 { for x in x1..x2 { let dst = &mut staging[(y * width + x) as usize]; - *dst = dst.and_then(&f.color); + *dst = dst.and_then(&color); } } }; diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 265ef2c9..8d3630d9 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -225,6 +225,7 @@ impl GuiElement for Button { None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -325,6 +326,7 @@ impl GuiElement for Label { None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -634,6 +636,7 @@ impl WindowData { ReleaseSync::Implicit, self.scale.get(), Some(&Color::from_gray(0)), + None, &mut |r| { if let Some(content) = self.content.get() { content.render_at(r, 0.0, 0.0) diff --git a/src/rect.rs b/src/rect.rs index d9a22cb5..77b0974a 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -5,20 +5,26 @@ mod tests; pub use region::{DamageQueue, RegionBuilder}; use { - jay_algorithms::rect::RectRaw, + jay_algorithms::rect::{NoTag, RectRaw, Tag}, smallvec::SmallVec, std::fmt::{Debug, Formatter}, }; #[derive(Copy, Clone, Eq, PartialEq, Default)] #[repr(transparent)] -pub struct Rect { - raw: RectRaw, +pub struct Rect +where + T: Tag, +{ + raw: RectRaw, } #[derive(Clone, Eq, PartialEq, Debug, Default)] -pub struct Region { - rects: SmallVec<[RectRaw; 1]>, +pub struct Region +where + T: Tag, +{ + rects: SmallVec<[RectRaw; 1]>, extents: Rect, } @@ -50,6 +56,23 @@ impl RectOverflow { } } +impl Rect +where + T: Tag, +{ + pub fn untag(&self) -> Rect { + Rect { + raw: RectRaw { + x1: self.raw.x1, + y1: self.raw.y1, + x2: self.raw.x2, + y2: self.raw.y2, + tag: NoTag, + }, + } + } +} + impl Rect { pub fn new_empty(x: i32, y: i32) -> Self { Self { @@ -58,6 +81,7 @@ impl Rect { y1: y, x2: x, y2: y, + tag: NoTag, }, } } @@ -67,7 +91,13 @@ impl Rect { return None; } Some(Self { - raw: RectRaw { x1, y1, x2, y2 }, + raw: RectRaw { + x1, + y1, + x2, + y2, + tag: NoTag, + }, }) } @@ -76,10 +106,16 @@ impl Rect { Self::new(x1, y1, x2, y2).unwrap() } - #[expect(dead_code)] + #[cfg_attr(not(test), expect(dead_code))] fn new_unchecked_danger(x1: i32, y1: i32, x2: i32, y2: i32) -> Self { Self { - raw: RectRaw { x1, y1, x2, y2 }, + raw: RectRaw { + x1, + y1, + x2, + y2, + tag: NoTag, + }, } } @@ -102,6 +138,57 @@ impl Rect { y1: self.raw.y1.min(other.raw.y1), x2: self.raw.x2.max(other.raw.x2), y2: self.raw.y2.max(other.raw.y2), + tag: NoTag, + }, + } + } + + pub fn intersect(&self, other: Self) -> Self { + let x1 = self.raw.x1.max(other.raw.x1); + let y1 = self.raw.y1.max(other.raw.y1); + let x2 = self.raw.x2.min(other.raw.x2).max(x1); + let y2 = self.raw.y2.min(other.raw.y2).max(y1); + Self { + raw: RectRaw { + x1, + y1, + x2, + y2, + tag: NoTag, + }, + } + } + + pub fn with_size(&self, width: i32, height: i32) -> Option { + Self::new_sized(self.raw.x1, self.raw.y1, width, height) + } + + pub fn with_tag(&self, tag: u32) -> Rect { + Rect { + raw: RectRaw { + x1: self.raw.x1, + y1: self.raw.y1, + x2: self.raw.x2, + y2: self.raw.y2, + tag, + }, + } + } +} + +impl Rect +where + T: Tag, +{ + #[cfg_attr(not(test), expect(dead_code))] + fn new_unchecked_danger_tagged(x1: i32, y1: i32, x2: i32, y2: i32, tag: T) -> Self { + Self { + raw: RectRaw { + x1, + y1, + x2, + y2, + tag, }, } } @@ -113,16 +200,6 @@ impl Rect { && other.raw.y1 < self.raw.y2 } - pub fn intersect(&self, other: Self) -> Self { - let x1 = self.raw.x1.max(other.raw.x1); - let y1 = self.raw.y1.max(other.raw.y1); - let x2 = self.raw.x2.min(other.raw.x2).max(x1); - let y2 = self.raw.y2.min(other.raw.y2).max(y1); - Self { - raw: RectRaw { x1, y1, x2, y2 }, - } - } - pub fn contains(&self, x: i32, y: i32) -> bool { self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y } @@ -144,14 +221,20 @@ impl Rect { } #[expect(dead_code)] - pub fn contains_rect(&self, rect: &Self) -> bool { + pub fn contains_rect(&self, rect: &Rect) -> bool + where + U: Tag, + { self.raw.x1 <= rect.raw.x1 && self.raw.y1 <= rect.raw.x1 && rect.raw.x2 <= self.raw.x2 && rect.raw.y2 <= self.raw.y2 } - pub fn get_overflow(&self, child: &Self) -> RectOverflow { + pub fn get_overflow(&self, child: &Rect) -> RectOverflow + where + U: Tag, + { RectOverflow { left: self.raw.x1 - child.raw.x1, right: child.raw.x2 - self.raw.x2, @@ -177,6 +260,7 @@ impl Rect { y1: 0, x2: self.raw.x2 - self.raw.x1, y2: self.raw.y2 - self.raw.y1, + tag: self.raw.tag, }, } } @@ -188,6 +272,7 @@ impl Rect { y1: self.raw.y1.saturating_add(dy), x2: self.raw.x2.saturating_add(dx), y2: self.raw.y2.saturating_add(dy), + tag: self.raw.tag, }, } } @@ -199,14 +284,11 @@ impl Rect { y1, x2: x1 + self.raw.x2 - self.raw.x1, y2: y1 + self.raw.y2 - self.raw.y1, + tag: self.raw.tag, }, } } - pub fn with_size(&self, width: i32, height: i32) -> Option { - Self::new_sized(self.raw.x1, self.raw.y1, width, height) - } - pub fn translate(&self, x: i32, y: i32) -> (i32, i32) { (x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1)) } @@ -253,4 +335,8 @@ impl Rect { self.raw.y1 + self.height() / 2, ) } + + pub fn tag(&self) -> T { + self.raw.tag + } } diff --git a/src/rect/region.rs b/src/rect/region.rs index 980dfc7a..9679bc8a 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -7,11 +7,15 @@ use { }, }, jay_algorithms::rect::{ - RectRaw, - region::{extents, rects_to_bands, subtract, union}, + RectRaw, Tag, + region::{ + extents, intersect, intersect_tagged, rects_to_bands, rects_to_bands_tagged, subtract, + union, + }, }, smallvec::SmallVec, std::{ + borrow::Cow, cell::UnsafeCell, fmt::{Debug, Formatter}, mem, @@ -29,19 +33,6 @@ thread_local! { } impl Region { - pub fn new(rect: Rect) -> Rc { - Rc::new(Self::new2(rect)) - } - - pub fn new2(rect: Rect) -> Self { - let mut rects = SmallVec::new(); - rects.push(rect.raw); - Self { - rects, - extents: rect, - } - } - pub fn empty() -> Rc { EMPTY.with(|e| e.clone()) } @@ -96,13 +87,92 @@ impl Region { }) } + pub fn subtract_cow<'a>(&'a self, other: &Self) -> Cow<'a, Self> { + if self.extents.is_empty() || other.extents.is_empty() { + return Cow::Borrowed(self); + } + let rects = subtract(&self.rects, &other.rects); + Cow::Owned(Self { + extents: Rect { + raw: extents(&rects), + }, + rects, + }) + } + + pub fn intersect(&self, other: &Region) -> Self { + if self.is_empty() || other.is_empty() { + return Self::default(); + } + let rects = intersect(&self.rects, &other.rects); + Self { + extents: Rect { + raw: extents(&rects), + }, + rects, + } + } +} + +impl Region { + pub fn from_rects_tagged(rects: &[Rect]) -> Self { + if rects.is_empty() { + return Self::default(); + } + if rects.len() == 1 { + let mut rect = rects[0]; + rect.raw.tag = rect.raw.tag.constrain(); + return Self::new2(rect); + } + let rects = rects_to_bands_tagged(unsafe { + mem::transmute::<&[Rect], &[RectRaw]>(rects) + }); + Self { + extents: Rect { + raw: extents(&rects), + }, + rects, + } + } + + pub fn intersect_tagged(&self, other: &Region) -> Self { + if self.is_empty() || other.is_empty() { + return Self::default(); + } + let rects = intersect_tagged(&self.rects, &other.rects); + Self { + extents: Rect { + raw: extents(&rects), + }, + rects, + } + } +} + +impl Region +where + T: Tag, +{ + pub fn new(rect: Rect) -> Rc { + Rc::new(Self::new2(rect)) + } + + pub fn new2(rect: Rect) -> Self { + let mut rects = SmallVec::new(); + rects.push(rect.raw); + Self { + rects, + extents: rect.untag(), + } + } + #[cfg_attr(not(feature = "it"), expect(dead_code))] pub fn extents(&self) -> Rect { self.extents } - pub fn rects(&self) -> &[Rect] { - unsafe { mem::transmute::<&[RectRaw], &[Rect]>(&self.rects[..]) } + pub fn rects(&self) -> &[Rect] { + unsafe { mem::transmute::<&[RectRaw], &[Rect]>(&self.rects[..]) } } pub fn contains(&self, x: i32, y: i32) -> bool { @@ -116,13 +186,41 @@ impl Region { } false } + + pub fn contains_rect(&self, rect: &Rect) -> bool { + self.contains_rect2(rect, |r| *r) + } + + pub fn contains_rect2(&self, rect: &Rect, map: impl Fn(&Rect) -> Rect) -> bool { + if rect.is_empty() { + return true; + } + let mut y1 = rect.y1(); + for r in self.rects() { + let r = map(r); + if r.y2() <= y1 || r.x2() <= rect.x1() { + continue; + } + if r.y1() > y1 || r.x1() > rect.x1() || r.x2() < rect.x2() { + return false; + } + y1 = r.y2(); + if y1 >= rect.y2() { + return true; + } + } + false + } } -impl Deref for Region { - type Target = [Rect]; +impl Deref for Region +where + T: Tag, +{ + type Target = [Rect]; fn deref(&self) -> &Self::Target { - unsafe { mem::transmute::<&[RectRaw], _>(&self.rects) } + unsafe { mem::transmute::<&[RectRaw], _>(&self.rects) } } } diff --git a/src/rect/tests.rs b/src/rect/tests.rs index 5ded4804..0725f472 100644 --- a/src/rect/tests.rs +++ b/src/rect/tests.rs @@ -1,6 +1,6 @@ use { crate::rect::{Rect, Region}, - jay_algorithms::rect::RectRaw, + jay_algorithms::rect::{NoTag, RectRaw}, }; #[test] @@ -43,25 +43,29 @@ fn subtract1() { x1: 0, y1: 0, x2: 20, - y2: 5 + y2: 5, + tag: NoTag, }, RectRaw { x1: 0, y1: 5, x2: 5, - y2: 15 + y2: 15, + tag: NoTag, }, RectRaw { x1: 15, y1: 5, x2: 20, - y2: 15 + y2: 15, + tag: NoTag, }, RectRaw { x1: 0, y1: 15, x2: 20, - y2: 20 + y2: 20, + tag: NoTag, }, ] ); @@ -83,19 +87,22 @@ fn rects_to_bands() { x1: 0, y1: 0, x2: 30, - y2: 5 + y2: 5, + tag: NoTag, }, RectRaw { x1: 0, y1: 5, x2: 50, - y2: 10 + y2: 10, + tag: NoTag, }, RectRaw { x1: 30, y1: 10, x2: 50, - y2: 15 + y2: 15, + tag: NoTag, }, ] ); @@ -111,3 +118,572 @@ fn rects_to_bands2() { // println!("{:#?}", r.rects); assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]); } + +#[test] +fn rects_to_bands_tagged1() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1), + Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 200, + y2: 50, + tag: 1, + }, + RectRaw { + x1: 0, + y1: 50, + x2: 50, + y2: 150, + tag: 1, + }, + RectRaw { + x1: 50, + y1: 50, + x2: 150, + y2: 150, + tag: 0, + }, + RectRaw { + x1: 150, + y1: 50, + x2: 200, + y2: 150, + tag: 1, + }, + RectRaw { + x1: 0, + y1: 150, + x2: 200, + y2: 200, + tag: 1, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged2() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1), + Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0), + Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 2), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 200, + y2: 50, + tag: 1, + }, + RectRaw { + x1: 0, + y1: 50, + x2: 50, + y2: 150, + tag: 1, + }, + RectRaw { + x1: 50, + y1: 50, + x2: 150, + y2: 150, + tag: 0, + }, + RectRaw { + x1: 150, + y1: 50, + x2: 200, + y2: 150, + tag: 1, + }, + RectRaw { + x1: 0, + y1: 150, + x2: 200, + y2: 200, + tag: 1, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged3() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 2), + Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 1), + Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 200, + y2: 50, + tag: 0, + }, + RectRaw { + x1: 0, + y1: 50, + x2: 50, + y2: 60, + tag: 0, + }, + RectRaw { + x1: 50, + y1: 50, + x2: 150, + y2: 60, + tag: 1, + }, + RectRaw { + x1: 150, + y1: 50, + x2: 200, + y2: 60, + tag: 0, + }, + RectRaw { + x1: 0, + y1: 60, + x2: 50, + y2: 140, + tag: 0, + }, + RectRaw { + x1: 50, + y1: 60, + x2: 60, + y2: 140, + tag: 1, + }, + RectRaw { + x1: 60, + y1: 60, + x2: 140, + y2: 140, + tag: 0, + }, + RectRaw { + x1: 140, + y1: 60, + x2: 150, + y2: 140, + tag: 1, + }, + RectRaw { + x1: 150, + y1: 60, + x2: 200, + y2: 140, + tag: 0, + }, + RectRaw { + x1: 0, + y1: 140, + x2: 50, + y2: 150, + tag: 0, + }, + RectRaw { + x1: 50, + y1: 140, + x2: 150, + y2: 150, + tag: 1, + }, + RectRaw { + x1: 150, + y1: 140, + x2: 200, + y2: 150, + tag: 0, + }, + RectRaw { + x1: 0, + y1: 150, + x2: 200, + y2: 200, + tag: 0, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged4() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1), + Rect::new_unchecked_danger_tagged(100, 0, 200, 200, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 1, + }, + RectRaw { + x1: 100, + y1: 0, + x2: 200, + y2: 100, + tag: 0, + }, + RectRaw { + x1: 100, + y1: 100, + x2: 200, + y2: 200, + tag: 0, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged5() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1), + Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 1, + }, + RectRaw { + x1: 100, + y1: 0, + x2: 200, + y2: 100, + tag: 0, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged6() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1), + Rect::new_unchecked_danger_tagged(100, 0, 300, 100, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 1, + }, + RectRaw { + x1: 100, + y1: 0, + x2: 300, + y2: 100, + tag: 0, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged7() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 0), + Rect::new_unchecked_danger_tagged(100, 0, 300, 200, 1), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 200, + y2: 100, + tag: 0, + }, + RectRaw { + x1: 200, + y1: 0, + x2: 300, + y2: 100, + tag: 1, + }, + RectRaw { + x1: 100, + y1: 100, + x2: 300, + y2: 200, + tag: 1, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged8() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1), + Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 1, + }, + RectRaw { + x1: 100, + y1: 0, + x2: 200, + y2: 100, + tag: 0, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged9() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1), + Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 1), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[RectRaw { + x1: 0, + y1: 0, + x2: 200, + y2: 100, + tag: 1, + },], + ); +} + +#[test] +fn rects_to_bands_tagged10() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1), + Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 1), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 200, + tag: 1, + },], + ); +} + +#[test] +fn rects_to_bands_tagged11() { + let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11)]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 1, + },], + ); +} + +#[test] +fn rects_to_bands_tagged12() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11), + Rect::new_unchecked_danger_tagged(200, 0, 300, 100, 10), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 1, + }, + RectRaw { + x1: 200, + y1: 0, + x2: 300, + y2: 100, + tag: 0, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged13() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1), + Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 1, + }, + RectRaw { + x1: 0, + y1: 100, + x2: 100, + y2: 200, + tag: 0, + }, + ], + ); +} + +#[test] +fn rects_to_bands_tagged14() { + let rects = [ + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1), + Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0), + ]; + let r = Region::from_rects_tagged(&rects[..]); + assert_eq!( + &r.rects[..], + &[RectRaw { + x1: 0, + y1: 0, + x2: 100, + y2: 100, + tag: 0, + },], + ); +} + +#[test] +fn intersect1() { + let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0)]; + let r1 = Region::from_rects_tagged(&rects[..]); + let rects = [Rect::new_unchecked_danger(100, 100, 200, 200)]; + let r2 = Region::from_rects2(&rects[..]); + let r3 = r1.intersect_tagged(&r2); + assert_eq!(&r3.rects[..], &[],); +} + +#[test] +fn intersect2() { + let rects = [Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 0)]; + let r1 = Region::from_rects_tagged(&rects[..]); + let rects = [Rect::new_unchecked_danger(50, 50, 150, 150)]; + let r2 = Region::from_rects2(&rects[..]); + let r3 = r1.intersect_tagged(&r2); + assert_eq!( + &r3.rects[..], + &[RectRaw { + x1: 50, + y1: 50, + x2: 150, + y2: 150, + tag: 0, + }], + ); +} + +#[test] +fn intersect3() { + macro_rules! t { + ($l:expr, $r:expr, $t:expr) => { + Rect::new_unchecked_danger_tagged($l, 0, $r, 1, $t) + }; + } + macro_rules! u { + ($l:expr, $r:expr) => { + Rect::new_unchecked_danger($l, 0, $r, 1) + }; + } + macro_rules! r { + ($l:expr, $r:expr, $t:expr) => { + RectRaw { + x1: $l, + y1: 0, + x2: $r, + y2: 1, + tag: $t, + } + }; + } + let rects = [ + t!(0, 100, 0), + t!(110, 130, 1), + t!(140, 160, 2), + t!(170, 180, 0), + ]; + let r1 = Region::from_rects_tagged(&rects[..]); + let rects = [ + u!(10, 20), + u!(50, 60), + u!(70, 100), + u!(120, 150), + u!(170, 180), + ]; + let r2 = Region::from_rects2(&rects[..]); + let r3 = r1.intersect_tagged(&r2); + assert_eq!( + &r3.rects[..], + &[ + r!(10, 20, 0), + r!(50, 60, 0), + r!(70, 100, 0), + r!(120, 130, 1), + r!(140, 150, 0), + r!(170, 180, 0), + ], + ); +} diff --git a/src/renderer.rs b/src/renderer.rs index b69d1a3b..fa8a0458 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -89,7 +89,7 @@ impl Renderer<'_> { let bar_bg = self.base.scale_rect(bar_bg); let c = theme.colors.bar_background.get(); self.base - .fill_boxes3(slice::from_ref(&bar_bg), &c, x, y, true); + .fill_boxes3(slice::from_ref(&bar_bg), &c, None, x, y, true); let rd = output.render_data.borrow_mut(); if let Some(aw) = &rd.active_workspace { let c = match aw.captured { @@ -124,6 +124,7 @@ impl Renderer<'_> { None, AcquireSync::None, ReleaseSync::None, + false, ); } if let Some(status) = &rd.status { @@ -141,6 +142,7 @@ impl Renderer<'_> { None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -219,6 +221,7 @@ impl Renderer<'_> { None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -264,6 +267,7 @@ impl Renderer<'_> { None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -336,7 +340,8 @@ impl Renderer<'_> { }; let color = self.state.theme.colors.highlight.get(); self.base.ops.push(GfxApiOpt::Sync); - self.base.fill_scaled_boxes(slice::from_ref(bounds), &color); + self.base + .fill_scaled_boxes(slice::from_ref(bounds), &color, None); } pub fn render_highlight(&mut self, rect: &Rect) { @@ -378,7 +383,6 @@ impl Renderer<'_> { } else { size = self.base.scale_point(size.0, size.1); } - let alpha = surface.alpha(); if let Some(children) = children.deref() { macro_rules! render { ($children:expr) => { @@ -400,10 +404,10 @@ impl Renderer<'_> { }; } render!(&children.below); - self.render_buffer(surface, &buffer, alpha, x, y, *tpoints, size, bounds); + self.render_buffer(surface, &buffer, x, y, *tpoints, size, bounds); render!(&children.above); } else { - self.render_buffer(surface, &buffer, alpha, x, y, *tpoints, size, bounds); + self.render_buffer(surface, &buffer, x, y, *tpoints, size, bounds); } } @@ -411,14 +415,18 @@ impl Renderer<'_> { &mut self, surface: &WlSurface, buffer: &Rc, - alpha: Option, x: i32, y: i32, tpoints: SampleRect, tsize: (i32, i32), bounds: Option<&Rect>, ) { + let alpha = surface.alpha(); if let Some(tex) = buffer.buffer.get_texture(surface) { + let mut opaque = surface.opaque(); + if !opaque && tex.format().has_alpha { + opaque = self.bounds_are_opaque(x, y, bounds, surface); + } self.base.render_texture( &tex, alpha, @@ -431,6 +439,7 @@ impl Renderer<'_> { Some(buffer.clone()), AcquireSync::Unnecessary, buffer.release_sync, + opaque, ); } else if let Some(color) = &buffer.buffer.color { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { @@ -440,11 +449,7 @@ impl Renderer<'_> { }; if !rect.is_empty() { self.base.ops.push(GfxApiOpt::Sync); - let mut color = *color; - if let Some(alpha) = alpha { - color = color * alpha; - } - self.base.fill_scaled_boxes(&[rect], &color); + self.base.fill_scaled_boxes(&[rect], color, alpha); } } } else { @@ -499,6 +504,7 @@ impl Renderer<'_> { None, AcquireSync::None, ReleaseSync::None, + false, ); } } @@ -517,4 +523,23 @@ impl Renderer<'_> { let (dx, dy) = surface.surface.extents.get().position(); self.render_surface(&surface.surface, x - dx, y - dy, None); } + + fn bounds_are_opaque( + &self, + x: i32, + y: i32, + bounds: Option<&Rect>, + surface: &WlSurface, + ) -> bool { + let Some(bounds) = bounds else { + return false; + }; + let Some(region) = surface.opaque_region() else { + return false; + }; + let surface_size = surface.buffer_abs_pos.get().at_point(0, 0); + let surface_size = self.base.scale_rect(surface_size); + let bounds = bounds.move_(-x, -y).intersect(surface_size); + region.contains_rect2(&bounds, |r| self.base.scale_rect(*r)) + } } diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index 5230e0d4..b5413b87 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -64,19 +64,27 @@ impl RendererBase<'_> { rect } - pub fn fill_scaled_boxes(&mut self, boxes: &[Rect], color: &Color) { - self.fill_boxes3(boxes, color, 0, 0, true); + pub fn fill_scaled_boxes(&mut self, boxes: &[Rect], color: &Color, alpha: Option) { + self.fill_boxes3(boxes, color, alpha, 0, 0, true); } pub fn fill_boxes(&mut self, boxes: &[Rect], color: &Color) { - self.fill_boxes3(boxes, color, 0, 0, false); + self.fill_boxes3(boxes, color, None, 0, 0, false); } pub fn fill_boxes2(&mut self, boxes: &[Rect], color: &Color, dx: i32, dy: i32) { - self.fill_boxes3(boxes, color, dx, dy, false); + self.fill_boxes3(boxes, color, None, dx, dy, false); } - pub fn fill_boxes3(&mut self, boxes: &[Rect], color: &Color, dx: i32, dy: i32, scaled: bool) { + pub fn fill_boxes3( + &mut self, + boxes: &[Rect], + color: &Color, + alpha: Option, + dx: i32, + dy: i32, + scaled: bool, + ) { if boxes.is_empty() || *color == Color::TRANSPARENT { return; } @@ -97,6 +105,7 @@ impl RendererBase<'_> { self.fb_height, ), color: *color, + alpha, })); } } @@ -129,6 +138,7 @@ impl RendererBase<'_> { self.fb_height, ), color: *color, + alpha: None, })); } } @@ -146,6 +156,7 @@ impl RendererBase<'_> { buffer_resv: Option>, acquire_sync: AcquireSync, release_sync: ReleaseSync, + opaque: bool, ) { let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); @@ -188,6 +199,7 @@ impl RendererBase<'_> { buffer_resv, acquire_sync, release_sync, + opaque, })); } } diff --git a/src/screenshoter.rs b/src/screenshoter.rs index ad613c9f..657551d4 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -88,6 +88,7 @@ pub fn take_screenshot( false, false, Transform::None, + None, )?; let drm = match allocator.drm() { Some(drm) => Some(drm.dup_render()?.fd().clone()), diff --git a/src/state.rs b/src/state.rs index a69c3795..d3b2694d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -27,8 +27,8 @@ use { forker::ForkerProxy, format::Format, gfx_api::{ - AcquireSync, BufferResv, GfxContext, GfxError, GfxFramebuffer, GfxTexture, - PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, SampleRect, SyncFile, + AcquireSync, BufferResv, GfxBlendBuffer, GfxContext, GfxError, GfxFramebuffer, + GfxTexture, PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, SampleRect, SyncFile, }, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal}, @@ -979,6 +979,7 @@ impl State { release_sync: ReleaseSync, tex: &Rc, render_hw_cursor: bool, + blend_buffer: Option<&Rc>, ) -> Result, GfxError> { let sync_file = fb.render_output( acquire_sync, @@ -989,6 +990,7 @@ impl State { output.global.persistent.scale.get(), render_hw_cursor, true, + blend_buffer, )?; output.latched(false); output.perform_screencopies( @@ -1046,6 +1048,7 @@ impl State { resv.cloned(), acquire_sync.clone(), release_sync, + false, ); if render_hardware_cursors { if let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() { @@ -1064,6 +1067,7 @@ impl State { target_release_sync, &ops, Some(&Color::SOLID_BLACK), + None, ) } diff --git a/src/theme.rs b/src/theme.rs index 44a38635..16dd235c 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -115,24 +115,26 @@ impl Color { [to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)] } - pub fn to_array_srgb(self) -> [f32; 4] { - [self.r, self.g, self.b, self.a] + pub fn to_array_srgb(self, alpha: Option) -> [f32; 4] { + let a = alpha.unwrap_or(1.0); + [self.r * a, self.g * a, self.b * a, self.a * a] } - #[expect(dead_code)] - pub fn to_array_linear(self) -> [f32; 4] { + pub fn to_array_linear(self, alpha: Option) -> [f32; 4] { fn to_linear(srgb: f32) -> f32 { if srgb <= 0.04045 { srgb / 12.92 } else { - (srgb + 0.055 / 1.055).powf(2.4) + ((srgb + 0.055) / 1.055).powf(2.4) } } + let a1 = if self.a == 0.0 { 1.0 } else { self.a }; + let a2 = self.a * alpha.unwrap_or(1.0); [ - to_linear(self.r), - to_linear(self.g), - to_linear(self.b), - self.a, + to_linear(self.r / a1) * a2, + to_linear(self.g / a1) * a2, + to_linear(self.b / a1) * a2, + a2, ] }