1
0
Fork 0
forked from wry/wry

Merge pull request #381 from mahkoh/jorth/blend-buffer-2

vulkan: add blend buffers
This commit is contained in:
mahkoh 2025-02-25 12:50:26 +01:00 committed by GitHub
commit 084006d64a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 2257 additions and 357 deletions

View file

@ -1,6 +1,7 @@
#![allow( #![allow(
clippy::mem_replace_with_default, clippy::mem_replace_with_default,
clippy::comparison_chain, clippy::comparison_chain,
clippy::collapsible_else_if,
clippy::needless_lifetimes clippy::needless_lifetimes
)] )]

View file

@ -5,31 +5,72 @@ use {
std::fmt::{Debug, Formatter}, 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)] #[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct RectRaw { pub struct RectRaw<T = NoTag>
where
T: Tag,
{
pub x1: i32, pub x1: i32,
pub y1: i32, pub y1: i32,
pub x2: i32, pub x2: i32,
pub y2: i32, pub y2: i32,
pub tag: T,
} }
impl Debug for RectRaw { impl<T> Debug for RectRaw<T>
where
T: Tag,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 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("x1", &self.x1)
.field("y1", &self.y1) .field("y1", &self.y1)
.field("x2", &self.x2) .field("x2", &self.x2)
.field("y2", &self.y2) .field("y2", &self.y2)
.field("width", &(self.x2 - self.x1)) .field("width", &(self.x2 - self.x1))
.field("height", &(self.y2 - self.y1)) .field("height", &(self.y2 - self.y1));
.finish() if T::IS_SIGNIFICANT {
debug.field("tag", &self.tag);
}
debug.finish()
} }
} }
impl RectRaw { impl<T> RectRaw<T>
where
T: Tag,
{
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.x1 == self.x2 || self.y1 == self.y2 self.x1 == self.x2 || self.y1 == self.y2
} }
} }
type Container = SmallVec<[RectRaw; 1]>; type Container<T = NoTag> = SmallVec<[RectRaw<T>; 1]>;

View file

@ -1,44 +1,64 @@
use { use {
crate::{ crate::{
rect::{Container, RectRaw}, rect::{Container, NoTag, RectRaw, Tag},
windows::WindowsExt, 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 { pub fn union(left: &Container, right: &Container) -> Container {
op::<Union>(left, right) op::<_, _, _, Union>(left, right)
} }
pub fn subtract(left: &Container, right: &Container) -> Container { pub fn subtract(left: &Container, right: &Container) -> Container {
op::<Subtract>(left, right) op::<_, _, _, Subtract>(left, right)
} }
struct Bands<'a> { pub fn intersect(left: &Container, right: &Container) -> Container {
rects: &'a [RectRaw], op::<_, _, _, Intersect<NoTag>>(left, right)
}
pub fn intersect_tagged(left: &Container<u32>, right: &Container) -> Container<u32> {
op::<_, _, _, Intersect<u32>>(left, right)
}
struct Bands<'a, T>
where
T: Tag,
{
rects: &'a [RectRaw<T>],
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct Band<'a> { struct Band<'a, T>
rects: &'a [RectRaw], where
T: Tag,
{
rects: &'a [RectRaw<T>],
y1: i32, y1: i32,
y2: i32, y2: i32,
} }
impl<'a> Band<'a> { impl<'a, T> Band<'a, T>
fn can_merge_with(&self, next: &Band) -> bool { where
T: Tag,
{
fn can_merge_with(&self, next: &Band<'_, T>) -> bool {
next.rects.len() == self.rects.len() next.rects.len() == self.rects.len()
&& next.y1 == self.y2 && next.y1 == self.y2
&& next && next
.rects .rects
.iter() .iter()
.zip(self.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> { impl<'a, T> Iterator for Bands<'a, T>
type Item = Band<'a>; where
T: Tag,
{
type Item = Band<'a, T>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.rects.is_empty() { if self.rects.is_empty() {
@ -62,7 +82,10 @@ impl<'a> Iterator for Bands<'a> {
} }
#[inline] #[inline]
pub fn extents(a: &[RectRaw]) -> RectRaw { pub fn extents<T>(a: &[RectRaw<T>]) -> RectRaw
where
T: Tag,
{
let mut a = a.iter(); let mut a = a.iter();
let mut res = match a.next() { let mut res = match a.next() {
Some(a) => *a, Some(a) => *a,
@ -74,10 +97,21 @@ pub fn extents(a: &[RectRaw]) -> RectRaw {
res.x2 = res.x2.max(a.x2); res.x2 = res.x2.max(a.x2);
res.y2 = res.y2.max(a.y2); 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<O: Op>(a: &[RectRaw], b: &[RectRaw]) -> Container { fn op<T1, T2, T3, O: Op<T1, T2, T3>>(a: &[RectRaw<T2>], b: &[RectRaw<T3>]) -> Container<T1>
where
T1: Tag,
T2: Tag,
T3: Tag,
{
let mut res = Container::new(); let mut res = Container::new();
let mut prev_band_y2 = 0; let mut prev_band_y2 = 0;
@ -100,7 +134,7 @@ fn op<O: Op>(a: &[RectRaw], b: &[RectRaw]) -> Container {
} }
macro_rules! append_nonoverlapping { 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 { if $append_opt {
let y2 = $a.y2.min($b.y1); let y2 = $a.y2.min($b.y1);
cur_band_start = res.len(); cur_band_start = res.len();
@ -111,6 +145,7 @@ fn op<O: Op>(a: &[RectRaw], b: &[RectRaw]) -> Container {
y1: $a.y1, y1: $a.y1,
x2: rect.x2, x2: rect.x2,
y2, y2,
tag: O::$map(rect.tag),
}); });
} }
fixup_new_band!($a.y1, y2); fixup_new_band!($a.y1, y2);
@ -125,9 +160,9 @@ fn op<O: Op>(a: &[RectRaw], b: &[RectRaw]) -> Container {
while let (Some(a), Some(b)) = (&mut a_opt, &mut b_opt) { while let (Some(a), Some(b)) = (&mut a_opt, &mut b_opt) {
if a.y1 < b.y1 { 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 { } 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 { } else {
let y2 = a.y2.min(b.y2); let y2 = a.y2.min(b.y2);
cur_band_start = res.len(); cur_band_start = res.len();
@ -149,7 +184,7 @@ fn op<O: Op>(a: &[RectRaw], b: &[RectRaw]) -> Container {
} }
macro_rules! push_trailing { macro_rules! push_trailing {
($a_opt:expr, $a_bands:expr) => {{ ($a_opt:expr, $a_bands:expr, $map:ident) => {{
while let Some(a) = $a_opt { while let Some(a) = $a_opt {
cur_band_start = res.len(); cur_band_start = res.len();
res.reserve(a.rects.len()); res.reserve(a.rects.len());
@ -159,6 +194,7 @@ fn op<O: Op>(a: &[RectRaw], b: &[RectRaw]) -> Container {
y1: a.y1, y1: a.y1,
x2: rect.x2, x2: rect.x2,
y2: a.y2, y2: a.y2,
tag: O::$map(rect.tag),
}); });
} }
fixup_new_band!(a.y1, a.y2); fixup_new_band!(a.y1, a.y2);
@ -168,25 +204,28 @@ fn op<O: Op>(a: &[RectRaw], b: &[RectRaw]) -> Container {
} }
if O::APPEND_NON_A { 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 { 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.shrink_to_fit();
res res
} }
fn coalesce(new: &mut Container, a: usize, b: usize, y2: i32) -> bool { fn coalesce<T>(new: &mut Container<T>, a: usize, b: usize, y2: i32) -> bool
where
T: Tag,
{
if new.len() - b != b - a { if new.len() - b != b - a {
return false; return false;
} }
let slice_a = &new[a..b]; let slice_a = &new[a..b];
let slice_b = &new[b..]; let slice_b = &new[b..];
for (a, b) in slice_a.iter().zip(slice_b.iter()) { 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; return false;
} }
} }
@ -197,16 +236,25 @@ fn coalesce(new: &mut Container, a: usize, b: usize, y2: i32) -> bool {
true true
} }
trait Op { trait Op<T1, T2, T3>
where
T1: Tag,
T2: Tag,
T3: Tag,
{
const APPEND_NON_A: bool; const APPEND_NON_A: bool;
const APPEND_NON_B: 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<T1>, a: &[RectRaw<T2>], b: &[RectRaw<T3>], y1: i32, y2: i32);
fn map_t2_to_t1(tag: T2) -> T1;
fn map_t3_to_t1(tag: T3) -> T1;
} }
struct Union; struct Union;
impl Op for Union { impl Op<NoTag, NoTag, NoTag> for Union {
const APPEND_NON_A: bool = true; const APPEND_NON_A: bool = true;
const APPEND_NON_B: bool = true; const APPEND_NON_B: bool = true;
@ -216,7 +264,13 @@ impl Op for Union {
macro_rules! push { 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!(); 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; struct Subtract;
impl Op for Subtract { impl Op<NoTag, NoTag, NoTag> for Subtract {
const APPEND_NON_A: bool = true; const APPEND_NON_A: bool = true;
const APPEND_NON_B: bool = false; const APPEND_NON_B: bool = false;
@ -291,6 +355,7 @@ impl Op for Subtract {
y1, y1,
x2: $x2, x2: $x2,
y2, y2,
tag: NoTag,
}); });
}; };
} }
@ -337,33 +402,145 @@ impl Op for Subtract {
pull!(); 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<T>(PhantomData<T>);
impl<T> Op<T, T, NoTag> for Intersect<T>
where
T: Tag,
{
const APPEND_NON_A: bool = false;
const APPEND_NON_B: bool = false;
fn handle_band(new: &mut Container<T>, a: &[RectRaw<T>], 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 { pub fn rects_to_bands(rects_tmp: &[RectRaw]) -> Container {
rects_to_bands_(rects_tmp)
}
pub fn rects_to_bands_tagged(rects_tmp: &[RectRaw<u32>]) -> Container<u32> {
rects_to_bands_(rects_tmp)
}
#[inline(always)]
fn rects_to_bands_<T>(rects_tmp: &[RectRaw<T>]) -> Container<T>
where
T: Tag,
{
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct W(RectRaw); struct W<T>(RectRaw<T>)
impl Eq for W {} where
impl PartialEq<Self> for W { T: Tag;
impl<T> Eq for W<T> where T: Tag {}
impl<T> PartialEq<Self> for W<T>
where
T: Tag,
{
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.0 == other.0 self.0 == other.0
} }
} }
impl PartialOrd<Self> for W { impl<T> PartialOrd<Self> for W<T>
where
T: Tag,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for W { impl<T> Ord for W<T>
where
T: Tag,
{
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.0 self.0
.y1 .y1
.cmp(&other.0.y1) .cmp(&other.0.y1)
.then_with(|| self.0.x1.cmp(&other.0.x1)) .then_with(|| self.0.x1.cmp(&other.0.x1))
.then_with(|| self.0.tag.cmp(&other.0.tag))
.reverse() .reverse()
} }
} }
impl Deref for W { impl<T> Deref for W<T>
type Target = RectRaw; where
T: Tag,
{
type Target = RectRaw<T>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
@ -411,17 +588,60 @@ pub fn rects_to_bands(rects_tmp: &[RectRaw]) -> Container {
check_rect!(rect); check_rect!(rect);
let mut x1 = rect.x1; let mut x1 = rect.x1;
let mut x2 = rect.x2; let mut x2 = rect.x2;
let mut tag: T = rect.tag;
while let Some(mut rect) = rects.peek().copied() { while let Some(mut rect) = rects.peek().copied() {
check_rect!(rect); check_rect!(rect);
if rect.x1 > x2 { if rect.x1 > x2 || (rect.tag != tag && rect.x1 == x2) {
res.push(RectRaw { x1, x2, y1, y2 }); res.push(RectRaw {
x1,
x2,
y1,
y2,
tag: tag.constrain(),
});
x1 = rect.x1; x1 = rect.x1;
x2 = rect.x2; x2 = rect.x2;
tag = rect.tag;
} else { } 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; break;
} }

View file

@ -13,6 +13,8 @@ pub fn main() -> anyhow::Result<()> {
compile_simple("fill.vert")?; compile_simple("fill.vert")?;
compile_simple("tex.vert")?; compile_simple("tex.vert")?;
compile_simple("tex.frag")?; compile_simple("tex.frag")?;
compile_simple("out.vert")?;
compile_simple("out.frag")?;
Ok(()) Ok(())
} }

View file

@ -1,6 +1,14 @@
# Unreleased # Unreleased
- Various bugfixes. - 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) # 1.9.1 (2025-02-13)

View file

@ -570,7 +570,7 @@ impl MetalConnector {
match opt { match opt {
GfxApiOpt::Sync => {} GfxApiOpt::Sync => {}
GfxApiOpt::FillRect(fr) => { 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. // Black fills can be ignored because this is the CRTC background color.
if fr.rect.is_covering() { if fr.rect.is_covering() {
// If fill covers the entire screen, we don't have to look further. // If fill covers the entire screen, we don't have to look further.
@ -749,6 +749,7 @@ impl MetalConnector {
ReleaseSync::Explicit, ReleaseSync::Explicit,
&latched.pass, &latched.pass,
&latched.damage, &latched.damage,
buffer.blend_buffer.as_ref(),
) )
.map_err(MetalError::RenderFrame)?; .map_err(MetalError::RenderFrame)?;
sync_file = buffer.copy_to_dev(sf)?; sync_file = buffer.copy_to_dev(sf)?;

View file

@ -18,8 +18,8 @@ use {
edid::{CtaDataBlock, Descriptor, EdidExtension}, edid::{CtaDataBlock, Descriptor, EdidExtension},
format::{ARGB8888, Format, XRGB8888}, format::{ARGB8888, Format, XRGB8888},
gfx_api::{ gfx_api::{
AcquireSync, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, SyncFile, AcquireSync, GfxBlendBuffer, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync,
needs_render_usage, SyncFile, needs_render_usage,
}, },
ifs::{ ifs::{
wl_output::OutputId, wl_output::OutputId,
@ -2607,6 +2607,15 @@ impl MetalBackend {
ctx: &MetalRenderContext, ctx: &MetalRenderContext,
cursor: bool, cursor: bool,
) -> Result<[RenderBuffer; N], MetalError> { ) -> 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::<N>()); let mut damage_queue = ArrayVec::from(DamageQueue::new::<N>());
let mut create = || { let mut create = || {
self.create_scanout_buffer( self.create_scanout_buffer(
@ -2618,6 +2627,7 @@ impl MetalBackend {
ctx, ctx,
cursor, cursor,
damage_queue.pop().unwrap(), damage_queue.pop().unwrap(),
blend_buffer.clone(),
) )
}; };
let mut array = ArrayVec::<_, N>::new(); let mut array = ArrayVec::<_, N>::new();
@ -2640,6 +2650,7 @@ impl MetalBackend {
render_ctx: &MetalRenderContext, render_ctx: &MetalRenderContext,
cursor: bool, cursor: bool,
damage_queue: DamageQueue, damage_queue: DamageQueue,
blend_buffer: Option<Rc<dyn GfxBlendBuffer>>,
) -> Result<RenderBuffer, MetalError> { ) -> Result<RenderBuffer, MetalError> {
let ctx = dev.ctx.get(); let ctx = dev.ctx.get();
let dev_gfx_formats = ctx.gfx.formats(); let dev_gfx_formats = ctx.gfx.formats();
@ -2771,6 +2782,7 @@ impl MetalBackend {
damage_queue, damage_queue,
dev_bo, dev_bo,
_render_bo: render_bo, _render_bo: render_bo,
blend_buffer,
dev_fb, dev_fb,
dev_tex, dev_tex,
render_tex, render_tex,
@ -2996,6 +3008,7 @@ pub struct RenderBuffer {
pub damage_queue: DamageQueue, pub damage_queue: DamageQueue,
pub dev_bo: GbmBo, pub dev_bo: GbmBo,
pub _render_bo: Option<GbmBo>, pub _render_bo: Option<GbmBo>,
pub blend_buffer: Option<Rc<dyn GfxBlendBuffer>>,
// ctx = dev // ctx = dev
// buffer location = dev // buffer location = dev
pub dev_fb: Rc<dyn GfxFramebuffer>, pub dev_fb: Rc<dyn GfxFramebuffer>,

View file

@ -755,6 +755,7 @@ impl XBackend {
ReleaseSync::Implicit, ReleaseSync::Implicit,
&image.tex.get(), &image.tex.get(),
true, true,
None,
); );
if let Err(e) = res { if let Err(e) = res {
log::error!("Could not render screen: {}", ErrorFmt(e)); log::error!("Could not render screen: {}", ErrorFmt(e));

View file

@ -391,6 +391,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -414,6 +415,7 @@ impl Cursor for StaticCursor {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -455,6 +457,7 @@ impl Cursor for AnimatedCursor {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }

View file

@ -408,7 +408,7 @@ static XBGR16161616: &Format = &Format {
..default(ConfigFormat::XBGR16161616) ..default(ConfigFormat::XBGR16161616)
}; };
static ABGR16161616F: &Format = &Format { pub static ABGR16161616F: &Format = &Format {
name: "abgr16161616f", name: "abgr16161616f",
vk_format: vk::Format::R16G16B16A16_SFLOAT, vk_format: vk::Format::R16G16B16A16_SFLOAT,
drm: fourcc_code('A', 'B', '4', 'H'), drm: fourcc_code('A', 'B', '4', 'H'),

View file

@ -116,7 +116,7 @@ impl SampleRect {
} }
} }
#[derive(Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct FramebufferRect { pub struct FramebufferRect {
pub x1: f32, pub x1: f32,
pub x2: f32, pub x2: f32,
@ -165,12 +165,45 @@ impl FramebufferRect {
pub fn is_covering(&self) -> bool { pub fn is_covering(&self) -> bool {
self.x1 == -1.0 && self.y1 == -1.0 && self.x2 == 1.0 && self.y2 == 1.0 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)] #[derive(Debug)]
pub struct FillRect { pub struct FillRect {
pub rect: FramebufferRect, pub rect: FramebufferRect,
pub color: Color, pub color: Color,
pub alpha: Option<f32>,
}
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 { pub struct CopyTexture {
@ -181,6 +214,7 @@ pub struct CopyTexture {
pub acquire_sync: AcquireSync, pub acquire_sync: AcquireSync,
pub release_sync: ReleaseSync, pub release_sync: ReleaseSync,
pub alpha: Option<f32>, pub alpha: Option<f32>,
pub opaque: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -254,6 +288,10 @@ pub enum ResetStatus {
Other(u32), Other(u32),
} }
pub trait GfxBlendBuffer: Debug {
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
}
pub trait GfxFramebuffer: Debug { pub trait GfxFramebuffer: Debug {
fn physical_size(&self) -> (i32, i32); fn physical_size(&self) -> (i32, i32);
@ -264,9 +302,15 @@ pub trait GfxFramebuffer: Debug {
ops: &[GfxApiOpt], ops: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
region: &Region, region: &Region,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError>; ) -> Result<Option<SyncFile>, GfxError>;
fn format(&self) -> &'static Format; 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 { pub trait GfxInternalFramebuffer: GfxFramebuffer {
@ -291,14 +335,16 @@ impl dyn GfxFramebuffer {
release_sync: ReleaseSync, release_sync: ReleaseSync,
ops: &[GfxApiOpt], ops: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
self.clone() self.clone().render_with_region(
.render_with_region(acquire_sync, release_sync, ops, clear, &self.full_region()) acquire_sync,
} release_sync,
ops,
fn full_region(&self) -> Region { clear,
let (width, height) = self.physical_size(); &self.full_region(),
Region::new2(Rect::new_sized_unchecked(0, 0, width, height)) blend_buffer,
)
} }
pub fn clear( pub fn clear(
@ -318,7 +364,13 @@ impl dyn GfxFramebuffer {
b: f32, b: f32,
a: f32, a: f32,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, 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) { pub fn logical_size(&self, transform: Transform) -> (i32, i32) {
@ -360,9 +412,10 @@ impl dyn GfxFramebuffer {
resv.cloned(), resv.cloned(),
acquire_sync, acquire_sync,
release_sync, release_sync,
false,
); );
let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); 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( pub fn render_custom(
@ -371,12 +424,13 @@ impl dyn GfxFramebuffer {
release_sync: ReleaseSync, release_sync: ReleaseSync,
scale: Scale, scale: Scale,
clear: Option<&Color>, clear: Option<&Color>,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
f: &mut dyn FnMut(&mut RendererBase), f: &mut dyn FnMut(&mut RendererBase),
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
let mut ops = vec![]; let mut ops = vec![];
let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); let mut renderer = self.renderer_base(&mut ops, scale, Transform::None);
f(&mut renderer); 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( pub fn create_render_pass(
@ -413,6 +467,7 @@ impl dyn GfxFramebuffer {
release_sync: ReleaseSync, release_sync: ReleaseSync,
pass: &GfxRenderPass, pass: &GfxRenderPass,
region: &Region, region: &Region,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
self.clone().render_with_region( self.clone().render_with_region(
acquire_sync, acquire_sync,
@ -420,6 +475,7 @@ impl dyn GfxFramebuffer {
&pass.ops, &pass.ops,
pass.clear.as_ref(), pass.clear.as_ref(),
region, region,
blend_buffer,
) )
} }
@ -433,6 +489,7 @@ impl dyn GfxFramebuffer {
scale: Scale, scale: Scale,
render_hardware_cursor: bool, render_hardware_cursor: bool,
fill_black_in_grace_period: bool, fill_black_in_grace_period: bool,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
self.render_node( self.render_node(
acquire_sync, acquire_sync,
@ -446,6 +503,7 @@ impl dyn GfxFramebuffer {
node.has_fullscreen(), node.has_fullscreen(),
fill_black_in_grace_period, fill_black_in_grace_period,
node.global.persistent.transform.get(), node.global.persistent.transform.get(),
blend_buffer,
) )
} }
@ -462,6 +520,7 @@ impl dyn GfxFramebuffer {
black_background: bool, black_background: bool,
fill_black_in_grace_period: bool, fill_black_in_grace_period: bool,
transform: Transform, transform: Transform,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
let pass = self.create_render_pass( let pass = self.create_render_pass(
node, node,
@ -475,7 +534,13 @@ impl dyn GfxFramebuffer {
transform, transform,
None, 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( pub fn render_hardware_cursor(
@ -498,7 +563,13 @@ impl dyn GfxFramebuffer {
}, },
}; };
cursor.render_hardware_cursor(&mut renderer); 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)) Rc::new(Dummy(size))
} }
fn acquire_blend_buffer(
&self,
width: i32,
height: i32,
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError>;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -197,12 +197,14 @@ enum RenderError {
UnsupportedShmFormat(&'static str), UnsupportedShmFormat(&'static str),
#[error("Could not access the client memory")] #[error("Could not access the client memory")]
AccessFailed(#[source] Box<dyn Error + Sync + Send>), AccessFailed(#[source] Box<dyn Error + Sync + Send>),
#[error("OpenGL does not support blend buffers")]
NoBlendBuffer,
} }
#[derive(Default)] #[derive(Default)]
struct GfxGlState { struct GfxGlState {
triangles: RefCell<Vec<[f32; 2]>>, triangles: RefCell<Vec<[f32; 2]>>,
fill_rect: VecStorage<&'static FillRect>, fill_rect: VecStorage<FillRect>,
copy_tex: VecStorage<&'static CopyTexture>, copy_tex: VecStorage<&'static CopyTexture>,
} }
@ -233,7 +235,11 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
} }
} }
GfxApiOpt::FillRect(f) => { GfxApiOpt::FillRect(f) => {
fill_rect.push(f); fill_rect.push(FillRect {
rect: f.rect,
color: f.effective_color(),
alpha: None,
});
i += 1; i += 1;
} }
GfxApiOpt::CopyTexture(c) => { GfxApiOpt::CopyTexture(c) => {
@ -249,7 +255,7 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
triangles.clear(); triangles.clear();
let mut color = None; let mut color = None;
while i < fill_rect.len() { while i < fill_rect.len() {
let fr = fill_rect[i]; let fr = &fill_rect[i];
match color { match color {
None => color = Some(fr.color), None => color = Some(fr.color),
Some(c) if c == fr.color => {} Some(c) if c == fr.color => {}

View file

@ -4,8 +4,8 @@ use {
cpu_worker::CpuWorker, cpu_worker::CpuWorker,
format::{Format, XRGB8888}, format::{Format, XRGB8888},
gfx_api::{ gfx_api::{
AsyncShmGfxTexture, BufferResvUser, GfxContext, GfxError, GfxFormat, GfxFramebuffer, AsyncShmGfxTexture, BufferResvUser, GfxBlendBuffer, GfxContext, GfxError, GfxFormat,
GfxImage, GfxInternalFramebuffer, ResetStatus, ShmGfxTexture, GfxFramebuffer, GfxImage, GfxInternalFramebuffer, ResetStatus, ShmGfxTexture,
}, },
gfx_apis::gl::{ gfx_apis::gl::{
GfxGlState, RenderError, Texture, GfxGlState, RenderError, Texture,
@ -339,4 +339,12 @@ impl GfxContext for GlRenderContext {
fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> { fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> {
Some(&self.sync_ctx) Some(&self.sync_ctx)
} }
fn acquire_blend_buffer(
&self,
_width: i32,
_height: i32,
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError> {
Err(GfxError(Box::new(RenderError::NoBlendBuffer)))
}
} }

View file

@ -2,9 +2,9 @@ use {
crate::{ crate::{
format::Format, format::Format,
gfx_api::{ gfx_api::{
AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxError, GfxFramebuffer, AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxBlendBuffer, GfxError,
GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer, ReleaseSync, ShmMemory, GfxFramebuffer, GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer,
SyncFile, ReleaseSync, ShmMemory, SyncFile,
}, },
gfx_apis::gl::{ gfx_apis::gl::{
RenderError, RenderError,
@ -106,6 +106,7 @@ impl GfxFramebuffer for Framebuffer {
ops: &[GfxApiOpt], ops: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
_region: &Region, _region: &Region,
_blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
(*self) (*self)
.render(acquire_sync, ops, clear) .render(acquire_sync, ops, clear)

View file

@ -1,4 +1,5 @@
mod allocator; mod allocator;
mod blend_buffer;
mod bo_allocator; mod bo_allocator;
mod command; mod command;
mod descriptor; mod descriptor;
@ -24,9 +25,9 @@ use {
cpu_worker::{CpuWorker, jobs::read_write::ReadWriteJobError}, cpu_worker::{CpuWorker, jobs::read_write::ReadWriteJobError},
format::Format, format::Format,
gfx_api::{ gfx_api::{
AsyncShmGfxTexture, GfxContext, GfxError, GfxFormat, GfxImage, GfxInternalFramebuffer, AsyncShmGfxTexture, GfxBlendBuffer, GfxContext, GfxError, GfxFormat, GfxImage,
GfxStagingBuffer, ResetStatus, STAGING_DOWNLOAD, STAGING_UPLOAD, ShmGfxTexture, GfxInternalFramebuffer, GfxStagingBuffer, ResetStatus, STAGING_DOWNLOAD,
StagingBufferUsecase, STAGING_UPLOAD, ShmGfxTexture, StagingBufferUsecase,
}, },
gfx_apis::vulkan::{ gfx_apis::vulkan::{
image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer,
@ -204,6 +205,8 @@ pub enum VulkanError {
UndefinedContents, UndefinedContents,
#[error("The framebuffer is being used by the transfer queue")] #[error("The framebuffer is being used by the transfer queue")]
BusyInTransfer, BusyInTransfer,
#[error("Driver does not support descriptor buffers")]
NoDescriptorBuffer,
} }
impl From<VulkanError> for GfxError { impl From<VulkanError> for GfxError {
@ -270,6 +273,7 @@ impl GfxContext for Context {
let old = old.into_texture().into_vk(&self.0.device.device); let old = old.into_texture().into_vk(&self.0.device.device);
let shm = match &old.ty { let shm = match &old.ty {
VulkanImageMemory::DmaBuf(_) => unreachable!(), VulkanImageMemory::DmaBuf(_) => unreachable!(),
VulkanImageMemory::Blend(_) => unreachable!(),
VulkanImageMemory::Internal(shm) => shm, VulkanImageMemory::Internal(shm) => shm,
}; };
if old.width as i32 == width if old.width as i32 == width
@ -350,6 +354,15 @@ impl GfxContext for Context {
.device .device
.create_staging_shell(size as u64, upload, download) .create_staging_shell(size as u64, upload, download)
} }
fn acquire_blend_buffer(
&self,
width: i32,
height: i32,
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError> {
let buffer = self.0.acquire_blend_buffer(width, height)?;
Ok(buffer)
}
} }
impl Drop for Context { impl Drop for Context {

View file

@ -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<Self>,
width: i32,
height: i32,
) -> Result<Rc<VulkanImage>, 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<Self>) -> Rc<dyn Any> {
self
}
}

View file

@ -1,9 +1,12 @@
use { use {
crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice, sampler::VulkanSampler}, crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice, sampler::VulkanSampler},
arrayvec::ArrayVec, arrayvec::ArrayVec,
ash::vk::{ ash::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags, ext::descriptor_buffer,
DescriptorSetLayoutCreateInfo, DescriptorType, DeviceSize, ShaderStageFlags, vk::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags,
DescriptorSetLayoutCreateInfo, DescriptorType, DeviceSize, ShaderStageFlags,
},
}, },
std::{rc::Rc, slice}, std::{rc::Rc, slice},
}; };
@ -14,7 +17,6 @@ pub(super) struct VulkanDescriptorSetLayout {
pub(super) size: DeviceSize, pub(super) size: DeviceSize,
pub(super) offsets: ArrayVec<DeviceSize, 1>, pub(super) offsets: ArrayVec<DeviceSize, 1>,
pub(super) _sampler: Option<Rc<VulkanSampler>>, pub(super) _sampler: Option<Rc<VulkanSampler>>,
pub(super) has_sampler: bool,
} }
impl Drop for VulkanDescriptorSetLayout { impl Drop for VulkanDescriptorSetLayout {
@ -28,7 +30,7 @@ impl Drop for VulkanDescriptorSetLayout {
} }
impl VulkanDevice { impl VulkanDevice {
pub(super) fn create_descriptor_set_layout( pub(super) fn create_tex_descriptor_set_layout(
self: &Rc<Self>, self: &Rc<Self>,
sampler: &Rc<VulkanSampler>, sampler: &Rc<VulkanSampler>,
) -> Result<Rc<VulkanDescriptorSetLayout>, VulkanError> { ) -> Result<Rc<VulkanDescriptorSetLayout>, VulkanError> {
@ -52,9 +54,7 @@ impl VulkanDevice {
let mut size = 0; let mut size = 0;
let mut offsets = ArrayVec::new(); let mut offsets = ArrayVec::new();
if let Some(db) = &self.descriptor_buffer { if let Some(db) = &self.descriptor_buffer {
size = unsafe { db.get_descriptor_set_layout_size(layout) }; size = self.get_descriptor_set_size(db, layout);
size =
(size + self.descriptor_buffer_offset_mask) & !self.descriptor_buffer_offset_mask;
unsafe { unsafe {
offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0)); offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0));
} }
@ -65,7 +65,43 @@ impl VulkanDevice {
size, size,
offsets, offsets,
_sampler: Some(sampler.clone()), _sampler: Some(sampler.clone()),
has_sampler: true,
})) }))
} }
pub(super) fn create_out_descriptor_set_layout(
self: &Rc<Self>,
db: &descriptor_buffer::Device,
) -> Result<Rc<VulkanDescriptorSetLayout>, 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
}
} }

View file

@ -36,8 +36,8 @@ pub struct VulkanDescriptorBufferUnused {
pub address: DeviceAddress, pub address: DeviceAddress,
} }
#[derive(Default)]
pub struct VulkanDescriptorBufferWriter { pub struct VulkanDescriptorBufferWriter {
set_size: usize,
buffer: Vec<u8>, buffer: Vec<u8>,
} }
@ -49,13 +49,13 @@ impl VulkanDescriptorBufferCache {
pub fn new( pub fn new(
device: &Rc<VulkanDevice>, device: &Rc<VulkanDevice>,
allocator: &Rc<VulkanAllocator>, allocator: &Rc<VulkanAllocator>,
layout: &VulkanDescriptorSetLayout, has_sampler: bool,
) -> Self { ) -> Self {
Self { Self {
device: device.clone(), device: device.clone(),
allocator: allocator.clone(), allocator: allocator.clone(),
buffers: Default::default(), buffers: Default::default(),
has_sampler: layout.has_sampler, has_sampler,
} }
} }
@ -164,13 +164,6 @@ impl Drop for VulkanDescriptorBufferUnused {
} }
impl VulkanDescriptorBufferWriter { impl VulkanDescriptorBufferWriter {
pub fn new(layout: &VulkanDescriptorSetLayout) -> Self {
Self {
set_size: layout.size as usize,
buffer: Default::default(),
}
}
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.buffer.clear(); self.buffer.clear();
} }
@ -179,10 +172,13 @@ impl VulkanDescriptorBufferWriter {
self.buffer.len() as DeviceSize 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 buffer = &mut self.buffer;
let lo = buffer.len(); let lo = buffer.len();
buffer.resize(lo + self.set_size, 0); buffer.resize(lo + layout.size as usize, 0);
VulkanDescriptorBufferSetWriter { VulkanDescriptorBufferSetWriter {
set: &mut buffer[lo..], set: &mut buffer[lo..],
} }

View file

@ -3,7 +3,7 @@ use {
format::XRGB8888, format::XRGB8888,
gfx_apis::vulkan::{ gfx_apis::vulkan::{
VulkanError, VulkanError,
format::VulkanFormat, format::{VulkanBlendBufferLimits, VulkanFormat},
instance::{ instance::{
API_VERSION, ApiVersionDisplay, Extensions, VulkanInstance, API_VERSION, ApiVersionDisplay, Extensions, VulkanInstance,
map_extension_properties, map_extension_properties,
@ -63,6 +63,7 @@ pub struct VulkanDevice {
pub(super) image_drm_format_modifier: image_drm_format_modifier::Device, pub(super) image_drm_format_modifier: image_drm_format_modifier::Device,
pub(super) descriptor_buffer: Option<descriptor_buffer::Device>, pub(super) descriptor_buffer: Option<descriptor_buffer::Device>,
pub(super) formats: AHashMap<u32, VulkanFormat>, pub(super) formats: AHashMap<u32, VulkanFormat>,
pub(super) blend_limits: VulkanBlendBufferLimits,
pub(super) memory_types: ArrayVec<MemoryType, MAX_MEMORY_TYPES>, pub(super) memory_types: ArrayVec<MemoryType, MAX_MEMORY_TYPES>,
pub(super) graphics_queue: Queue, pub(super) graphics_queue: Queue,
pub(super) graphics_queue_idx: u32, pub(super) graphics_queue_idx: u32,
@ -71,6 +72,7 @@ pub struct VulkanDevice {
pub(super) transfer_granularity_mask: (u32, u32), pub(super) transfer_granularity_mask: (u32, u32),
pub(super) descriptor_buffer_offset_mask: DeviceSize, pub(super) descriptor_buffer_offset_mask: DeviceSize,
pub(super) combined_image_sampler_descriptor_size: usize, pub(super) combined_image_sampler_descriptor_size: usize,
pub(super) sampled_image_descriptor_size: usize,
} }
impl Drop for VulkanDevice { impl Drop for VulkanDevice {
@ -272,6 +274,9 @@ impl VulkanInstance {
} }
} }
let supports_descriptor_buffer = extensions.contains_key(descriptor_buffer::NAME); 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 (graphics_queue_family_idx, transfer_queue_family) = self.find_queues(phy_dev)?;
let mut distinct_transfer_queue_family_idx = None; let mut distinct_transfer_queue_family_idx = None;
let mut transfer_granularity_mask = (0, 0); let mut transfer_granularity_mask = (0, 0);
@ -334,6 +339,7 @@ impl VulkanInstance {
Err(e) => return Err(VulkanError::CreateDevice(e)), Err(e) => return Err(VulkanError::CreateDevice(e)),
}; };
let destroy_device = OnDrop(|| unsafe { device.destroy_device(None) }); 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 formats = self.load_formats(phy_dev)?;
let supports_xrgb8888 = formats let supports_xrgb8888 = formats
.get(&XRGB8888.drm) .get(&XRGB8888.drm)
@ -361,6 +367,7 @@ impl VulkanInstance {
.then(|| descriptor_buffer::Device::new(&self.instance, &device)); .then(|| descriptor_buffer::Device::new(&self.instance, &device));
let mut descriptor_buffer_offset_mask = 0; let mut descriptor_buffer_offset_mask = 0;
let mut combined_image_sampler_descriptor_size = 0; let mut combined_image_sampler_descriptor_size = 0;
let mut sampled_image_descriptor_size = 0;
if supports_descriptor_buffer { if supports_descriptor_buffer {
let mut descriptor_buffer_props = let mut descriptor_buffer_props =
PhysicalDeviceDescriptorBufferPropertiesEXT::default(); PhysicalDeviceDescriptorBufferPropertiesEXT::default();
@ -377,6 +384,7 @@ impl VulkanInstance {
- 1; - 1;
combined_image_sampler_descriptor_size = combined_image_sampler_descriptor_size =
descriptor_buffer_props.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 = let memory_properties =
unsafe { self.instance.get_physical_device_memory_properties(phy_dev) }; unsafe { self.instance.get_physical_device_memory_properties(phy_dev) };
@ -415,6 +423,8 @@ impl VulkanInstance {
transfer_granularity_mask, transfer_granularity_mask,
descriptor_buffer_offset_mask, descriptor_buffer_offset_mask,
combined_image_sampler_descriptor_size, combined_image_sampler_descriptor_size,
sampled_image_descriptor_size,
blend_limits,
})) }))
} }
} }

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
format::{FORMATS, Format}, format::{ABGR16161616F, FORMATS, Format},
gfx_apis::vulkan::{VulkanError, instance::VulkanInstance}, gfx_apis::vulkan::{VulkanError, instance::VulkanInstance},
video::{LINEAR_MODIFIER, Modifier}, video::{LINEAR_MODIFIER, Modifier},
}, },
@ -38,18 +38,24 @@ pub struct VulkanModifier {
pub render_needs_bridge: bool, pub render_needs_bridge: bool,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Default)]
pub struct VulkanModifierLimits { pub struct VulkanModifierLimits {
pub max_width: u32, pub max_width: u32,
pub max_height: u32, pub max_height: u32,
pub exportable: bool, pub exportable: bool,
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct VulkanInternalFormat { pub struct VulkanInternalFormat {
pub limits: VulkanModifierLimits, 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( const FRAMEBUFFER_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw(
0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw()
| FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(), | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(),
@ -79,6 +85,16 @@ const TRANSFER_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw(
const SHM_USAGE: ImageUsageFlags = const SHM_USAGE: ImageUsageFlags =
ImageUsageFlags::from_raw(TRANSFER_USAGE.as_raw() | TEX_USAGE.as_raw()); 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 { impl VulkanInstance {
pub(super) fn load_formats( pub(super) fn load_formats(
&self, &self,
@ -126,6 +142,29 @@ impl VulkanInstance {
Ok(()) Ok(())
} }
pub fn load_blend_format_limits(
&self,
phy_dev: PhysicalDevice,
) -> Result<VulkanBlendBufferLimits, VulkanError> {
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( fn load_shm_format(
&self, &self,
phy_dev: PhysicalDevice, phy_dev: PhysicalDevice,

View file

@ -3,9 +3,9 @@ use {
format::Format, format::Format,
gfx_api::{ gfx_api::{
AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback,
AsyncShmGfxTextureTransferCancellable, GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, AsyncShmGfxTextureTransferCancellable, GfxApiOpt, GfxBlendBuffer, GfxError,
GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, PendingShmTransfer, ReleaseSync, GfxFramebuffer, GfxImage, GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture,
ShmGfxTexture, ShmMemory, SyncFile, PendingShmTransfer, ReleaseSync, ShmGfxTexture, ShmMemory, SyncFile,
}, },
gfx_apis::vulkan::{ gfx_apis::vulkan::{
VulkanError, allocator::VulkanAllocation, device::VulkanDevice, VulkanError, allocator::VulkanAllocation, device::VulkanDevice,
@ -64,6 +64,7 @@ pub struct VulkanImage {
pub(super) ty: VulkanImageMemory, pub(super) ty: VulkanImageMemory,
pub(super) bridge: Option<VulkanFramebufferBridge>, pub(super) bridge: Option<VulkanFramebufferBridge>,
pub(super) shader_read_only_optimal_descriptor: Box<[u8]>, pub(super) shader_read_only_optimal_descriptor: Box<[u8]>,
pub(super) sampled_image_descriptor: Box<[u8]>,
pub(super) descriptor_buffer_version: Cell<u64>, pub(super) descriptor_buffer_version: Cell<u64>,
pub(super) descriptor_buffer_offset: Cell<DeviceSize>, pub(super) descriptor_buffer_offset: Cell<DeviceSize>,
pub(super) execution_version: Cell<u64>, pub(super) execution_version: Cell<u64>,
@ -102,6 +103,7 @@ pub enum QueueTransfer {
pub enum VulkanImageMemory { pub enum VulkanImageMemory {
DmaBuf(VulkanDmaBufImage), DmaBuf(VulkanDmaBufImage),
Internal(VulkanShmImage), Internal(VulkanShmImage),
Blend(#[expect(dead_code)] VulkanAllocation),
} }
pub struct VulkanDmaBufImage { pub struct VulkanDmaBufImage {
@ -451,6 +453,7 @@ impl VulkanDmaBufImageTemplate {
shader_read_only_optimal_descriptor: self shader_read_only_optimal_descriptor: self
.renderer .renderer
.sampler_read_only_descriptor(texture_view), .sampler_read_only_descriptor(texture_view),
sampled_image_descriptor: Box::new([]),
descriptor_buffer_version: Cell::new(0), descriptor_buffer_version: Cell::new(0),
descriptor_buffer_offset: Cell::new(0), descriptor_buffer_offset: Cell::new(0),
execution_version: Cell::new(0), execution_version: Cell::new(0),
@ -539,9 +542,30 @@ impl GfxFramebuffer for VulkanImage {
ops: &[GfxApiOpt], ops: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
region: &Region, region: &Region,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, 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 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()) .map_err(|e| e.into())
} }
@ -609,6 +633,7 @@ impl GfxTexture for VulkanImage {
match &self.ty { match &self.ty {
VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf), VulkanImageMemory::DmaBuf(b) => Some(&b.template.dmabuf),
VulkanImageMemory::Internal(_) => None, VulkanImageMemory::Internal(_) => None,
VulkanImageMemory::Blend(_) => None,
} }
} }

View file

@ -39,6 +39,7 @@ pub(super) struct PipelineCreateInfo {
pub(super) blend: bool, pub(super) blend: bool,
pub(super) src_has_alpha: bool, pub(super) src_has_alpha: bool,
pub(super) has_alpha_mult: bool, pub(super) has_alpha_mult: bool,
pub(super) with_linear_output: bool,
pub(super) frag_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>, pub(super) frag_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
} }
@ -76,8 +77,8 @@ impl VulkanDevice {
}; };
let destroy_layout = let destroy_layout =
OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) }); OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) });
let mut frag_spec_data = ArrayVec::<_, 8>::new(); let mut frag_spec_data = ArrayVec::<_, { 3 * 4 }>::new();
let mut frag_spec_entries = ArrayVec::<_, 2>::new(); let mut frag_spec_entries = ArrayVec::<_, 3>::new();
let mut frag_spec_entry = |data: &[u8]| { let mut frag_spec_entry = |data: &[u8]| {
let entry = SpecializationMapEntry::default() let entry = SpecializationMapEntry::default()
.constant_id(frag_spec_entries.len() as _) .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.src_has_alpha as u32).to_ne_bytes());
frag_spec_entry(&(info.has_alpha_mult 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() let frag_spec = SpecializationInfo::default()
.map_entries(&frag_spec_entries) .map_entries(&frag_spec_entries)
.data(&frag_spec_data); .data(&frag_spec_data);

View file

@ -4,8 +4,8 @@ use {
cpu_worker::PendingJob, cpu_worker::PendingJob,
format::XRGB8888, format::XRGB8888,
gfx_api::{ gfx_api::{
AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxFormat, GfxTexture, AcquireSync, BufferResv, BufferResvUser, GfxApiOpt, GfxBlendBuffer, GfxFormat,
GfxWriteModifier, ReleaseSync, SyncFile, GfxTexture, GfxWriteModifier, ReleaseSync, SyncFile,
}, },
gfx_apis::vulkan::{ gfx_apis::vulkan::{
VulkanError, VulkanError,
@ -22,12 +22,12 @@ use {
sampler::VulkanSampler, sampler::VulkanSampler,
semaphore::VulkanSemaphore, semaphore::VulkanSemaphore,
shaders::{ shaders::{
FILL_FRAG, FILL_VERT, FillPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants, FILL_FRAG, FILL_VERT, FillPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants,
VulkanShader, TEX_FRAG, TEX_VERT, TexPushConstants, VulkanShader,
}, },
}, },
io_uring::IoUring, io_uring::IoUring,
rect::Region, rect::{Rect, Region},
theme::Color, theme::Color,
utils::{ utils::{
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, once::Once, 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}, video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file},
}, },
ahash::AHashMap, ahash::AHashMap,
arrayvec::ArrayVec,
ash::{ ash::{
Device, Device,
vk::{ vk::{
@ -51,12 +52,15 @@ use {
}, },
}, },
isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt}, isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt},
jay_algorithms::rect::Tag,
linearize::{Linearize, StaticMap, static_map}, linearize::{Linearize, StaticMap, static_map},
std::{ std::{
borrow::Cow,
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::hash_map::Entry,
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
mem, ptr, mem, ptr,
rc::Rc, rc::{Rc, Weak},
slice, slice,
}, },
uapi::OwnedFd, uapi::OwnedFd,
@ -65,7 +69,8 @@ use {
pub struct VulkanRenderer { pub struct VulkanRenderer {
pub(super) formats: Rc<AHashMap<u32, GfxFormat>>, pub(super) formats: Rc<AHashMap<u32, GfxFormat>>,
pub(super) device: Rc<VulkanDevice>, pub(super) device: Rc<VulkanDevice>,
pub(super) pipelines: CopyHashMap<vk::Format, Rc<VulkanFormatPipelines>>, pub(super) pipelines: StaticMap<RenderPass, CopyHashMap<vk::Format, Rc<VulkanFormatPipelines>>>,
pub(super) out_pipelines: RefCell<AHashMap<vk::Format, Rc<VulkanPipeline>>>,
pub(super) gfx_command_buffers: CachedCommandBuffers, pub(super) gfx_command_buffers: CachedCommandBuffers,
pub(super) transfer_command_buffers: Option<CachedCommandBuffers>, pub(super) transfer_command_buffers: Option<CachedCommandBuffers>,
pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>, pub(super) wait_semaphores: Stack<Rc<VulkanSemaphore>>,
@ -81,13 +86,17 @@ pub struct VulkanRenderer {
pub(super) fill_frag_shader: Rc<VulkanShader>, pub(super) fill_frag_shader: Rc<VulkanShader>,
pub(super) tex_vert_shader: Rc<VulkanShader>, pub(super) tex_vert_shader: Rc<VulkanShader>,
pub(super) tex_frag_shader: Rc<VulkanShader>, pub(super) tex_frag_shader: Rc<VulkanShader>,
pub(super) out_vert_shader: Rc<VulkanShader>,
pub(super) out_frag_shader: Rc<VulkanShader>,
pub(super) tex_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>, pub(super) tex_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
pub(super) out_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
pub(super) defunct: Cell<bool>, pub(super) defunct: Cell<bool>,
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>, pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>, pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
pub(super) sampler: Rc<VulkanSampler>, pub(super) sampler: Rc<VulkanSampler>,
pub(super) tex_sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>, pub(super) sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
pub(super) tex_descriptor_buffer_writer: RefCell<VulkanDescriptorBufferWriter>, pub(super) resource_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
pub(super) blend_buffers: RefCell<AHashMap<(u32, u32), Weak<VulkanImage>>>,
} }
pub(super) struct CachedCommandBuffers { pub(super) struct CachedCommandBuffers {
@ -139,14 +148,23 @@ pub(super) struct Memory {
wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>, wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>,
release_fence: Option<Rc<VulkanFence>>, release_fence: Option<Rc<VulkanFence>>,
release_sync_file: Option<SyncFile>, release_sync_file: Option<SyncFile>,
descriptor_buffer: Option<VulkanDescriptorBuffer>, descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
paint_regions: Vec<PaintRegion>, paint_regions: StaticMap<RenderPass, Vec<PaintRegion>>,
clear_rects: Vec<ClearRect>, clear_rects: StaticMap<RenderPass, Vec<ClearRect>>,
image_copy_regions: Vec<ImageCopy2<'static>>, image_copy_regions: Vec<ImageCopy2<'static>>,
sampler_descriptor_buffer_writer: VulkanDescriptorBufferWriter,
resource_descriptor_buffer_writer: VulkanDescriptorBufferWriter,
regions_1: Vec<Rect>,
regions_2: Vec<Rect<u32>>,
}
#[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)]
pub(super) enum RenderPass {
BlendBuffer,
FrameBuffer,
} }
struct PaintRegion { struct PaintRegion {
rect: Rect2D,
x1: f32, x1: f32,
y1: f32, y1: f32,
x2: f32, x2: f32,
@ -158,15 +176,16 @@ pub(super) struct PendingFrame {
renderer: Rc<VulkanRenderer>, renderer: Rc<VulkanRenderer>,
cmd: Cell<Option<Rc<VulkanCommandBuffer>>>, cmd: Cell<Option<Rc<VulkanCommandBuffer>>>,
_fb: Rc<VulkanImage>, _fb: Rc<VulkanImage>,
_bb: Option<Rc<VulkanImage>>,
_textures: Vec<UsedTexture>, _textures: Vec<UsedTexture>,
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>, wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
waiter: Cell<Option<SpawnedFuture<()>>>, waiter: Cell<Option<SpawnedFuture<()>>>,
_release_fence: Option<Rc<VulkanFence>>, _release_fence: Option<Rc<VulkanFence>>,
_descriptor_buffer: Option<VulkanDescriptorBuffer>, _descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
} }
pub(super) struct VulkanFormatPipelines { pub(super) struct VulkanFormatPipelines {
pub(super) fill: Rc<VulkanPipeline>, pub(super) fill: StaticMap<TexSourceType, Rc<VulkanPipeline>>,
pub(super) tex: StaticMap<TexCopyType, StaticMap<TexSourceType, Rc<VulkanPipeline>>>, pub(super) tex: StaticMap<TexCopyType, StaticMap<TexSourceType, Rc<VulkanPipeline>>>,
} }
@ -179,7 +198,14 @@ impl VulkanDevice {
let fill_vert_shader = self.create_shader(FILL_VERT)?; let fill_vert_shader = self.create_shader(FILL_VERT)?;
let fill_frag_shader = self.create_shader(FILL_FRAG)?; let fill_frag_shader = self.create_shader(FILL_FRAG)?;
let sampler = self.create_sampler()?; 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_vert_shader = self.create_shader(TEX_VERT)?;
let tex_frag_shader = self.create_shader(TEX_FRAG)?; let tex_frag_shader = self.create_shader(TEX_FRAG)?;
let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?; let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?;
@ -220,18 +246,15 @@ impl VulkanDevice {
.collect(); .collect();
let allocator = self.create_allocator()?; let allocator = self.create_allocator()?;
let shm_allocator = self.create_threaded_allocator()?; let shm_allocator = self.create_threaded_allocator()?;
let tex_descriptor_buffer_cache = Rc::new(VulkanDescriptorBufferCache::new( let sampler_descriptor_buffer_cache =
self, Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, true));
&allocator, let resource_descriptor_buffer_cache =
&tex_descriptor_set_layout, Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, false));
));
let tex_descriptor_buffer_writer = RefCell::new(VulkanDescriptorBufferWriter::new(
&tex_descriptor_set_layout,
));
let render = Rc::new(VulkanRenderer { let render = Rc::new(VulkanRenderer {
formats: Rc::new(formats), formats: Rc::new(formats),
device: self.clone(), device: self.clone(),
pipelines: Default::default(), pipelines: Default::default(),
out_pipelines: Default::default(),
gfx_command_buffers, gfx_command_buffers,
transfer_command_buffers, transfer_command_buffers,
wait_semaphores: Default::default(), wait_semaphores: Default::default(),
@ -247,15 +270,19 @@ impl VulkanDevice {
fill_frag_shader, fill_frag_shader,
tex_vert_shader, tex_vert_shader,
tex_frag_shader, tex_frag_shader,
out_vert_shader,
out_frag_shader,
tex_descriptor_set_layout, tex_descriptor_set_layout,
out_descriptor_set_layout,
defunct: Cell::new(false), defunct: Cell::new(false),
pending_cpu_jobs: Default::default(), pending_cpu_jobs: Default::default(),
shm_allocator, shm_allocator,
sampler, sampler,
tex_sampler_descriptor_buffer_cache: tex_descriptor_buffer_cache, sampler_descriptor_buffer_cache,
tex_descriptor_buffer_writer, 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) Ok(render)
} }
} }
@ -264,21 +291,28 @@ impl VulkanRenderer {
fn get_or_create_pipelines( fn get_or_create_pipelines(
&self, &self,
format: vk::Format, format: vk::Format,
pass: RenderPass,
) -> Result<Rc<VulkanFormatPipelines>, VulkanError> { ) -> Result<Rc<VulkanFormatPipelines>, 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); return Ok(pl);
} }
let fill = self let create_fill_pipeline = |src_has_alpha| {
.device self.device
.create_pipeline::<FillPushConstants>(PipelineCreateInfo { .create_pipeline::<FillPushConstants>(PipelineCreateInfo {
format, format,
vert: self.fill_vert_shader.clone(), vert: self.fill_vert_shader.clone(),
frag: self.fill_frag_shader.clone(), frag: self.fill_frag_shader.clone(),
blend: true, blend: src_has_alpha,
src_has_alpha: true, src_has_alpha,
has_alpha_mult: false, has_alpha_mult: false,
frag_descriptor_set_layout: None, 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| { let create_tex_pipeline = |src_has_alpha, has_alpha_mult| {
self.device self.device
.create_pipeline::<TexPushConstants>(PipelineCreateInfo { .create_pipeline::<TexPushConstants>(PipelineCreateInfo {
@ -288,6 +322,7 @@ impl VulkanRenderer {
blend: src_has_alpha || has_alpha_mult, blend: src_has_alpha || has_alpha_mult,
src_has_alpha, src_has_alpha,
has_alpha_mult, has_alpha_mult,
with_linear_output,
frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()), 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_alpha = create_tex_pipeline(true, false)?;
let tex_mult_opaque = create_tex_pipeline(false, true)?; let tex_mult_opaque = create_tex_pipeline(false, true)?;
let tex_mult_alpha = create_tex_pipeline(true, true)?; let tex_mult_alpha = create_tex_pipeline(true, true)?;
let pipelines = Rc::new(VulkanFormatPipelines { let format_pipelines = Rc::new(VulkanFormatPipelines {
fill, fill: static_map! {
TexSourceType::HasAlpha => fill_alpha.clone(),
TexSourceType::Opaque => fill_opaque.clone(),
},
tex: static_map! { tex: static_map! {
TexCopyType::Identity => static_map! { TexCopyType::Identity => static_map! {
TexSourceType::HasAlpha => tex_alpha.clone(), TexSourceType::HasAlpha => tex_alpha.clone(),
@ -308,27 +346,38 @@ impl VulkanRenderer {
}, },
}, },
}); });
self.pipelines.set(format, pipelines.clone()); pipelines.set(format, format_pipelines.clone());
Ok(pipelines) Ok(format_pipelines)
} }
pub(super) fn allocate_point(&self) -> u64 { pub(super) fn allocate_point(&self) -> u64 {
self.last_point.fetch_add(1) + 1 self.last_point.fetch_add(1) + 1
} }
fn create_descriptor_buffer( fn create_descriptor_buffers(
&self, &self,
buf: CommandBuffer, buf: CommandBuffer,
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
bb: Option<&VulkanImage>,
) -> Result<(), VulkanError> { ) -> Result<(), VulkanError> {
let Some(db) = &self.device.descriptor_buffer else { let Some(db) = &self.device.descriptor_buffer else {
return Ok(()); return Ok(());
}; };
zone!("create_descriptor_buffer"); zone!("create_descriptor_buffers");
let version = self.allocate_point(); let version = self.allocate_point();
let memory = &mut *self.memory.borrow_mut(); let memory = &mut *self.memory.borrow_mut();
let writer = &mut *self.tex_descriptor_buffer_writer.borrow_mut(); memory.descriptor_buffers.clear();
writer.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 { for cmd in opts {
let GfxApiOpt::CopyTexture(c) = cmd else { let GfxApiOpt::CopyTexture(c) = cmd else {
continue; continue;
@ -337,27 +386,32 @@ impl VulkanRenderer {
if tex.descriptor_buffer_version.replace(version) == version { if tex.descriptor_buffer_version.replace(version) == version {
continue; continue;
} }
let offset = writer.next_offset(); let offset = sampler_writer.next_offset();
tex.descriptor_buffer_offset.set(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( writer.write(
self.tex_descriptor_set_layout.offsets[0], self.tex_descriptor_set_layout.offsets[0],
&tex.shader_read_only_optimal_descriptor, &tex.shader_read_only_optimal_descriptor,
); );
} }
let buffer = self let mut infos = ArrayVec::<_, 2>::new();
.tex_sampler_descriptor_buffer_cache for (writer, cache) in [
.allocate(writer.len() as DeviceSize)?; (&sampler_writer, &self.sampler_descriptor_buffer_cache),
buffer.buffer.allocation.upload(|ptr, _| unsafe { (&resource_writer, &self.resource_descriptor_buffer_cache),
ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len()) ] {
})?; let buffer = cache.allocate(writer.len() as DeviceSize)?;
let info = DescriptorBufferBindingInfoEXT::default() buffer.buffer.allocation.upload(|ptr, _| unsafe {
.usage(self.tex_sampler_descriptor_buffer_cache.usage()) ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len())
.address(buffer.buffer.address); })?;
unsafe { let info = DescriptorBufferBindingInfoEXT::default()
db.cmd_bind_descriptor_buffers(buf, slice::from_ref(&info)); .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(()) Ok(())
} }
@ -500,42 +554,61 @@ impl VulkanRenderer {
Ok(()) 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"); zone!("begin_rendering");
let memory = &mut *self.memory.borrow_mut(); let memory = &mut *self.memory.borrow_mut();
let clear_value = clear.map(|clear| ClearValue { let mut load_clear = None;
color: ClearColorValue { let mut manual_clear = None;
float32: clear.to_array_srgb(), let clear_rects = &memory.clear_rects[pass];
}, if let Some(clear) = clear {
}); if clear_rects.is_not_empty() {
let load_clear = memory.paint_regions.len() == 1 && { let clear_value = ClearValue {
let rect = &memory.paint_regions[0].rect; color: ClearColorValue {
rect.offset.x == 0 float32: match pass {
&& rect.offset.y == 0 RenderPass::BlendBuffer => clear.to_array_linear(None),
&& rect.extent.width == fb.width RenderPass::FrameBuffer => clear.to_array_srgb(None),
&& rect.extent.height == fb.height },
}; },
let (load_clear, manual_clear) = match load_clear { };
false => (None, clear_value), let use_load_clear = clear_rects.len() == 1 && {
true => (clear_value, None), let rect = &clear_rects[0].rect;
}; rect.offset.x == 0
let rendering_attachment_info = { && rect.offset.y == 0
let mut rai = RenderingAttachmentInfo::default() && rect.extent.width == target.width
.image_view(fb.render_view.unwrap_or(fb.texture_view)) && rect.extent.height == target.height
.image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) };
.load_op(AttachmentLoadOp::LOAD) if use_load_clear {
.store_op(AttachmentStoreOp::STORE); load_clear = Some(clear_value);
if let Some(clear) = load_clear { } else {
rai = rai.clear_value(clear).load_op(AttachmentLoadOp::CLEAR); 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() let rendering_info = RenderingInfo::default()
.render_area(Rect2D { .render_area(Rect2D {
offset: Default::default(), offset: Default::default(),
extent: Extent2D { extent: Extent2D {
width: fb.width, width: target.width,
height: fb.height, height: target.height,
}, },
}) })
.layer_count(1) .layer_count(1)
@ -543,26 +616,16 @@ impl VulkanRenderer {
unsafe { unsafe {
self.device.device.cmd_begin_rendering(buf, &rendering_info); 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 { if let Some(clear) = manual_clear {
let clear_attachment = ClearAttachment::default() let clear_attachment = ClearAttachment::default()
.color_attachment(0) .color_attachment(0)
.clear_value(clear) .clear_value(clear)
.aspect_mask(ImageAspectFlags::COLOR); .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 { unsafe {
self.device.device.cmd_clear_attachments( self.device
buf, .device
&[clear_attachment], .cmd_clear_attachments(buf, &[clear_attachment], clear_rects);
&memory.clear_rects,
);
} }
} }
} }
@ -598,12 +661,14 @@ impl VulkanRenderer {
fn record_draws( fn record_draws(
&self, &self,
buf: CommandBuffer, buf: CommandBuffer,
fb: &VulkanImage, target: &VulkanImage,
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
pass: RenderPass,
) -> Result<(), VulkanError> { ) -> Result<(), VulkanError> {
zone!("record_draws"); zone!("record_draws");
let memory = &*self.memory.borrow(); 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 dev = &self.device.device;
let mut current_pipeline = None; let mut current_pipeline = None;
let mut bind = |pipeline: &VulkanPipeline| { let mut bind = |pipeline: &VulkanPipeline| {
@ -620,19 +685,27 @@ impl VulkanRenderer {
GfxApiOpt::FillRect(r) => { GfxApiOpt::FillRect(r) => {
let push = FillPushConstants { let push = FillPushConstants {
pos: r.rect.to_points(), 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 mut push = push;
let draw = region.constrain(&mut push.pos, None); let draw = region.constrain(&mut push.pos, None);
if !draw { if !draw {
continue; continue;
} }
bind(&pipelines.fill); bind(pipeline);
unsafe { unsafe {
dev.cmd_push_constants( dev.cmd_push_constants(
buf, buf,
pipelines.fill.pipeline_layout, pipeline.pipeline_layout,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
0, 0,
uapi::as_bytes(&push), uapi::as_bytes(&push),
@ -656,7 +729,7 @@ impl VulkanRenderer {
true => TexCopyType::Multiply, true => TexCopyType::Multiply,
false => TexCopyType::Identity, false => TexCopyType::Identity,
}; };
let source_type = match tex.format.has_alpha { let source_type = match tex.format.has_alpha && !c.opaque {
true => TexSourceType::HasAlpha, true => TexSourceType::HasAlpha,
false => TexSourceType::Opaque, false => TexSourceType::Opaque,
}; };
@ -670,7 +743,7 @@ impl VulkanRenderer {
.image_view(tex.texture_view) .image_view(tex.texture_view)
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
let init = Once::default(); let init = Once::default();
for region in &memory.paint_regions { for region in paint_regions {
let mut push = push; let mut push = push;
let draw = region.constrain(&mut push.pos, Some(&mut push.tex_pos)); let draw = region.constrain(&mut push.pos, Some(&mut push.tex_pos));
if !draw { if !draw {
@ -717,6 +790,112 @@ impl VulkanRenderer {
Ok(()) 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::<OutPushConstants>(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) { fn end_rendering(&self, buf: CommandBuffer) {
zone!("end_rendering"); zone!("end_rendering");
unsafe { 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"); zone!("copy_bridge_to_dmabuf");
let Some(bridge) = &fb.bridge else { let Some(bridge) = &fb.bridge else {
return; return;
@ -766,15 +945,14 @@ impl VulkanRenderer {
.base_array_layer(0) .base_array_layer(0)
.mip_level(0); .mip_level(0);
memory.image_copy_regions.clear(); memory.image_copy_regions.clear();
for region in &memory.paint_regions { for rect in region.rects() {
let offset = Offset3D { let Some([x1, y1, x2, y2]) = constrain_to_fb(fb, rect) else {
x: region.rect.offset.x, continue;
y: region.rect.offset.y,
z: 0,
}; };
let offset = Offset3D { x: x1, y: y1, z: 0 };
let extent = Extent3D { let extent = Extent3D {
width: region.rect.extent.width, width: (x2 - x1) as _,
height: region.rect.extent.height, height: (y2 - y1) as _,
depth: 1, depth: 1,
}; };
memory.image_copy_regions.push( memory.image_copy_regions.push(
@ -786,14 +964,16 @@ impl VulkanRenderer {
.extent(extent), .extent(extent),
); );
} }
let copy_image_info = CopyImageInfo2::default() if memory.image_copy_regions.is_not_empty() {
.src_image(fb.image) let copy_image_info = CopyImageInfo2::default()
.src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) .src_image(fb.image)
.dst_image(bridge.dmabuf_image) .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL)
.dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) .dst_image(bridge.dmabuf_image)
.regions(&memory.image_copy_regions); .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL)
unsafe { .regions(&memory.image_copy_regions);
self.device.device.cmd_copy_image2(buf, &copy_image_info); unsafe {
self.device.device.cmd_copy_image2(buf, &copy_image_info);
}
} }
} }
@ -999,7 +1179,10 @@ impl VulkanRenderer {
Ok(()) 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.is_undefined.set(false);
fb.contents_are_undefined.set(false); fb.contents_are_undefined.set(false);
fb.queue_state.set(QueueState::Acquired { fb.queue_state.set(QueueState::Acquired {
@ -1013,7 +1196,12 @@ impl VulkanRenderer {
} }
} }
fn create_pending_frame(self: &Rc<Self>, buf: Rc<VulkanCommandBuffer>, fb: &Rc<VulkanImage>) { fn create_pending_frame(
self: &Rc<Self>,
buf: Rc<VulkanCommandBuffer>,
fb: &Rc<VulkanImage>,
bb: Option<Rc<VulkanImage>>,
) {
zone!("create_pending_frame"); zone!("create_pending_frame");
let point = self.allocate_point(); let point = self.allocate_point();
let mut memory = self.memory.borrow_mut(); let mut memory = self.memory.borrow_mut();
@ -1022,11 +1210,12 @@ impl VulkanRenderer {
renderer: self.clone(), renderer: self.clone(),
cmd: Cell::new(Some(buf)), cmd: Cell::new(Some(buf)),
_fb: fb.clone(), _fb: fb.clone(),
_bb: bb,
_textures: mem::take(&mut memory.textures), _textures: mem::take(&mut memory.textures),
wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)), wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)),
waiter: Cell::new(None), waiter: Cell::new(None),
_release_fence: memory.release_fence.take(), _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()); self.pending_frames.set(frame.point, frame.clone());
let future = self.eng.spawn( let future = self.eng.spawn(
@ -1049,9 +1238,18 @@ impl VulkanRenderer {
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
region: &Region, region: &Region,
blend_buffer: Option<Rc<VulkanImage>>,
) -> Result<Option<SyncFile>, VulkanError> { ) -> Result<Option<SyncFile>, VulkanError> {
zone!("execute"); 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 sync_file = {
let mut memory = self.memory.borrow_mut(); let mut memory = self.memory.borrow_mut();
memory.textures.clear(); memory.textures.clear();
@ -1059,7 +1257,7 @@ impl VulkanRenderer {
memory.queue_transfer.clear(); memory.queue_transfer.clear();
memory.wait_semaphores.clear(); memory.wait_semaphores.clear();
memory.release_fence.take(); memory.release_fence.take();
memory.descriptor_buffer.take(); memory.descriptor_buffers.clear();
memory.release_sync_file.take() memory.release_sync_file.take()
}; };
res.map(|_| sync_file) res.map(|_| sync_file)
@ -1074,37 +1272,124 @@ impl VulkanRenderer {
Ok(semaphore) 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(); let memory = &mut *self.memory.borrow_mut();
memory.paint_regions.clear(); memory.regions_1.clear();
for rect in region.rects() { memory.regions_2.clear();
let x1 = rect.x1().max(0); let width = fb.width as f32;
let y1 = rect.y1().max(0); let height = fb.height as f32;
let x2 = rect.x2(); let mut tag = 0;
let y2 = rect.y2(); for opt in opts.iter().rev() {
if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { let (opaque, fb_rect) = match opt {
continue; 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 rect = fb_rect.to_rect(width, height);
let y2 = y2.min(fb.height as i32); if opaque && clear.is_some() {
let to_fb = |c: i32, max: u32| 2.0 * (c as f32 / max as f32) - 1.0; memory.regions_1.push(rect);
memory.paint_regions.push(PaintRegion { }
rect: Rect2D { memory.regions_2.push(rect.with_tag(tag));
offset: Offset2D { }
x: x1 as _, let clear_region = if clear.is_some() {
y: y1 as _, let opaque_region = Region::from_rects2(&memory.regions_1);
}, region.subtract_cow(&opaque_region)
extent: Extent2D { } else {
width: (x2 - x1) as u32, Cow::Owned(Region::default())
height: (y2 - y1) as u32, };
}, 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), x1: to_fb(x1, fb.width),
x2: to_fb(x2, fb.width), x2: to_fb(x2, fb.width),
y1: to_fb(y1, fb.height), y1: to_fb(y1, fb.height),
y2: to_fb(y2, 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<Rc<VulkanImage>>) {
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( fn try_execute(
@ -1115,26 +1400,43 @@ impl VulkanRenderer {
opts: &[GfxApiOpt], opts: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
region: &Region, region: &Region,
mut blend_buffer: Option<Rc<VulkanImage>>,
) -> Result<(), VulkanError> { ) -> Result<(), VulkanError> {
self.check_defunct()?; 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()?; let buf = self.gfx_command_buffers.allocate()?;
self.collect_memory(opts); self.collect_memory(opts);
self.begin_command_buffer(buf.buffer)?; 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.initial_barriers(buf.buffer, fb)?;
self.begin_rendering(buf.buffer, fb, clear);
self.set_viewport(buf.buffer, fb); self.set_viewport(buf.buffer, fb);
self.record_draws(buf.buffer, fb, opts)?; if let Some(bb) = bb {
self.end_rendering(buf.buffer); zone!("blend buffer pass");
self.copy_bridge_to_dmabuf(buf.buffer, fb); 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.final_barriers(buf.buffer, fb);
self.end_command_buffer(buf.buffer)?; self.end_command_buffer(buf.buffer)?;
self.create_wait_semaphores(fb, &fb_acquire_sync)?; self.create_wait_semaphores(fb, &fb_acquire_sync)?;
self.submit(buf.buffer)?; self.submit(buf.buffer)?;
self.import_release_semaphore(fb, fb_release_sync); self.import_release_semaphore(fb, fb_release_sync);
self.store_layouts(fb); self.store_layouts(fb, bb);
self.create_pending_frame(buf, fb); self.create_pending_frame(buf, fb, blend_buffer);
Ok(()) Ok(())
} }
@ -1206,6 +1508,17 @@ impl dyn GfxTexture {
} }
} }
impl dyn GfxBlendBuffer {
pub(super) fn into_vk(self: Rc<Self>, device: &Device) -> Rc<VulkanImage> {
let img: Rc<VulkanImage> = self
.into_any()
.downcast()
.expect("Non-vulkan blend buffer passed into vulkan");
img.assert_device(device);
img
}
}
pub(super) fn image_barrier() -> ImageMemoryBarrier2<'static> { pub(super) fn image_barrier() -> ImageMemoryBarrier2<'static> {
ImageMemoryBarrier2::default().subresource_range( ImageMemoryBarrier2::default().subresource_range(
ImageSubresourceRange::default() ImageSubresourceRange::default()
@ -1291,3 +1604,19 @@ impl PaintRegion {
true true
} }
} }
fn constrain_to_fb<T>(fb: &VulkanImage, rect: &Rect<T>) -> 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])
}

View file

@ -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 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_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 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 struct VulkanShader {
pub(super) device: Rc<VulkanDevice>, pub(super) device: Rc<VulkanDevice>,
@ -34,6 +36,14 @@ pub struct TexPushConstants {
unsafe impl Packed for 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 { impl VulkanDevice {
pub(super) fn create_shader( pub(super) fn create_shader(
self: &Rc<Self>, self: &Rc<Self>,

View file

@ -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 = 0) const bool src_has_alpha = false;
layout(constant_id = 1) const bool has_alpha_multiplier = false; layout(constant_id = 1) const bool has_alpha_multiplier = false;
layout(constant_id = 2) const bool color_management = false;
#endif

View file

@ -0,0 +1,3 @@
layout(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4];
} data;

View file

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

View file

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

View file

@ -1,6 +1,7 @@
#version 450 #version 450
#include "frag_spec_const.glsl" #include "frag_spec_const.glsl"
#include "transfer_functions.glsl"
#include "tex.common.glsl" #include "tex.common.glsl"
layout(set = 0, binding = 0) uniform sampler2D tex; 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; layout(location = 0) out vec4 out_color;
void main() { 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 (has_alpha_multiplier) {
if (src_has_alpha) { if (src_has_alpha) {
out_color = textureLod(tex, tex_pos, 0) * data.mul; c *= data.mul;
} else { } 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;
} }

View file

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

View file

@ -460,12 +460,14 @@ impl VulkanRenderer {
ty: VulkanImageMemory::Internal(shm), ty: VulkanImageMemory::Internal(shm),
bridge: None, bridge: None,
shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view), shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view),
sampled_image_descriptor: Box::new([]),
descriptor_buffer_version: Cell::new(0), descriptor_buffer_version: Cell::new(0),
descriptor_buffer_offset: Cell::new(0), descriptor_buffer_offset: Cell::new(0),
execution_version: Cell::new(0), execution_version: Cell::new(0),
}); });
let shm = match &img.ty { let shm = match &img.ty {
VulkanImageMemory::DmaBuf(_) => unreachable!(), VulkanImageMemory::DmaBuf(_) => unreachable!(),
VulkanImageMemory::Blend(_) => unreachable!(),
VulkanImageMemory::Internal(s) => s, VulkanImageMemory::Internal(s) => s,
}; };
if data.is_not_empty() { if data.is_not_empty() {

View file

@ -245,6 +245,7 @@ impl ExtImageCopyCaptureFrameV1 {
true, true,
false, false,
jay_config::video::Transform::None, jay_config::video::Transform::None,
None,
) )
}); });
} }

View file

@ -202,6 +202,7 @@ impl JayScreencast {
false, false,
false, false,
Transform::None, Transform::None,
None,
); );
match res { match res {
Ok(_) => { Ok(_) => {

View file

@ -278,7 +278,7 @@ pub struct WlSurface {
role: Cell<SurfaceRole>, role: Cell<SurfaceRole>,
pending: RefCell<Box<PendingState>>, pending: RefCell<Box<PendingState>>,
input_region: CloneCell<Option<Rc<Region>>>, input_region: CloneCell<Option<Rc<Region>>>,
opaque_region: Cell<Option<Rc<Region>>>, opaque_region: CloneCell<Option<Rc<Region>>>,
buffer_points: RefCell<BufferPoints>, buffer_points: RefCell<BufferPoints>,
pub buffer_points_norm: RefCell<SampleRect>, pub buffer_points_norm: RefCell<SampleRect>,
damage_matrix: Cell<DamageMatrix>, damage_matrix: Cell<DamageMatrix>,
@ -331,6 +331,7 @@ pub struct WlSurface {
clear_fifo_on_vblank: Cell<bool>, clear_fifo_on_vblank: Cell<bool>,
commit_timer: CloneCell<Option<Rc<WpCommitTimerV1>>>, commit_timer: CloneCell<Option<Rc<WpCommitTimerV1>>>,
before_latch_listener: EventListener<dyn BeforeLatchListener>, before_latch_listener: EventListener<dyn BeforeLatchListener>,
is_opaque: Cell<bool>,
} }
impl Debug for WlSurface { impl Debug for WlSurface {
@ -668,6 +669,7 @@ impl WlSurface {
clear_fifo_on_vblank: Default::default(), clear_fifo_on_vblank: Default::default(),
commit_timer: Default::default(), commit_timer: Default::default(),
before_latch_listener: EventListener::new(slf.clone()), 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 transform_changed = viewport_changed || scale_changed || buffer_transform_changed;
let mut buffer_abs_pos_size_changed = false;
if buffer_changed || transform_changed { if buffer_changed || transform_changed {
let mut buffer_points = self.buffer_points.borrow_mut(); let mut buffer_points = self.buffer_points.borrow_mut();
let mut buffer_points_norm = self.buffer_points_norm.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()); .set(buffer_abs_pos.with_size(width, height).unwrap());
max_surface_size = (width.max(old_width), height.max(old_height)); max_surface_size = (width.max(old_width), height.max(old_height));
damage_full = true; damage_full = true;
buffer_abs_pos_size_changed = true;
} }
} }
let has_new_frame_requests = pending.frame_request.is_not_empty(); 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); mem::swap(fbs.deref_mut(), &mut pending.presentation_feedback);
fbs.is_not_empty() fbs.is_not_empty()
}; };
let mut opaque_region_changed = false;
{ {
if let Some(region) = pending.input_region.take() { if let Some(region) = pending.input_region.take() {
self.input_region.set(region); self.input_region.set(region);
self.client.state.tree_changed(); self.client.state.tree_changed();
} }
if let Some(region) = pending.opaque_region.take() { if let Some(region) = pending.opaque_region.take() {
opaque_region_changed = true;
self.opaque_region.set(region); 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; let mut tearing_changed = false;
if let Some(tearing) = pending.tearing.take() { if let Some(tearing) = pending.tearing.take() {
if self.tearing.replace(tearing) != tearing { if self.tearing.replace(tearing) != tearing {
@ -1636,6 +1650,14 @@ impl WlSurface {
pub fn alpha(&self) -> Option<f32> { pub fn alpha(&self) -> Option<f32> {
self.alpha.get() self.alpha.get()
} }
pub fn opaque(&self) -> bool {
self.is_opaque.get()
}
pub fn opaque_region(&self) -> Option<Rc<Region>> {
self.opaque_region.get()
}
} }
object_base! { object_base! {

View file

@ -5,9 +5,10 @@ use {
format::{ARGB8888, Format, XRGB8888}, format::{ARGB8888, Format, XRGB8888},
gfx_api::{ gfx_api::{
AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FillRect, AcquireSync, AsyncShmGfxTexture, AsyncShmGfxTextureCallback, CopyTexture, FillRect,
FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, FramebufferRect, GfxApiOpt, GfxBlendBuffer, GfxContext, GfxError, GfxFormat,
GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture, GfxWriteModifier, GfxFramebuffer, GfxImage, GfxInternalFramebuffer, GfxStagingBuffer, GfxTexture,
PendingShmTransfer, ReleaseSync, ResetStatus, ShmGfxTexture, ShmMemory, SyncFile, GfxWriteModifier, PendingShmTransfer, ReleaseSync, ResetStatus, ShmGfxTexture,
ShmMemory, SyncFile,
}, },
rect::{Rect, Region}, rect::{Rect, Region},
theme::Color, theme::Color,
@ -37,6 +38,8 @@ enum TestGfxError {
ImportDmaBuf(#[source] AllocatorError), ImportDmaBuf(#[source] AllocatorError),
#[error("Could not access the client memory")] #[error("Could not access the client memory")]
AccessFailed(#[source] Box<dyn Error + Sync + Send>), AccessFailed(#[source] Box<dyn Error + Sync + Send>),
#[error("Text API does not support blend buffers")]
NoBlendBuffer,
} }
impl From<TestGfxError> for GfxError { impl From<TestGfxError> for GfxError {
@ -189,6 +192,14 @@ impl GfxContext for TestGfxCtx {
fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> { fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> {
None None
} }
fn acquire_blend_buffer(
&self,
_width: i32,
_height: i32,
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError> {
Err(GfxError(Box::new(TestGfxError::NoBlendBuffer)))
}
} }
enum TestGfxImage { enum TestGfxImage {
@ -383,6 +394,7 @@ impl GfxFramebuffer for TestGfxFb {
ops: &[GfxApiOpt], ops: &[GfxApiOpt],
clear: Option<&Color>, clear: Option<&Color>,
_region: &Region, _region: &Region,
_blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
let fb_points = |width: i32, height: i32, rect: &FramebufferRect| { let fb_points = |width: i32, height: i32, rect: &FramebufferRect| {
let points = rect.to_points(); let points = rect.to_points();
@ -441,11 +453,12 @@ impl GfxFramebuffer for TestGfxFb {
} }
}; };
let fill_rect = |f: &FillRect, staging: &mut [Color]| { let fill_rect = |f: &FillRect, staging: &mut [Color]| {
let color = f.effective_color();
let (x1, y1, x2, y2) = fb_points(width, height, &f.rect); let (x1, y1, x2, y2) = fb_points(width, height, &f.rect);
for y in y1..y2 { for y in y1..y2 {
for x in x1..x2 { for x in x1..x2 {
let dst = &mut staging[(y * width + x) as usize]; let dst = &mut staging[(y * width + x) as usize];
*dst = dst.and_then(&f.color); *dst = dst.and_then(&color);
} }
} }
}; };

View file

@ -225,6 +225,7 @@ impl GuiElement for Button {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -325,6 +326,7 @@ impl GuiElement for Label {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -634,6 +636,7 @@ impl WindowData {
ReleaseSync::Implicit, ReleaseSync::Implicit,
self.scale.get(), self.scale.get(),
Some(&Color::from_gray(0)), Some(&Color::from_gray(0)),
None,
&mut |r| { &mut |r| {
if let Some(content) = self.content.get() { if let Some(content) = self.content.get() {
content.render_at(r, 0.0, 0.0) content.render_at(r, 0.0, 0.0)

View file

@ -5,20 +5,26 @@ mod tests;
pub use region::{DamageQueue, RegionBuilder}; pub use region::{DamageQueue, RegionBuilder};
use { use {
jay_algorithms::rect::RectRaw, jay_algorithms::rect::{NoTag, RectRaw, Tag},
smallvec::SmallVec, smallvec::SmallVec,
std::fmt::{Debug, Formatter}, std::fmt::{Debug, Formatter},
}; };
#[derive(Copy, Clone, Eq, PartialEq, Default)] #[derive(Copy, Clone, Eq, PartialEq, Default)]
#[repr(transparent)] #[repr(transparent)]
pub struct Rect { pub struct Rect<T = NoTag>
raw: RectRaw, where
T: Tag,
{
raw: RectRaw<T>,
} }
#[derive(Clone, Eq, PartialEq, Debug, Default)] #[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct Region { pub struct Region<T = NoTag>
rects: SmallVec<[RectRaw; 1]>, where
T: Tag,
{
rects: SmallVec<[RectRaw<T>; 1]>,
extents: Rect, extents: Rect,
} }
@ -50,6 +56,23 @@ impl RectOverflow {
} }
} }
impl<T> Rect<T>
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 { impl Rect {
pub fn new_empty(x: i32, y: i32) -> Self { pub fn new_empty(x: i32, y: i32) -> Self {
Self { Self {
@ -58,6 +81,7 @@ impl Rect {
y1: y, y1: y,
x2: x, x2: x,
y2: y, y2: y,
tag: NoTag,
}, },
} }
} }
@ -67,7 +91,13 @@ impl Rect {
return None; return None;
} }
Some(Self { 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() 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 { fn new_unchecked_danger(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
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), y1: self.raw.y1.min(other.raw.y1),
x2: self.raw.x2.max(other.raw.x2), x2: self.raw.x2.max(other.raw.x2),
y2: self.raw.y2.max(other.raw.y2), 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> {
Self::new_sized(self.raw.x1, self.raw.y1, width, height)
}
pub fn with_tag(&self, tag: u32) -> Rect<u32> {
Rect {
raw: RectRaw {
x1: self.raw.x1,
y1: self.raw.y1,
x2: self.raw.x2,
y2: self.raw.y2,
tag,
},
}
}
}
impl<T> Rect<T>
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 && 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 { 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 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)] #[expect(dead_code)]
pub fn contains_rect(&self, rect: &Self) -> bool { pub fn contains_rect<U>(&self, rect: &Rect<U>) -> bool
where
U: Tag,
{
self.raw.x1 <= rect.raw.x1 self.raw.x1 <= rect.raw.x1
&& self.raw.y1 <= rect.raw.x1 && self.raw.y1 <= rect.raw.x1
&& rect.raw.x2 <= self.raw.x2 && rect.raw.x2 <= self.raw.x2
&& rect.raw.y2 <= self.raw.y2 && rect.raw.y2 <= self.raw.y2
} }
pub fn get_overflow(&self, child: &Self) -> RectOverflow { pub fn get_overflow<U>(&self, child: &Rect<U>) -> RectOverflow
where
U: Tag,
{
RectOverflow { RectOverflow {
left: self.raw.x1 - child.raw.x1, left: self.raw.x1 - child.raw.x1,
right: child.raw.x2 - self.raw.x2, right: child.raw.x2 - self.raw.x2,
@ -177,6 +260,7 @@ impl Rect {
y1: 0, y1: 0,
x2: self.raw.x2 - self.raw.x1, x2: self.raw.x2 - self.raw.x1,
y2: self.raw.y2 - self.raw.y1, y2: self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
}, },
} }
} }
@ -188,6 +272,7 @@ impl Rect {
y1: self.raw.y1.saturating_add(dy), y1: self.raw.y1.saturating_add(dy),
x2: self.raw.x2.saturating_add(dx), x2: self.raw.x2.saturating_add(dx),
y2: self.raw.y2.saturating_add(dy), y2: self.raw.y2.saturating_add(dy),
tag: self.raw.tag,
}, },
} }
} }
@ -199,14 +284,11 @@ impl Rect {
y1, y1,
x2: x1 + self.raw.x2 - self.raw.x1, x2: x1 + self.raw.x2 - self.raw.x1,
y2: y1 + self.raw.y2 - self.raw.y1, y2: y1 + self.raw.y2 - self.raw.y1,
tag: self.raw.tag,
}, },
} }
} }
pub fn with_size(&self, width: i32, height: i32) -> Option<Self> {
Self::new_sized(self.raw.x1, self.raw.y1, width, height)
}
pub fn translate(&self, x: i32, y: i32) -> (i32, i32) { pub fn translate(&self, x: i32, y: i32) -> (i32, i32) {
(x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1)) (x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1))
} }
@ -253,4 +335,8 @@ impl Rect {
self.raw.y1 + self.height() / 2, self.raw.y1 + self.height() / 2,
) )
} }
pub fn tag(&self) -> T {
self.raw.tag
}
} }

View file

@ -7,11 +7,15 @@ use {
}, },
}, },
jay_algorithms::rect::{ jay_algorithms::rect::{
RectRaw, RectRaw, Tag,
region::{extents, rects_to_bands, subtract, union}, region::{
extents, intersect, intersect_tagged, rects_to_bands, rects_to_bands_tagged, subtract,
union,
},
}, },
smallvec::SmallVec, smallvec::SmallVec,
std::{ std::{
borrow::Cow,
cell::UnsafeCell, cell::UnsafeCell,
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
mem, mem,
@ -29,19 +33,6 @@ thread_local! {
} }
impl Region { impl Region {
pub fn new(rect: Rect) -> Rc<Self> {
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<Self> { pub fn empty() -> Rc<Self> {
EMPTY.with(|e| e.clone()) 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<u32> {
pub fn from_rects_tagged(rects: &[Rect<u32>]) -> 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<u32>], &[RectRaw<u32>]>(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<T> Region<T>
where
T: Tag,
{
pub fn new(rect: Rect<T>) -> Rc<Self> {
Rc::new(Self::new2(rect))
}
pub fn new2(rect: Rect<T>) -> Self {
let mut rects = SmallVec::new();
rects.push(rect.raw);
Self {
rects,
extents: rect.untag(),
}
}
#[cfg_attr(not(feature = "it"), expect(dead_code))] #[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn extents(&self) -> Rect { pub fn extents(&self) -> Rect {
self.extents self.extents
} }
pub fn rects(&self) -> &[Rect] { pub fn rects(&self) -> &[Rect<T>] {
unsafe { mem::transmute::<&[RectRaw], &[Rect]>(&self.rects[..]) } unsafe { mem::transmute::<&[RectRaw<T>], &[Rect<T>]>(&self.rects[..]) }
} }
pub fn contains(&self, x: i32, y: i32) -> bool { pub fn contains(&self, x: i32, y: i32) -> bool {
@ -116,13 +186,41 @@ impl Region {
} }
false 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<T>) -> Rect<T>) -> 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 { impl<T> Deref for Region<T>
type Target = [Rect]; where
T: Tag,
{
type Target = [Rect<T>];
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { mem::transmute::<&[RectRaw], _>(&self.rects) } unsafe { mem::transmute::<&[RectRaw<T>], _>(&self.rects) }
} }
} }

View file

@ -1,6 +1,6 @@
use { use {
crate::rect::{Rect, Region}, crate::rect::{Rect, Region},
jay_algorithms::rect::RectRaw, jay_algorithms::rect::{NoTag, RectRaw},
}; };
#[test] #[test]
@ -43,25 +43,29 @@ fn subtract1() {
x1: 0, x1: 0,
y1: 0, y1: 0,
x2: 20, x2: 20,
y2: 5 y2: 5,
tag: NoTag,
}, },
RectRaw { RectRaw {
x1: 0, x1: 0,
y1: 5, y1: 5,
x2: 5, x2: 5,
y2: 15 y2: 15,
tag: NoTag,
}, },
RectRaw { RectRaw {
x1: 15, x1: 15,
y1: 5, y1: 5,
x2: 20, x2: 20,
y2: 15 y2: 15,
tag: NoTag,
}, },
RectRaw { RectRaw {
x1: 0, x1: 0,
y1: 15, y1: 15,
x2: 20, x2: 20,
y2: 20 y2: 20,
tag: NoTag,
}, },
] ]
); );
@ -83,19 +87,22 @@ fn rects_to_bands() {
x1: 0, x1: 0,
y1: 0, y1: 0,
x2: 30, x2: 30,
y2: 5 y2: 5,
tag: NoTag,
}, },
RectRaw { RectRaw {
x1: 0, x1: 0,
y1: 5, y1: 5,
x2: 50, x2: 50,
y2: 10 y2: 10,
tag: NoTag,
}, },
RectRaw { RectRaw {
x1: 30, x1: 30,
y1: 10, y1: 10,
x2: 50, x2: 50,
y2: 15 y2: 15,
tag: NoTag,
}, },
] ]
); );
@ -111,3 +118,572 @@ fn rects_to_bands2() {
// println!("{:#?}", r.rects); // println!("{:#?}", r.rects);
assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]); 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),
],
);
}

View file

@ -89,7 +89,7 @@ impl Renderer<'_> {
let bar_bg = self.base.scale_rect(bar_bg); let bar_bg = self.base.scale_rect(bar_bg);
let c = theme.colors.bar_background.get(); let c = theme.colors.bar_background.get();
self.base 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(); let rd = output.render_data.borrow_mut();
if let Some(aw) = &rd.active_workspace { if let Some(aw) = &rd.active_workspace {
let c = match aw.captured { let c = match aw.captured {
@ -124,6 +124,7 @@ impl Renderer<'_> {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
if let Some(status) = &rd.status { if let Some(status) = &rd.status {
@ -141,6 +142,7 @@ impl Renderer<'_> {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -219,6 +221,7 @@ impl Renderer<'_> {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -264,6 +267,7 @@ impl Renderer<'_> {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -336,7 +340,8 @@ impl Renderer<'_> {
}; };
let color = self.state.theme.colors.highlight.get(); let color = self.state.theme.colors.highlight.get();
self.base.ops.push(GfxApiOpt::Sync); 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) { pub fn render_highlight(&mut self, rect: &Rect) {
@ -378,7 +383,6 @@ impl Renderer<'_> {
} else { } else {
size = self.base.scale_point(size.0, size.1); size = self.base.scale_point(size.0, size.1);
} }
let alpha = surface.alpha();
if let Some(children) = children.deref() { if let Some(children) = children.deref() {
macro_rules! render { macro_rules! render {
($children:expr) => { ($children:expr) => {
@ -400,10 +404,10 @@ impl Renderer<'_> {
}; };
} }
render!(&children.below); 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); render!(&children.above);
} else { } 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, &mut self,
surface: &WlSurface, surface: &WlSurface,
buffer: &Rc<SurfaceBuffer>, buffer: &Rc<SurfaceBuffer>,
alpha: Option<f32>,
x: i32, x: i32,
y: i32, y: i32,
tpoints: SampleRect, tpoints: SampleRect,
tsize: (i32, i32), tsize: (i32, i32),
bounds: Option<&Rect>, bounds: Option<&Rect>,
) { ) {
let alpha = surface.alpha();
if let Some(tex) = buffer.buffer.get_texture(surface) { 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( self.base.render_texture(
&tex, &tex,
alpha, alpha,
@ -431,6 +439,7 @@ impl Renderer<'_> {
Some(buffer.clone()), Some(buffer.clone()),
AcquireSync::Unnecessary, AcquireSync::Unnecessary,
buffer.release_sync, buffer.release_sync,
opaque,
); );
} else if let Some(color) = &buffer.buffer.color { } else if let Some(color) = &buffer.buffer.color {
if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) { if let Some(rect) = Rect::new_sized(x, y, tsize.0, tsize.1) {
@ -440,11 +449,7 @@ impl Renderer<'_> {
}; };
if !rect.is_empty() { if !rect.is_empty() {
self.base.ops.push(GfxApiOpt::Sync); self.base.ops.push(GfxApiOpt::Sync);
let mut color = *color; self.base.fill_scaled_boxes(&[rect], color, alpha);
if let Some(alpha) = alpha {
color = color * alpha;
}
self.base.fill_scaled_boxes(&[rect], &color);
} }
} }
} else { } else {
@ -499,6 +504,7 @@ impl Renderer<'_> {
None, None,
AcquireSync::None, AcquireSync::None,
ReleaseSync::None, ReleaseSync::None,
false,
); );
} }
} }
@ -517,4 +523,23 @@ impl Renderer<'_> {
let (dx, dy) = surface.surface.extents.get().position(); let (dx, dy) = surface.surface.extents.get().position();
self.render_surface(&surface.surface, x - dx, y - dy, None); 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))
}
} }

View file

@ -64,19 +64,27 @@ impl RendererBase<'_> {
rect rect
} }
pub fn fill_scaled_boxes(&mut self, boxes: &[Rect], color: &Color) { pub fn fill_scaled_boxes(&mut self, boxes: &[Rect], color: &Color, alpha: Option<f32>) {
self.fill_boxes3(boxes, color, 0, 0, true); self.fill_boxes3(boxes, color, alpha, 0, 0, true);
} }
pub fn fill_boxes(&mut self, boxes: &[Rect], color: &Color) { 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) { 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<f32>,
dx: i32,
dy: i32,
scaled: bool,
) {
if boxes.is_empty() || *color == Color::TRANSPARENT { if boxes.is_empty() || *color == Color::TRANSPARENT {
return; return;
} }
@ -97,6 +105,7 @@ impl RendererBase<'_> {
self.fb_height, self.fb_height,
), ),
color: *color, color: *color,
alpha,
})); }));
} }
} }
@ -129,6 +138,7 @@ impl RendererBase<'_> {
self.fb_height, self.fb_height,
), ),
color: *color, color: *color,
alpha: None,
})); }));
} }
} }
@ -146,6 +156,7 @@ impl RendererBase<'_> {
buffer_resv: Option<Rc<dyn BufferResv>>, buffer_resv: Option<Rc<dyn BufferResv>>,
acquire_sync: AcquireSync, acquire_sync: AcquireSync,
release_sync: ReleaseSync, release_sync: ReleaseSync,
opaque: bool,
) { ) {
let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity);
@ -188,6 +199,7 @@ impl RendererBase<'_> {
buffer_resv, buffer_resv,
acquire_sync, acquire_sync,
release_sync, release_sync,
opaque,
})); }));
} }
} }

View file

@ -88,6 +88,7 @@ pub fn take_screenshot(
false, false,
false, false,
Transform::None, Transform::None,
None,
)?; )?;
let drm = match allocator.drm() { let drm = match allocator.drm() {
Some(drm) => Some(drm.dup_render()?.fd().clone()), Some(drm) => Some(drm.dup_render()?.fd().clone()),

View file

@ -27,8 +27,8 @@ use {
forker::ForkerProxy, forker::ForkerProxy,
format::Format, format::Format,
gfx_api::{ gfx_api::{
AcquireSync, BufferResv, GfxContext, GfxError, GfxFramebuffer, GfxTexture, AcquireSync, BufferResv, GfxBlendBuffer, GfxContext, GfxError, GfxFramebuffer,
PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, SampleRect, SyncFile, GfxTexture, PendingShmTransfer, ReleaseSync, STAGING_DOWNLOAD, SampleRect, SyncFile,
}, },
gfx_apis::create_gfx_context, gfx_apis::create_gfx_context,
globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal}, globals::{Globals, GlobalsError, RemovableWaylandGlobal, WaylandGlobal},
@ -979,6 +979,7 @@ impl State {
release_sync: ReleaseSync, release_sync: ReleaseSync,
tex: &Rc<dyn GfxTexture>, tex: &Rc<dyn GfxTexture>,
render_hw_cursor: bool, render_hw_cursor: bool,
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> { ) -> Result<Option<SyncFile>, GfxError> {
let sync_file = fb.render_output( let sync_file = fb.render_output(
acquire_sync, acquire_sync,
@ -989,6 +990,7 @@ impl State {
output.global.persistent.scale.get(), output.global.persistent.scale.get(),
render_hw_cursor, render_hw_cursor,
true, true,
blend_buffer,
)?; )?;
output.latched(false); output.latched(false);
output.perform_screencopies( output.perform_screencopies(
@ -1046,6 +1048,7 @@ impl State {
resv.cloned(), resv.cloned(),
acquire_sync.clone(), acquire_sync.clone(),
release_sync, release_sync,
false,
); );
if render_hardware_cursors { if render_hardware_cursors {
if let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() { if let Some(cursor_user_group) = self.cursor_user_group_hardware_cursor.get() {
@ -1064,6 +1067,7 @@ impl State {
target_release_sync, target_release_sync,
&ops, &ops,
Some(&Color::SOLID_BLACK), Some(&Color::SOLID_BLACK),
None,
) )
} }

View file

@ -115,24 +115,26 @@ impl Color {
[to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)] [to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)]
} }
pub fn to_array_srgb(self) -> [f32; 4] { pub fn to_array_srgb(self, alpha: Option<f32>) -> [f32; 4] {
[self.r, self.g, self.b, self.a] 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, alpha: Option<f32>) -> [f32; 4] {
pub fn to_array_linear(self) -> [f32; 4] {
fn to_linear(srgb: f32) -> f32 { fn to_linear(srgb: f32) -> f32 {
if srgb <= 0.04045 { if srgb <= 0.04045 {
srgb / 12.92 srgb / 12.92
} else { } 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.r / a1) * a2,
to_linear(self.g), to_linear(self.g / a1) * a2,
to_linear(self.b), to_linear(self.b / a1) * a2,
self.a, a2,
] ]
} }