diff --git a/docs/configuration.md b/docs/configuration.md
index 7b0ff4da66b..4eb74577b4c 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -2204,7 +2204,8 @@ supported on the current platform.
| wlr |
Capture for wlroots based Wayland compositors via wlr-screencopy-unstable-v1. It is possible to capture
- virtual displays in e.g. Hyprland using this method.
+ virtual displays in e.g. Hyprland using this method. Frames are captured only when the display changes, so
+ an idle display costs the compositor no redraws.
@note{Applies to Linux only.} |
diff --git a/src/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp
index ee3f2c3edd6..30eb773d755 100644
--- a/src/platform/linux/wayland.cpp
+++ b/src/platform/linux/wayland.cpp
@@ -507,9 +507,10 @@ namespace wl {
// Store for cleanup
self->current_wl_buffer = buffer;
- // Start the actual copy
+ // Start the actual copy. With damage, the compositor completes it on the next change to the output instead of
+ // redrawing the whole output for it, so an unchanged screen costs nothing to capture.
zwp_linux_buffer_params_v1_destroy(params);
- zwlr_screencopy_frame_v1_copy(frame, buffer);
+ zwlr_screencopy_frame_v1_copy_with_damage(frame, buffer);
}
// Buffer params failed callback
diff --git a/src/platform/linux/wayland.h b/src/platform/linux/wayland.h
index 5cd24bf0bc5..64f99b837a8 100644
--- a/src/platform/linux/wayland.h
+++ b/src/platform/linux/wayland.h
@@ -227,6 +227,17 @@ namespace wl {
bool y_invert {false};
};
+ /**
+ * @brief Determine whether a capture should request a new screencopy frame.
+ *
+ * Frames are copied with damage, so the compositor holds a request until the output changes. A capture that timed
+ * out still has that request in flight, and requesting another would replace the buffer it is being copied into.
+ *
+ * @param status State of the current screencopy request.
+ * @return `true` unless a requested frame is still pending.
+ */
+ bool should_request_frame(dmabuf_t::status_e status);
+
/**
* @brief Wayland output metadata used to match a configured display name.
*/
diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp
index 30be46e5fad..0258492fa63 100644
--- a/src/platform/linux/wlgrab.cpp
+++ b/src/platform/linux/wlgrab.cpp
@@ -33,6 +33,10 @@ namespace wl {
return false;
}
+ bool should_request_frame(dmabuf_t::status_e status) {
+ return status != dmabuf_t::WAITING;
+ }
+
/**
* @brief Captured frame buffer shared between capture and encode stages.
*/
@@ -174,8 +178,17 @@ namespace wl {
inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
auto to = std::chrono::steady_clock::now() + timeout;
+ if (should_request_frame(dmabuf.status)) {
+ dmabuf.listen(
+ interface.screencopy_manager,
+ interface.dmabuf_interface,
+ &interface.supported_modifiers,
+ output,
+ cursor
+ );
+ }
+
// Dispatch events until we get a new frame or the timeout expires
- dmabuf.listen(interface.screencopy_manager, interface.dmabuf_interface, &interface.supported_modifiers, output, cursor);
do {
auto remaining_time_ms = std::chrono::duration_cast(to - std::chrono::steady_clock::now());
if (remaining_time_ms.count() < 0 || !display.dispatch(remaining_time_ms)) {
diff --git a/tests/unit/platform/linux/test_wayland.cpp b/tests/unit/platform/linux/test_wayland.cpp
index 98b221a9dde..e8d50e62b2e 100644
--- a/tests/unit/platform/linux/test_wayland.cpp
+++ b/tests/unit/platform/linux/test_wayland.cpp
@@ -104,6 +104,21 @@ TEST(WaylandCaptureTest, UsesVramForCudaOnlyWhenCudaSupportIsBuilt) {
#endif
}
+TEST(WaylandCaptureTest, RequestsTheFirstFrame) {
+ wl::dmabuf_t dmabuf;
+
+ EXPECT_TRUE(wl::should_request_frame(dmabuf.status));
+}
+
+TEST(WaylandCaptureTest, WaitsOnAFramePendingDamage) {
+ EXPECT_FALSE(wl::should_request_frame(wl::dmabuf_t::WAITING));
+}
+
+TEST(WaylandCaptureTest, RequestsAFrameOnceTheLastOneCompleted) {
+ EXPECT_TRUE(wl::should_request_frame(wl::dmabuf_t::READY));
+ EXPECT_TRUE(wl::should_request_frame(wl::dmabuf_t::REINIT));
+}
+
TEST(WaylandInterfaceTest, RecordsOnlyExplicitDmabufModifiers) {
constexpr std::uint32_t format = DRM_FORMAT_XRGB8888;
constexpr std::uint64_t explicit_modifier = 0x100000000000004;