Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/platform/linux/graphics.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ namespace egl {
// PipeWire metadata
std::optional<uint64_t> pts;
std::optional<uint64_t> seq;
std::optional<bool> pw_damage;
std::optional<uint32_t> pw_flags;
};

class sws_t {
Expand Down
153 changes: 104 additions & 49 deletions src/platform/linux/portalgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,24 @@ namespace portal {

~pipewire_t() {
cleanup_stream();

pw_thread_loop_lock(loop);

if (core) {
pw_core_disconnect(core);
core = nullptr;
}
if (context) {
pw_context_destroy(context);
context = nullptr;
}

pw_thread_loop_unlock(loop);

pw_thread_loop_stop(loop);
if (fd >= 0) {
close(fd);
}
pw_thread_loop_destroy(loop);
}

Expand All @@ -734,6 +752,14 @@ namespace portal {
return stream_data.frame_cv;
}

bool is_frame_ready() const {
return stream_data.frame_ready;
}

void set_frame_ready(bool ready) {
stream_data.frame_ready = ready;
}

void init(int stream_fd, int stream_node, std::shared_ptr<shared_state_t> shared_state) {
fd = stream_fd;
node = stream_node;
Expand Down Expand Up @@ -767,21 +793,8 @@ namespace portal {
pw_stream_destroy(stream_data.stream);
stream_data.stream = nullptr;
}
if (core) {
pw_core_disconnect(core);
core = nullptr;
}
if (context) {
pw_context_destroy(context);
context = nullptr;
}

pw_thread_loop_unlock(loop);

pw_thread_loop_stop(loop);
if (fd >= 0) {
close(fd);
}
}
session_cache_t::instance().invalidate();
}
Expand Down Expand Up @@ -843,14 +856,14 @@ namespace portal {
}

// 2. Validate we have a buffer and a signal that it's "new"
if (stream_data.current_buffer && stream_data.frame_ready) {
if (stream_data.current_buffer) {
struct spa_buffer *buf = stream_data.current_buffer->buffer;

if (buf->datas[0].chunk->size != 0) {
const auto img_descriptor = static_cast<egl::img_descriptor_t *>(img);
img_descriptor->frame_timestamp = std::chrono::steady_clock::now();

// Passthrough PipeWire metadata
// PipeWire header metadata
struct spa_meta_header *h = static_cast<struct spa_meta_header *>(
spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h))
);
Expand All @@ -859,6 +872,23 @@ namespace portal {
img_descriptor->pts = h->pts;
}

// PipeWire flags
if (buf->n_datas > 0) {
img_descriptor->pw_flags = buf->datas[0].chunk->flags;
}

// PipeWire damage metadata
struct spa_meta_region *damage = (struct spa_meta_region *) spa_buffer_find_meta_data(
stream_data.current_buffer->buffer,
SPA_META_VideoDamage,
sizeof(*damage)
);
if (damage) {
img_descriptor->pw_damage = (damage->region.size.width > 0 && damage->region.size.height > 0);
} else {
img_descriptor->pw_damage = std::nullopt;
}

if (buf->datas[0].type == SPA_DATA_DmaBuf) {
img_descriptor->sd.width = stream_data.format.info.raw.size.width;
img_descriptor->sd.height = stream_data.format.info.raw.size.height;
Expand All @@ -874,10 +904,6 @@ namespace portal {
// Point the encoder to the front buffer
img->data = stream_data.front_buffer->data();
img->row_pitch = stream_data.local_stride;

// Reset flags
stream_data.frame_ready = false;
stream_data.current_buffer = nullptr;
}
}
} else {
Expand Down Expand Up @@ -1088,7 +1114,7 @@ namespace portal {

// Ack the buffer type and metadata
std::array<uint8_t, SPA_POD_BUFFER_SIZE> buffer;
std::array<const struct spa_pod *, 2> params;
std::array<const struct spa_pod *, 3> params;
int n_params = 0;
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
auto buffer_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types)));
Expand All @@ -1097,6 +1123,10 @@ namespace portal {
auto meta_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))));
params[n_params] = meta_param;
n_params++;
int videoDamageRegionCount = 16;
auto damage_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * videoDamageRegionCount, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * videoDamageRegionCount)));
params[n_params] = damage_param;
n_params++;

pw_stream_update_params(d->stream, params.data(), n_params);
}
Expand Down Expand Up @@ -1185,48 +1215,33 @@ namespace portal {

platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool show_cursor) {
// FIXME: show_cursor is ignored
auto start_time = std::chrono::steady_clock::now();
auto deadline = std::chrono::steady_clock::now() + timeout;
int retries = 0;

while (std::chrono::steady_clock::now() < deadline) {
if (!wait_for_frame(deadline)) {
return stream_stopped.load() ? platf::capture_e::interrupted : platf::capture_e::timeout;
}

while (true) {
if (!pull_free_image_cb(img_out)) {
return platf::capture_e::interrupted;
}

const auto img_egl = static_cast<egl::img_descriptor_t *>(img_out.get());
auto *img_egl = static_cast<egl::img_descriptor_t *>(img_out.get());
img_egl->reset();
pipewire.fill_img(img_egl);

// Check if we got valid data (either DMA-BUF fd or memory pointer)
bool is_valid_data = (img_egl->sd.fds[0] >= 0 || img_egl->data != nullptr);

// Duplicate detection: PipeWire seq increments on each new frame,
// pts advances with each buffer update. Both must advance to accept frame.
bool is_duplicate = (img_egl->seq.has_value() && img_egl->pts.has_value() && last_pts.has_value() && last_seq.has_value() && img_egl->pts.value() == last_pts.value() && img_egl->seq.value() == last_seq.value());

if (is_valid_data && !is_duplicate) {
// Frame found; check deadline
auto end_time = std::chrono::steady_clock::now();
if (end_time - start_time > timeout) {
return platf::capture_e::timeout;
}

if (img_egl->seq.has_value() && img_egl->pts.has_value()) {
last_seq = img_egl->seq.value();
last_pts = img_egl->pts.value();
}
img_egl->sequence = ++sequence;
// Check if we got valid data (either DMA-BUF fd or memory pointer), then filter duplicates
if ((img_egl->sd.fds[0] >= 0 || img_egl->data != nullptr) && !is_buffer_redundant(img_egl)) {
// Update frame metadata
update_metadata(img_egl, retries);
return platf::capture_e::ok;
}

// No valid frame yet, or it was a duplicate
auto now = std::chrono::steady_clock::now();
if (now - start_time >= timeout) {
return platf::capture_e::timeout;
}

std::unique_lock lock(pipewire.frame_mutex());
pipewire.frame_cv().wait_until(lock, start_time + timeout);
retries++;
}
return platf::capture_e::timeout;
}

std::shared_ptr<platf::img_t> alloc_img() override {
Expand Down Expand Up @@ -1267,6 +1282,8 @@ namespace portal {
stream_stopped.store(false);
previous_height.store(0);
previous_width.store(0);
// Delay interrupt signal to give Portal time to detect change
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
return platf::capture_e::interrupted;
} else {
BOOST_LOG(warning) << "PipeWire stream disconnected. Forcing session reset."sv;
Expand Down Expand Up @@ -1342,6 +1359,44 @@ namespace portal {
}

private:
bool is_buffer_redundant(const egl::img_descriptor_t *img) {
// Check for corrupted frame
if (img->pw_flags.has_value() && (img->pw_flags.value() & SPA_CHUNK_FLAG_CORRUPTED)) {
return true;
}

// If PTS is identical, only drop if damage metadata confirms no change
if (img->pts.has_value() && last_pts.has_value() && img->pts.value() == last_pts.value()) {
return img->pw_damage.has_value() && !img->pw_damage.value();
}

return false;
}

void update_metadata(egl::img_descriptor_t *img, int retries) {
last_seq = img->seq;
last_pts = img->pts;
img->sequence = ++sequence;

if (retries > 0) {
BOOST_LOG(debug) << "Processed frame after " << retries << " redundant events."sv;
}
}

bool wait_for_frame(std::chrono::steady_clock::time_point deadline) {
std::unique_lock<std::mutex> lock(pipewire.frame_mutex());

bool success = pipewire.frame_cv().wait_until(lock, deadline, [&] {
return pipewire.is_frame_ready() || stream_stopped.load();
});

if (success && !stream_stopped.load()) {
pipewire.set_frame_ready(false);
return true;
}
return false;
}

static uint32_t lookup_pw_format(uint64_t fourcc) {
for (const auto &fmt : format_map) {
if (fmt.fourcc == 0) {
Expand Down