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(
clippy::mem_replace_with_default,
clippy::comparison_chain,
clippy::collapsible_else_if,
clippy::needless_lifetimes
)]

View file

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

View file

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

View file

@ -1,6 +1,14 @@
# Unreleased
- Various bugfixes.
- The vulkan renderer now only renders in damaged areas. This has exposed several places
where the damage tracking was incorrect. There might be additional damage tracking bugs.
Such bugs manifest through flickering or through areas getting stuck with an old image.
If you encounter such an issue, please open a bug.
- The vulkan renderer now performs blending in linear space. A green window with 50%
opacity on top of a red window will produce a perfectly yellow image instead of a muddy
yellow. The blend buffer is only used for those areas of the screen where blending is
observable. This should have no impact on performance in the common case.
# 1.9.1 (2025-02-13)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,8 +4,8 @@ use {
cpu_worker::CpuWorker,
format::{Format, XRGB8888},
gfx_api::{
AsyncShmGfxTexture, BufferResvUser, GfxContext, GfxError, GfxFormat, GfxFramebuffer,
GfxImage, GfxInternalFramebuffer, ResetStatus, ShmGfxTexture,
AsyncShmGfxTexture, BufferResvUser, GfxBlendBuffer, GfxContext, GfxError, GfxFormat,
GfxFramebuffer, GfxImage, GfxInternalFramebuffer, ResetStatus, ShmGfxTexture,
},
gfx_apis::gl::{
GfxGlState, RenderError, Texture,
@ -339,4 +339,12 @@ impl GfxContext for GlRenderContext {
fn sync_obj_ctx(&self) -> Option<&Rc<SyncObjCtx>> {
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::{
format::Format,
gfx_api::{
AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxError, GfxFramebuffer,
GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer, ReleaseSync, ShmMemory,
SyncFile,
AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxBlendBuffer, GfxError,
GfxFramebuffer, GfxInternalFramebuffer, GfxStagingBuffer, PendingShmTransfer,
ReleaseSync, ShmMemory, SyncFile,
},
gfx_apis::gl::{
RenderError,
@ -106,6 +106,7 @@ impl GfxFramebuffer for Framebuffer {
ops: &[GfxApiOpt],
clear: Option<&Color>,
_region: &Region,
_blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
) -> Result<Option<SyncFile>, GfxError> {
(*self)
.render(acquire_sync, ops, clear)

View file

@ -1,4 +1,5 @@
mod allocator;
mod blend_buffer;
mod bo_allocator;
mod command;
mod descriptor;
@ -24,9 +25,9 @@ use {
cpu_worker::{CpuWorker, jobs::read_write::ReadWriteJobError},
format::Format,
gfx_api::{
AsyncShmGfxTexture, GfxContext, GfxError, GfxFormat, GfxImage, GfxInternalFramebuffer,
GfxStagingBuffer, ResetStatus, STAGING_DOWNLOAD, STAGING_UPLOAD, ShmGfxTexture,
StagingBufferUsecase,
AsyncShmGfxTexture, GfxBlendBuffer, GfxContext, GfxError, GfxFormat, GfxImage,
GfxInternalFramebuffer, GfxStagingBuffer, ResetStatus, STAGING_DOWNLOAD,
STAGING_UPLOAD, ShmGfxTexture, StagingBufferUsecase,
},
gfx_apis::vulkan::{
image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer,
@ -204,6 +205,8 @@ pub enum VulkanError {
UndefinedContents,
#[error("The framebuffer is being used by the transfer queue")]
BusyInTransfer,
#[error("Driver does not support descriptor buffers")]
NoDescriptorBuffer,
}
impl From<VulkanError> for GfxError {
@ -270,6 +273,7 @@ impl GfxContext for Context {
let old = old.into_texture().into_vk(&self.0.device.device);
let shm = match &old.ty {
VulkanImageMemory::DmaBuf(_) => unreachable!(),
VulkanImageMemory::Blend(_) => unreachable!(),
VulkanImageMemory::Internal(shm) => shm,
};
if old.width as i32 == width
@ -350,6 +354,15 @@ impl GfxContext for Context {
.device
.create_staging_shell(size as u64, upload, download)
}
fn acquire_blend_buffer(
&self,
width: i32,
height: i32,
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError> {
let buffer = self.0.acquire_blend_buffer(width, height)?;
Ok(buffer)
}
}
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 {
crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice, sampler::VulkanSampler},
arrayvec::ArrayVec,
ash::vk::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags,
DescriptorSetLayoutCreateInfo, DescriptorType, DeviceSize, ShaderStageFlags,
ash::{
ext::descriptor_buffer,
vk::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags,
DescriptorSetLayoutCreateInfo, DescriptorType, DeviceSize, ShaderStageFlags,
},
},
std::{rc::Rc, slice},
};
@ -14,7 +17,6 @@ pub(super) struct VulkanDescriptorSetLayout {
pub(super) size: DeviceSize,
pub(super) offsets: ArrayVec<DeviceSize, 1>,
pub(super) _sampler: Option<Rc<VulkanSampler>>,
pub(super) has_sampler: bool,
}
impl Drop for VulkanDescriptorSetLayout {
@ -28,7 +30,7 @@ impl Drop for VulkanDescriptorSetLayout {
}
impl VulkanDevice {
pub(super) fn create_descriptor_set_layout(
pub(super) fn create_tex_descriptor_set_layout(
self: &Rc<Self>,
sampler: &Rc<VulkanSampler>,
) -> Result<Rc<VulkanDescriptorSetLayout>, VulkanError> {
@ -52,9 +54,7 @@ impl VulkanDevice {
let mut size = 0;
let mut offsets = ArrayVec::new();
if let Some(db) = &self.descriptor_buffer {
size = unsafe { db.get_descriptor_set_layout_size(layout) };
size =
(size + self.descriptor_buffer_offset_mask) & !self.descriptor_buffer_offset_mask;
size = self.get_descriptor_set_size(db, layout);
unsafe {
offsets.push(db.get_descriptor_set_layout_binding_offset(layout, 0));
}
@ -65,7 +65,43 @@ impl VulkanDevice {
size,
offsets,
_sampler: Some(sampler.clone()),
has_sampler: true,
}))
}
pub(super) fn create_out_descriptor_set_layout(
self: &Rc<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,
}
#[derive(Default)]
pub struct VulkanDescriptorBufferWriter {
set_size: usize,
buffer: Vec<u8>,
}
@ -49,13 +49,13 @@ impl VulkanDescriptorBufferCache {
pub fn new(
device: &Rc<VulkanDevice>,
allocator: &Rc<VulkanAllocator>,
layout: &VulkanDescriptorSetLayout,
has_sampler: bool,
) -> Self {
Self {
device: device.clone(),
allocator: allocator.clone(),
buffers: Default::default(),
has_sampler: layout.has_sampler,
has_sampler,
}
}
@ -164,13 +164,6 @@ impl Drop for VulkanDescriptorBufferUnused {
}
impl VulkanDescriptorBufferWriter {
pub fn new(layout: &VulkanDescriptorSetLayout) -> Self {
Self {
set_size: layout.size as usize,
buffer: Default::default(),
}
}
pub fn clear(&mut self) {
self.buffer.clear();
}
@ -179,10 +172,13 @@ impl VulkanDescriptorBufferWriter {
self.buffer.len() as DeviceSize
}
pub fn add_set(&mut self) -> VulkanDescriptorBufferSetWriter<'_> {
pub fn add_set(
&mut self,
layout: &VulkanDescriptorSetLayout,
) -> VulkanDescriptorBufferSetWriter<'_> {
let buffer = &mut self.buffer;
let lo = buffer.len();
buffer.resize(lo + self.set_size, 0);
buffer.resize(lo + layout.size as usize, 0);
VulkanDescriptorBufferSetWriter {
set: &mut buffer[lo..],
}

View file

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

View file

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

View file

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

View file

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

View file

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

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),
bridge: None,
shader_read_only_optimal_descriptor: self.sampler_read_only_descriptor(view),
sampled_image_descriptor: Box::new([]),
descriptor_buffer_version: Cell::new(0),
descriptor_buffer_offset: Cell::new(0),
execution_version: Cell::new(0),
});
let shm = match &img.ty {
VulkanImageMemory::DmaBuf(_) => unreachable!(),
VulkanImageMemory::Blend(_) => unreachable!(),
VulkanImageMemory::Internal(s) => s,
};
if data.is_not_empty() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,11 +7,15 @@ use {
},
},
jay_algorithms::rect::{
RectRaw,
region::{extents, rects_to_bands, subtract, union},
RectRaw, Tag,
region::{
extents, intersect, intersect_tagged, rects_to_bands, rects_to_bands_tagged, subtract,
union,
},
},
smallvec::SmallVec,
std::{
borrow::Cow,
cell::UnsafeCell,
fmt::{Debug, Formatter},
mem,
@ -29,19 +33,6 @@ thread_local! {
}
impl Region {
pub fn new(rect: Rect) -> Rc<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> {
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))]
pub fn extents(&self) -> Rect {
self.extents
}
pub fn rects(&self) -> &[Rect] {
unsafe { mem::transmute::<&[RectRaw], &[Rect]>(&self.rects[..]) }
pub fn rects(&self) -> &[Rect<T>] {
unsafe { mem::transmute::<&[RectRaw<T>], &[Rect<T>]>(&self.rects[..]) }
}
pub fn contains(&self, x: i32, y: i32) -> bool {
@ -116,13 +186,41 @@ impl Region {
}
false
}
pub fn contains_rect(&self, rect: &Rect) -> bool {
self.contains_rect2(rect, |r| *r)
}
pub fn contains_rect2(&self, rect: &Rect, map: impl Fn(&Rect<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 {
type Target = [Rect];
impl<T> Deref for Region<T>
where
T: Tag,
{
type Target = [Rect<T>];
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 {
crate::rect::{Rect, Region},
jay_algorithms::rect::RectRaw,
jay_algorithms::rect::{NoTag, RectRaw},
};
#[test]
@ -43,25 +43,29 @@ fn subtract1() {
x1: 0,
y1: 0,
x2: 20,
y2: 5
y2: 5,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 5,
x2: 5,
y2: 15
y2: 15,
tag: NoTag,
},
RectRaw {
x1: 15,
y1: 5,
x2: 20,
y2: 15
y2: 15,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 15,
x2: 20,
y2: 20
y2: 20,
tag: NoTag,
},
]
);
@ -83,19 +87,22 @@ fn rects_to_bands() {
x1: 0,
y1: 0,
x2: 30,
y2: 5
y2: 5,
tag: NoTag,
},
RectRaw {
x1: 0,
y1: 5,
x2: 50,
y2: 10
y2: 10,
tag: NoTag,
},
RectRaw {
x1: 30,
y1: 10,
x2: 50,
y2: 15
y2: 15,
tag: NoTag,
},
]
);
@ -111,3 +118,572 @@ fn rects_to_bands2() {
// println!("{:#?}", r.rects);
assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]);
}
#[test]
fn rects_to_bands_tagged1() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 1,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 150,
tag: 1,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 150,
tag: 1,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged2() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 1),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 0),
Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 2),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 1,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 150,
tag: 1,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 150,
tag: 1,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged3() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 2),
Rect::new_unchecked_danger_tagged(50, 50, 150, 150, 1),
Rect::new_unchecked_danger_tagged(60, 60, 140, 140, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 50,
tag: 0,
},
RectRaw {
x1: 0,
y1: 50,
x2: 50,
y2: 60,
tag: 0,
},
RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 60,
tag: 1,
},
RectRaw {
x1: 150,
y1: 50,
x2: 200,
y2: 60,
tag: 0,
},
RectRaw {
x1: 0,
y1: 60,
x2: 50,
y2: 140,
tag: 0,
},
RectRaw {
x1: 50,
y1: 60,
x2: 60,
y2: 140,
tag: 1,
},
RectRaw {
x1: 60,
y1: 60,
x2: 140,
y2: 140,
tag: 0,
},
RectRaw {
x1: 140,
y1: 60,
x2: 150,
y2: 140,
tag: 1,
},
RectRaw {
x1: 150,
y1: 60,
x2: 200,
y2: 140,
tag: 0,
},
RectRaw {
x1: 0,
y1: 140,
x2: 50,
y2: 150,
tag: 0,
},
RectRaw {
x1: 50,
y1: 140,
x2: 150,
y2: 150,
tag: 1,
},
RectRaw {
x1: 150,
y1: 140,
x2: 200,
y2: 150,
tag: 0,
},
RectRaw {
x1: 0,
y1: 150,
x2: 200,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged4() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 200, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
RectRaw {
x1: 100,
y1: 100,
x2: 200,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged5() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged6() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 300, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 300,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged7() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 200, 100, 0),
Rect::new_unchecked_danger_tagged(100, 0, 300, 200, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
RectRaw {
x1: 200,
y1: 0,
x2: 300,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 100,
x2: 300,
y2: 200,
tag: 1,
},
],
);
}
#[test]
fn rects_to_bands_tagged8() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 100,
y1: 0,
x2: 200,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged9() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(100, 0, 200, 100, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 200,
y2: 100,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged10() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 1),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 200,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged11() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11)];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},],
);
}
#[test]
fn rects_to_bands_tagged12() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 11),
Rect::new_unchecked_danger_tagged(200, 0, 300, 100, 10),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 200,
y1: 0,
x2: 300,
y2: 100,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged13() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 100, 100, 200, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[
RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 1,
},
RectRaw {
x1: 0,
y1: 100,
x2: 100,
y2: 200,
tag: 0,
},
],
);
}
#[test]
fn rects_to_bands_tagged14() {
let rects = [
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 1),
Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0),
];
let r = Region::from_rects_tagged(&rects[..]);
assert_eq!(
&r.rects[..],
&[RectRaw {
x1: 0,
y1: 0,
x2: 100,
y2: 100,
tag: 0,
},],
);
}
#[test]
fn intersect1() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 100, 100, 0)];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [Rect::new_unchecked_danger(100, 100, 200, 200)];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(&r3.rects[..], &[],);
}
#[test]
fn intersect2() {
let rects = [Rect::new_unchecked_danger_tagged(0, 0, 200, 200, 0)];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [Rect::new_unchecked_danger(50, 50, 150, 150)];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(
&r3.rects[..],
&[RectRaw {
x1: 50,
y1: 50,
x2: 150,
y2: 150,
tag: 0,
}],
);
}
#[test]
fn intersect3() {
macro_rules! t {
($l:expr, $r:expr, $t:expr) => {
Rect::new_unchecked_danger_tagged($l, 0, $r, 1, $t)
};
}
macro_rules! u {
($l:expr, $r:expr) => {
Rect::new_unchecked_danger($l, 0, $r, 1)
};
}
macro_rules! r {
($l:expr, $r:expr, $t:expr) => {
RectRaw {
x1: $l,
y1: 0,
x2: $r,
y2: 1,
tag: $t,
}
};
}
let rects = [
t!(0, 100, 0),
t!(110, 130, 1),
t!(140, 160, 2),
t!(170, 180, 0),
];
let r1 = Region::from_rects_tagged(&rects[..]);
let rects = [
u!(10, 20),
u!(50, 60),
u!(70, 100),
u!(120, 150),
u!(170, 180),
];
let r2 = Region::from_rects2(&rects[..]);
let r3 = r1.intersect_tagged(&r2);
assert_eq!(
&r3.rects[..],
&[
r!(10, 20, 0),
r!(50, 60, 0),
r!(70, 100, 0),
r!(120, 130, 1),
r!(140, 150, 0),
r!(170, 180, 0),
],
);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -115,24 +115,26 @@ impl Color {
[to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)]
}
pub fn to_array_srgb(self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
pub fn to_array_srgb(self, alpha: Option<f32>) -> [f32; 4] {
let a = alpha.unwrap_or(1.0);
[self.r * a, self.g * a, self.b * a, self.a * a]
}
#[expect(dead_code)]
pub fn to_array_linear(self) -> [f32; 4] {
pub fn to_array_linear(self, alpha: Option<f32>) -> [f32; 4] {
fn to_linear(srgb: f32) -> f32 {
if srgb <= 0.04045 {
srgb / 12.92
} else {
(srgb + 0.055 / 1.055).powf(2.4)
((srgb + 0.055) / 1.055).powf(2.4)
}
}
let a1 = if self.a == 0.0 { 1.0 } else { self.a };
let a2 = self.a * alpha.unwrap_or(1.0);
[
to_linear(self.r),
to_linear(self.g),
to_linear(self.b),
self.a,
to_linear(self.r / a1) * a2,
to_linear(self.g / a1) * a2,
to_linear(self.b / a1) * a2,
a2,
]
}