Skip to content

Import shm buffers through udmabuf - #2254

Open
Netflate wants to merge 7 commits into
Smithay:masterfrom
Netflate:shm-udmabuf
Open

Netflate wants to merge 7 commits into
Smithay:masterfrom
Netflate:shm-udmabuf

Conversation

@Netflate

@Netflate Netflate commented Oct 30, 2026 •

Copy link
Copy Markdown

Description

Import shm buffers via udmabuf.

Closes #2044, indirectly helps a bit with #1652.

Details

  • udmabuf is only enabled on integrated i915 (with LLC), xe and amdgpu GPUs: discrete ones are slower through it, and other drivers are either broken or untested.
    • an external renderer doesn't pass the driver, so udmabuf is disabled by default for it. If preferred, we can require external renderers to define prefers_udmabuf, but it's a breaking change.
  • Client can start writing to a buffer after wl_buffer.release while the gpu is still reading it. Two solutions are implemented: a blocking one via a fence, and a fast non blocking one via DrmCompositor (not a driver workaround). Fence is still required when DrmCompositor isn't available.
  • I didn't really initially plan an udmabuf pool, at least in this pr (plus Kde doesn't have one either), but without it the gains really drop when client keeps creating new wl_buffer for every frame, like in Firefox.
  • Up to 4 udmabufs are cached instead of 1, because sctk clients cycle through 4 buffers. With 1 the gain would drop.
  • create_buffer_from_memfd: removed the stride and offset checks. Offset and size no longer need to be page multiples: the udmabuf starts at the page boundary, and the remainder is passed as the plane offset.
    • The stride check is really only needed for Nvidia (silently shows broken frames), which is disabled by default anyway. Intel works with any stride. AMD needs 256 (as ids1024 said), but seems like the driver rejects an incorrect stride itself.
    • Offsets: not needed on Intel, Nvidia rejects non-multiples of 64 for me
    • AMD : Kde doesn't have a stride check, and everything is okay, but for offsets there isn't any past experience, so that requires testing, which i can't do

Testing results

Intel Tiger Lake + NVIDIA GeForce RTX 3050 Ti Mobile, using Cosmic and anvil.

Results on Intel:

  1. Firefox on Cosmic (1080p60, new wl_buffer every frame): ~650–690 microseconds copying vs ~22 microseconds with cached udmabuf (~255 microsecond without cache).
  2. anvil + test_subsurface (1024×768, reused buffers): ~545–577 microseconds copying vs ~26–30 microseconds with udmabuf.
  3. anvil + Okular: ~0.7–2.3 ms copying vs ~20–28 microseconds with udmabuf.
  4. anvil compositor load: −37% during video playback and −55% during scrolling.
  5. Buffer holding: in tty maximum wait before wl_buffer.release goes from 1.6 ms to 19 microseconds with it

small note: Nvidia stale pixel issue (the reason it's fully denied in kde) occurs very rarely for me, but it was widely reported in Kde, so there's no point in making an exception for Nvidia.

Checklist

Copilot AI balanced review requested due to automatic review settings October 30, 2026 22:49

This comment was marked as low quality.

@Netflate

Copy link
Copy Markdown
Author

About holding buffers in DrmCompositor: it doesn't look like the most elegant solution, but it's kinda the only way to avoid the blocking wait without an async fence based release, which would need an event loop where smithay releases buffers

(And I'm really sorry for whatever copilot thing is??? It never happened in my other PRs, I have no idea why it was even enabled)

@ids1024

ids1024 commented Oct 31, 2026

Copy link
Copy Markdown
Member

(And I'm really sorry for whatever copilot thing is??? It never happened in my other PRs, I have no idea why it was even enabled)

I don't think anyone's explicitly enabled anything Copilot related on this repo. Maybe something Github has turned on by default that we may want to disable...

@Netflate

Copy link
Copy Markdown
Author

Don't worry, turns out 'automatic copilot code review' was enabled on my side. Tho I'm confused why it showed up only here for the first time, and there aren't any security logs suggesting it was enabled. Really want to blame github, but couldn't find anyone reporting github enabling this itself, so whatever
image

Comment thread src/backend/allocator/udmabuf.rs Resolved
@ids1024

ids1024 commented Oct 31, 2026

Copy link
Copy Markdown
Member

So the idea of PoolUdmabufs is that Firefox creates a new wl_buffer each time, but reuses the same offset/width/height/etc?

Something I was wondering is if we can just allocate a udmabuf file descriptor for the whole wl_shm_pool, then create multiple of smithay's (slightly inaccurately named) Dmabuf with different offsets. In #2085 I updated Dmabuf to use an Arc<OwnedFd> so we can share a file descriptor like this, though I haven't done testing of exactly how that ends up working with udmabuf.

Does that amount to the same thing as creating multiple udmabufs with different offsets? And perform the same? If so we may need to create new udmabufs on pool resizes, but it sounds cleaner than trying to make assumptions about how many buffers to cache per pool.

@Netflate

Netflate commented Nov 1, 2026 •

Copy link
Copy Markdown
Author

Yeah that's it, every wl_buffer creation is with the same layout, that's why the pool cache really helps. Copy ~760 microseconds, udmabuf without cache ~345, with ~27 (cpu 10.0% / 7.8% / 5.5%).

I've considered one udmabuf per pool. For the gpu it's the same thing and works fine, but it's not faster, the renderer caches imports per Dmabuf, so a new Dmabuf per wl_buffer means a new import every frame, increasing cpu usage, and +- doubling import time. Fixing that needs the same cache again.
And it hits udmabuf's 64 mb limit: at 4K sctk's pool reaches 127 mb, so nothing gets imported and goes back to copying. The only gain is fewer udmabuf creations at startup for pools with several buffers, which are more of an exception.

About buffers number: my description was a bit misleading, it's up to 4, not always 4. Cache only keeps layouts pool actually used, and the vast majority of apps use only one. Dynamic number is easy to implement I think, but I don't know any app using more than 4, and if it needs more it will just create a new udmabuf, which is still faster than copying

@xerpi

xerpi commented Nov 2, 2026

Copy link
Copy Markdown

And it hits udmabuf's 64 mb limit: at 4K sctk's pool reaches 127 mb, so nothing gets imported and goes back to copying.

Just passing by, and also not sure if it will have any impact at all, but it seems udmabuf's 64MiB limit has been lifted in Linux 7.3 (original series https://lists.freedesktop.org/archives/dri-devel/2026-July/583848.html).

@Drakulix Drakulix left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a stab at this, in general this looks really good.

I wish we could just unconditionally import all shm bufs as udmabufs and not make this renderer-specific, but given the current constraints, this seems like a really solid implementation.

Just a couple of remarks, since I would want to allow downstream to be able to control some aspects of this more precisely.

Comment thread src/backend/allocator/udmabuf.rs Resolved
Comment thread src/backend/allocator/udmabuf.rs Resolved
/// Client buffers the GPU reads directly while rendering this frame (shm via udmabuf).
/// Keeping them alive delays their `wl_buffer.release` until the frame is showed,
/// when GPU is done reading them.
sampled_buffers: Vec<crate::backend::renderer::utils::Buffer>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little unsure why this is necessary. I thought we already hold the buffer in the DrmScanoutBuffer as part of the plane state?

Ping @cmeissl

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plane state only holds a client buffer when it's put directly on a plane. Udmabuf shm buffers get rendered into the swapchain buffer instead so nothing keeps them. My comment was a bit misleading, fixed it

Also they only need to live until the frame is presented, in plane state they'd live one frame longer, which isn't a big deal, but clients with 2 buffers would get theirs back up to a frame later

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that would affect normal dmabuf buffers as well, right? I wonder if the renderer should rather hold buffers until rendering is done, that seems like the part of the code that should be responsible to me.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implicitly synced dmabufs are fine, since the client's gpu write waits for the compositor's read fence. Tho looks like explicit sync have the same problem, release point is signaled when the buffer is dropped, not when the gpu is done. Probably rare, but worth fixing

I guess yeah, it's cleaner to implement overall buffer holding in renderer. But I'm not sure where to move it. As a new method on Frame, or OutputDamageTracker if possible, which already has the drawn elements and the SyncPoint from finish(). The latter is better I suppose?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally a GlesTexture would hold a weak reference, which is upgraded and stored on render_texture.

Given this also concerns explicit sync: ping @ids1024

Comment thread src/backend/renderer/gles/mod.rs Resolved
Only integrated GPUs are allowed. Discrete would read udmabuf
over PCIe on every draw, so copying into their own memory is faster
if a buffer is drawn more than once.

Known broken ones:
- nvidia-drm: sometimes shows outdated data, `DMA_BUF_IOCTL_SYNC` doesn't help
- i915 without LLC: CPU cache is flushed only on the first import
- virtio_gpu: breaks virgl context (KWin report, precaution)
- vmwgfx: kernel leak (KWin report, precaution)
gpu reads use DMA_BUF_IOCTL_SYNC for sync, END on every import,
and START right after udmabuf creation (since the kernel rejects
END without a preceding START).

The syncs only matter where CPU caches aren't coherent with the GPU (e.g. many ARM SoCs).
Delays their wl_buffer.release until gpu is done reading them, so
`wait_udmabuf_idle` doesn't block the compositor for miliseconds.

Unlike `wait_udmabuf_idle`, this also works on gpus that don't report
their reads (e.g. nvidia).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a helper for importing shm buffers via udmabuf

5 participants