atagen

monolith

public
Star0
Watch0

Commit b03e3b32603a

tarball

atagen <boss@atagen.co>2026-10-19 07:56 UTC

4 files changed+77-3
render-vk: honour Y_INVERT on imported dmabufs

Clients that render with their origin at the bottom flag the dmabuf as y_inverted, and the renderer sampled such buffers upside down. ImageInner keeps the flag from import and draw_texture mirrors the texture coordinates around the texture.
Modified file: crates/render-vk/src/dmabuf.rs+1 additions
@@ -271,6 +271,7 @@ pub(crate) fn import(
271271 size.1,
272272 usage,
273273 true,
274+ dmabuf.y_inverted(),
274275 )
275276 }
276277
Modified file: crates/render-vk/src/frame.rs+6 additions−1 deletions
@@ -179,7 +179,12 @@ impl<'frame, 'buffer> VulkanFrame<'frame, 'buffer> {
179179 if scissors.is_empty() {
180180 return Ok(());
181181 }
182- let uvs = texture_uvs(src, dst.size, texture.size(), src_transform);
182+ let mut uvs = texture_uvs(src, dst.size, texture.size(), src_transform);
183+ if texture.0.y_inverted {
184+ for uv in &mut uvs {
185+ uv[1] = 1.0 - uv[1];
186+ }
187+ }
183188 let (fb, uv) = to_framebuffer(dst, uvs, self.area, self.transform);
184189 let (clip, radii, ring) = self.clip_push();
185190 let push = Push {
Modified file: crates/render-vk/src/image.rs+5 additions−1 deletions
@@ -26,6 +26,8 @@ pub(crate) struct ImageInner {
2626 pub width: u32,
2727 pub height: u32,
2828 pub usage: vk::ImageUsageFlags,
29+ /// The rows are stored bottom to top, as a dmabuf flagged `Y_INVERT`.
30+ pub y_inverted: bool,
2931 pub layout: Mutex<vk::ImageLayout>,
3032 /// Shared with other devices or processes through a dmabuf. Every use
3133 /// acquires the image from the foreign queue family and releases it after.
@@ -118,7 +120,7 @@ impl ImageInner {
118120 Memory::Imported(m) => (*m, 0),
119121 };
120122 Self::finish(
121- shared, image, memory, mem, offset, fourcc, width, height, usage, false,
123+ shared, image, memory, mem, offset, fourcc, width, height, usage, false, false,
122124 )
123125 }
124126
@@ -136,6 +138,7 @@ impl ImageInner {
136138 height: u32,
137139 usage: vk::ImageUsageFlags,
138140 external: bool,
141+ y_inverted: bool,
139142 ) -> Result<Arc<Self>, Error> {
140143 let fmt = vk_format(fourcc).ok_or(Error::UnsupportedFormat(fourcc))?;
141144 let d = &shared.device.device;
@@ -149,6 +152,7 @@ impl ImageInner {
149152 width,
150153 height,
151154 usage,
155+ y_inverted,
152156 layout: Mutex::new(if external {
153157 vk::ImageLayout::GENERAL
154158 } else {
Modified file: crates/render-vk/tests/dmabuf.rs+65 additions−1 deletions
@@ -2,7 +2,7 @@
22 //! without dmabuf support, such as lavapipe inside the nix sandbox.
33
44 use monolith_render_vk::VulkanRenderer;
5-use smithay::backend::allocator::dmabuf::Dmabuf;
5+use smithay::backend::allocator::dmabuf::{Dmabuf, DmabufFlags};
66 use smithay::backend::allocator::{Allocator, Fourcc, Modifier};
77 use smithay::backend::renderer::{
88 Bind, Color32F, ExportMem, Frame, ImportDma, Offscreen, Renderer,
@@ -102,3 +102,67 @@ fn frame_fences_export_as_sync_files() {
102102 sync.wait().unwrap();
103103 assert!(sync.is_reached());
104104 }
105+
106+#[test]
107+fn y_inverted_dmabuf_is_sampled_upside_down() {
108+ let Some(mut r) = renderer() else { return };
109+ let mut buf = dmabuf(&r);
110+ {
111+ let mut target = r.bind(&mut buf).unwrap();
112+ let mut frame = r
113+ .render(&mut target, (SIZE, SIZE).into(), Transform::Normal)
114+ .unwrap();
115+ let half = SIZE / 2;
116+ frame
117+ .clear(
118+ Color32F::from([1.0, 0.0, 0.0, 1.0]),
119+ &[Rectangle::from_size((SIZE, SIZE).into())],
120+ )
121+ .unwrap();
122+ frame
123+ .clear(
124+ Color32F::from([0.0, 1.0, 0.0, 1.0]),
125+ &[Rectangle::new((0, half).into(), (SIZE, half).into())],
126+ )
127+ .unwrap();
128+ frame.finish().unwrap().wait().unwrap();
129+ }
130+ let mut inverted = Dmabuf::builder_from_buffer(&buf, DmabufFlags::Y_INVERT);
131+ for ((fd, offset), stride) in buf.handles().zip(buf.offsets()).zip(buf.strides()) {
132+ inverted.add_plane(fd.try_clone_to_owned().unwrap(), offset, stride);
133+ }
134+ let texture = r.import_dmabuf(&inverted.build().unwrap(), None).unwrap();
135+ let mut out = r
136+ .create_buffer(Fourcc::Argb8888, (SIZE, SIZE).into())
137+ .unwrap();
138+ let mut target = r.bind(&mut out).unwrap();
139+ {
140+ let full = Rectangle::from_size((SIZE, SIZE).into());
141+ let mut frame = r
142+ .render(&mut target, (SIZE, SIZE).into(), Transform::Normal)
143+ .unwrap();
144+ frame
145+ .render_texture_from_to(
146+ &texture,
147+ Rectangle::from_size((SIZE as f64, SIZE as f64).into()),
148+ full,
149+ &[full],
150+ &[],
151+ Transform::Normal,
152+ 1.0,
153+ )
154+ .unwrap();
155+ frame.finish().unwrap().wait().unwrap();
156+ }
157+ let mapping = r
158+ .copy_framebuffer(
159+ &target,
160+ Rectangle::from_size((SIZE, SIZE).into()),
161+ Fourcc::Abgr8888,
162+ )
163+ .unwrap();
164+ let pixels = r.map_texture(&mapping).unwrap().as_chunks::<4>().0;
165+ let at = |y: i32| pixels[(y * SIZE + SIZE / 2) as usize];
166+ assert_eq!(at(1), [0, 255, 0, 255]);
167+ assert_eq!(at(SIZE - 2), [255, 0, 0, 255]);
168+}