Skip to content
Draft
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
133 changes: 131 additions & 2 deletions src/platform/linux/portalgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,20 @@ namespace portal {
guint subscription_id;
};

struct shared_state_t {
std::atomic<int> negotiated_width {0};
std::atomic<int> negotiated_height {0};
std::atomic<uint64_t> format_generation {0};
std::atomic<bool> stream_dead {false};
};

struct stream_data_t {
struct pw_stream *stream;
struct spa_hook stream_listener;
struct spa_video_info format;
struct pw_buffer *current_buffer;
uint64_t drm_format;
std::shared_ptr<shared_state_t> shared;
};

struct dmabuf_format_info_t {
Expand Down Expand Up @@ -677,15 +685,25 @@ namespace portal {
pw_thread_loop_destroy(loop);
}

void init(int stream_fd, int stream_node) {
void init(int stream_fd, int stream_node, std::shared_ptr<shared_state_t> shared_state) {
fd = stream_fd;
node = stream_node;
stream_data.shared = std::move(shared_state);

context = pw_context_new(pw_thread_loop_get_loop(loop), nullptr, 0);
core = pw_context_connect_fd(context, dup(fd), nullptr, 0);
pw_core_add_listener(core, &core_listener, &core_events, nullptr);
}

void cleanup_stream() {
if (loop && stream_data.stream) {
pw_thread_loop_lock(loop);
pw_stream_destroy(stream_data.stream);
stream_data.stream = nullptr;
pw_thread_loop_unlock(loop);
}
}

void ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
pw_thread_loop_lock(loop);
if (!stream_data.stream) {
Expand Down Expand Up @@ -820,6 +838,31 @@ namespace portal {
.error = on_core_error_cb,
};

static void on_stream_state_changed(void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) {
auto *d = static_cast<stream_data_t *>(user_data);

switch (state) {
case PW_STREAM_STATE_ERROR:
case PW_STREAM_STATE_UNCONNECTED:
// If we hit an actual error or unconnected, it's always dead.
if (d->shared) {
d->shared->stream_dead.store(true, std::memory_order_relaxed);
}
break;
case PW_STREAM_STATE_PAUSED:
// FIXME: On mode change, mutter pauses the stream and destroys the pipewire connection
// This workaround will cause a legitimate session pause to immediately unpause.
if (old == PW_STREAM_STATE_STREAMING && d->shared) {
BOOST_LOG(warning) << "Active stream was paused by compositor. Marking as dead for re-init."sv;
d->shared->stream_dead.store(true, std::memory_order_relaxed);
}
break;
default:
break;
}
return;
}

static void on_process(void *user_data) {
const auto d = static_cast<struct stream_data_t *>(user_data);
struct pw_buffer *b = nullptr;
Expand Down Expand Up @@ -873,6 +916,22 @@ namespace portal {
BOOST_LOG(info) << "Framerate (from compositor, max): "sv << d->format.info.raw.max_framerate.num << "/"sv << d->format.info.raw.max_framerate.denom;
}

int physical_w = d->format.info.raw.size.width;
int physical_h = d->format.info.raw.size.height;

if (d->shared) {
int old_w = d->shared->negotiated_width.load(std::memory_order_relaxed);
int old_h = d->shared->negotiated_height.load(std::memory_order_relaxed);

if (physical_w != old_w || physical_h != old_h) {
d->shared->negotiated_width.store(physical_w, std::memory_order_relaxed);
d->shared->negotiated_height.store(physical_h, std::memory_order_relaxed);

// Publish the change *after* dimensions are written
d->shared->format_generation.fetch_add(1, std::memory_order_release);
}
}

uint64_t drm_format = 0;
for (const auto &fmt : format_map) {
if (fmt.fourcc == 0) {
Expand Down Expand Up @@ -906,6 +965,7 @@ namespace portal {

constexpr static const struct pw_stream_events stream_events = {
.version = PW_VERSION_STREAM_EVENTS,
.state_changed = on_stream_state_changed,
.param_changed = on_param_changed,
.process = on_process,
};
Expand All @@ -931,7 +991,43 @@ namespace portal {

framerate = config.framerate;

pipewire.init(pipewire_fd, pipewire_node);
shared_state = std::make_shared<shared_state_t>();

pipewire.init(pipewire_fd, pipewire_node, shared_state);

// Start PipeWire now so format negotiation can proceed before capture start
pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia);

uint64_t start_gen = shared_state->format_generation.load();
int timeout_ms = 1500;
int negotiated_w = 0;
int negotiated_h = 0;

while (timeout_ms > 0) {
negotiated_w = shared_state->negotiated_width.load();
negotiated_h = shared_state->negotiated_height.load();
if (negotiated_w > 0 && negotiated_h > 0) {
break;
}
uint64_t gen = shared_state->format_generation.load(std::memory_order_acquire);
if (gen != start_gen) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
timeout_ms -= 10;
}

if (negotiated_w > 0 && negotiated_h > 0 &&
(negotiated_w != width || negotiated_h != height)) {
BOOST_LOG(info) << "Using negotiated resolution "sv
<< negotiated_w << "x" << negotiated_h;

width = negotiated_w;
height = negotiated_h;
}

last_seen_generation =
shared_state->format_generation.load(std::memory_order_acquire);

return 0;
}
Expand Down Expand Up @@ -980,6 +1076,37 @@ namespace portal {
sleep_overshoot_logger.reset();

while (true) {
// 1. Check if PipeWire signaled a state error (stream dead)
if (shared_state->stream_dead.exchange(false)) {
BOOST_LOG(warning) << "PipeWire stream disconnected. Forcing session reset."sv;

pipewire.cleanup_stream();
session_cache_t::instance().invalidate();

// Add a small delay before reinit to let WirePlumber see state change
std::this_thread::sleep_for(std::chrono::milliseconds(500));
return platf::capture_e::reinit;
}

// 2. Check for resolution changes (generation change)
uint64_t gen = shared_state->format_generation.load(std::memory_order_acquire);
if (gen != last_seen_generation) {
int new_w = shared_state->negotiated_width.load();
int new_h = shared_state->negotiated_height.load();

last_seen_generation = gen;

if (new_w != width || new_h != height) {
BOOST_LOG(info) << "Dynamic resolution change detected: "sv
<< new_w << "x" << new_h;

// Update local state and signal Sunshine to re-create the encoder
width = new_w;
height = new_h;
return platf::capture_e::reinit;
}
}

auto now = std::chrono::steady_clock::now();

if (next_frame > now) {
Expand Down Expand Up @@ -1149,6 +1276,8 @@ namespace portal {
std::chrono::nanoseconds delay;
std::uint64_t sequence {};
uint32_t framerate;
std::shared_ptr<shared_state_t> shared_state;
uint64_t last_seen_generation {0};
};
} // namespace portal

Expand Down