Skip to content

Commit d1784a9

Browse files
psyke83Kishi85
andcommitted
fix(pipewire): gate variable rate capture and fix fractional framerates
Targeted fix for half-speed desktop animations that will apply to KWin 6.8+, but also improves compatibility with mutter, and ensures that NTSC fractional framerates are requested properly. Unfortunately, KWin 6.7 will continue exhibiting half-speed desktop animations unless the related fix is backported to this branch. * Isolate variable rate capture to KWin 5.x-6.7.x. Other compositors and KWin 6.8+ will request the actual framerate. Ref: https://bugs.kde.org/show_bug.cgi?id=524129 * Since we now rely on compositor pacing, ensure that the pipewire session negotiates using the fractional rate instead of the coarse framerate value. * Nit: rename refresh_rate to more descriptive target_fps * Nit: ensure target_fps is populated during initial format negotiation * Nit: log host-side delay interval when variable rate is negotiated Co-authored-by: Kishi85 <41839133+Kishi85@users.noreply.github.com>
1 parent 790d70f commit d1784a9

1 file changed

Lines changed: 108 additions & 15 deletions

File tree

src/platform/linux/pipewire.cpp

Lines changed: 108 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -279,13 +279,13 @@ namespace pipewire {
279279
* @param mem_type Mem type.
280280
* @param width Frame or display width in pixels.
281281
* @param height Frame or display height in pixels.
282-
* @param refresh_rate Refresh rate.
282+
* @param target_framerate Target framerate expressed as AVRational.
283283
* @param dmabuf_infos Dmabuf infos.
284284
* @param n_dmabuf_infos N dmabuf infos.
285285
* @param display_is_nvidia Display is nvidia.
286286
* @return 0 when the PipeWire stream is configured; nonzero on negotiation failure.
287287
*/
288-
int 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) {
288+
int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const AVRational target_framerate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
289289
pw_thread_loop_lock(loop);
290290
int result = 0;
291291
if (!stream_data.stream) {
@@ -316,15 +316,15 @@ namespace pipewire {
316316
(mem_type == platf::mem_type_e::cuda && display_is_nvidia));
317317
if (use_dmabuf) {
318318
for (int i = 0; i < n_dmabuf_infos; i++) {
319-
auto format_param = build_format_parameter(&pod_builder, width, height, refresh_rate, dmabuf_infos[i].format, dmabuf_infos[i].modifiers, dmabuf_infos[i].n_modifiers);
319+
auto format_param = build_format_parameter(&pod_builder, width, height, target_framerate, dmabuf_infos[i].format, dmabuf_infos[i].modifiers, dmabuf_infos[i].n_modifiers);
320320
params[n_params] = format_param;
321321
n_params++;
322322
}
323323
}
324324

325325
// Add fallback for memptr
326326
for (const auto &fmt : format_map) {
327-
auto format_param = build_format_parameter(&pod_builder, width, height, refresh_rate, fmt.pw_format, nullptr, 0);
327+
auto format_param = build_format_parameter(&pod_builder, width, height, target_framerate, fmt.pw_format, nullptr, 0);
328328
params[n_params] = format_param;
329329
n_params++;
330330
}
@@ -473,7 +473,7 @@ namespace pipewire {
473473
uint64_t object_serial;
474474
bool negotiate_maxframerate_ = true;
475475

476-
struct spa_pod *build_format_parameter(struct spa_pod_builder *b, uint32_t width, uint32_t height, uint32_t refresh_rate, int32_t format, uint64_t *modifiers, int n_modifiers) {
476+
struct spa_pod *build_format_parameter(struct spa_pod_builder *b, uint32_t width, uint32_t height, AVRational target_framerate, int32_t format, uint64_t *modifiers, int n_modifiers) {
477477
struct spa_pod_frame object_frame;
478478
struct spa_pod_frame modifier_frame;
479479
std::array<struct spa_rectangle, 3> sizes;
@@ -483,18 +483,22 @@ namespace pipewire {
483483
sizes[1] = SPA_RECTANGLE(1, 1);
484484
sizes[2] = SPA_RECTANGLE(8192, 4096);
485485

486-
framerates[0] = SPA_FRACTION(0, 1); // default; we only want variable rate, thus bypassing compositor pacing
486+
framerates[0] = SPA_FRACTION(uint32_t(target_framerate.num), uint32_t(target_framerate.den)); // default/preferred
487487
framerates[1] = SPA_FRACTION(0, 1); // min
488-
framerates[2] = SPA_FRACTION(0, 1); // max
488+
framerates[2] = SPA_FRACTION(1000, 1); // max
489489

490490
spa_pod_builder_push_object(b, &object_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
491491
spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
492492
spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
493493
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
494494
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&sizes[0], &sizes[1], &sizes[2]), 0);
495-
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerates[0]), 0);
496495
if (negotiate_maxframerate_) {
496+
// Always request variable rate (0, 1) for framerate when populating maxFramerate with default,min,max values
497+
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerates[1]), 0);
497498
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&framerates[0], &framerates[1], &framerates[2]), 0);
499+
} else {
500+
// Request target framerate (target_framerate) for framerate in fallback case
501+
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerates[0]), 0);
498502
}
499503

500504
if (format == SPA_VIDEO_FORMAT_xBGR_210LE) {
@@ -809,14 +813,22 @@ namespace pipewire {
809813
*/
810814
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
811815
// calculate frame interval we should capture at
812-
framerate = config.framerate;
813816
delay = ::video::capture_frame_interval(config);
814-
const AVRational fps = ::video::framerate_to_rational(config);
817+
818+
// WORKAROUND: request variable rate (0, 1) capture only if the active compositor is KWin 5.x-6.7.x.
819+
// Ref: https://bugs.kde.org/show_bug.cgi?id=524129
820+
const static std::vector<int> kwin_version = get_running_kwin_version();
821+
const static bool negotiate_variable_rate = !kwin_version.empty() && (kwin_version[0] == 5 || (kwin_version[0] == 6 && kwin_version[1] < 8));
822+
823+
const AVRational fps = (negotiate_variable_rate ? AVRational {0, 1} : ::video::framerate_to_rational(config));
815824
if (fps.den != 1) {
816825
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "/" << fps.den << ", approx. " << av_q2d(fps) << " fps]";
826+
} else if (fps.num == 0 && fps.den == 1) {
827+
BOOST_LOG(info) << "[pipewire] Requested variable rate capture [host pacing: " << std::chrono::duration<double, std::milli>(delay).count() << "ms]";
817828
} else {
818829
BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "fps]";
819830
}
831+
this->target_framerate = fps;
820832
mem_type = hwdevice_type;
821833

822834
if (get_dmabuf_modifiers() < 0) {
@@ -836,8 +848,6 @@ namespace pipewire {
836848
// Verify or update display parameters for streaming to ensure absolute touch inputs work as expected
837849
verify_and_update_display_parameters();
838850

839-
framerate = config.framerate;
840-
841851
if (!shared_state) {
842852
shared_state = std::make_shared<shared_state_t>();
843853
} else {
@@ -854,7 +864,7 @@ namespace pipewire {
854864
}
855865

856866
// Start PipeWire now so format negotiation can proceed before capture start
857-
if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
867+
if (pipewire.ensure_stream(mem_type, width, height, target_framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
858868
BOOST_LOG(error) << "[pipewire] Failed to ensure pipewire stream. pipewire_t::init() failed.";
859869
return -1;
860870
}
@@ -966,7 +976,7 @@ namespace pipewire {
966976
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
967977
auto next_frame = std::chrono::steady_clock::now();
968978

969-
if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
979+
if (pipewire.ensure_stream(mem_type, width, height, target_framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
970980
BOOST_LOG(error) << "[pipewire] Failed to ensure pipewire stream. capture() failed with error.";
971981
return platf::capture_e::error;
972982
}
@@ -1141,6 +1151,89 @@ namespace pipewire {
11411151
return false;
11421152
}
11431153

1154+
/**
1155+
* Fetch the currently running KWin version (if available from its DBUS support information method)
1156+
*
1157+
* @return A vector with 3 elements containing KWin's major.minor.micro version or an empty vector if KWin's version could not be determined
1158+
*/
1159+
static std::vector<int> get_running_kwin_version() {
1160+
#if !GLIB_CHECK_VERSION(2, 74, 0)
1161+
// Compatibility for Ubuntu 22.04 (Glib 2.72)
1162+
constexpr auto G_REGEX_DEFAULT = static_cast<GRegexCompileFlags>(0);
1163+
constexpr auto G_REGEX_MATCH_DEFAULT = static_cast<GRegexMatchFlags>(0);
1164+
#endif
1165+
auto conn = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
1166+
std::vector<int> result;
1167+
1168+
if (!conn) {
1169+
return result;
1170+
}
1171+
1172+
auto reply = g_dbus_connection_call_sync(
1173+
conn,
1174+
"org.kde.KWin",
1175+
"/KWin",
1176+
"org.kde.KWin",
1177+
"supportInformation",
1178+
nullptr,
1179+
G_VARIANT_TYPE("(s)"),
1180+
G_DBUS_CALL_FLAGS_NONE,
1181+
-1,
1182+
nullptr,
1183+
nullptr
1184+
);
1185+
1186+
if (!reply) {
1187+
g_clear_object(&conn);
1188+
return result;
1189+
}
1190+
1191+
g_autofree gchar *support_info = nullptr;
1192+
g_variant_get(reply, "(s)", &support_info);
1193+
1194+
if (!support_info) {
1195+
g_variant_unref(reply);
1196+
g_clear_object(&conn);
1197+
return result;
1198+
}
1199+
1200+
auto *regex = g_regex_new(
1201+
"KWin version: ([0-9]+)\\.([0-9]+)\\.([0-9]+)",
1202+
G_REGEX_DEFAULT,
1203+
G_REGEX_MATCH_DEFAULT,
1204+
nullptr
1205+
);
1206+
1207+
if (!regex) {
1208+
g_variant_unref(reply);
1209+
g_clear_object(&conn);
1210+
return result;
1211+
}
1212+
1213+
GMatchInfo *match_info = nullptr;
1214+
g_regex_match(regex, support_info, G_REGEX_MATCH_DEFAULT, &match_info);
1215+
1216+
if (g_match_info_matches(match_info)) {
1217+
g_autofree const gchar *major =
1218+
g_match_info_fetch(match_info, 1);
1219+
g_autofree const gchar *minor =
1220+
g_match_info_fetch(match_info, 2);
1221+
g_autofree const gchar *micro =
1222+
g_match_info_fetch(match_info, 3);
1223+
1224+
result.emplace_back(std::atoi(major));
1225+
result.emplace_back(std::atoi(minor));
1226+
result.emplace_back(std::atoi(micro));
1227+
}
1228+
1229+
g_match_info_free(match_info);
1230+
g_regex_unref(regex);
1231+
g_variant_unref(reply);
1232+
g_clear_object(&conn);
1233+
1234+
return result;
1235+
}
1236+
11441237
private:
11451238
bool is_buffer_redundant(const egl::img_descriptor_t *img) {
11461239
// Check for corrupted frame
@@ -1280,7 +1373,7 @@ namespace pipewire {
12801373
std::optional<std::uint64_t> last_pts {};
12811374
std::optional<std::uint64_t> last_seq {};
12821375
std::uint64_t sequence {};
1283-
uint32_t framerate;
1376+
AVRational target_framerate;
12841377

12851378
protected:
12861379
// Allow subclasses to access for pipewire requirements setup and stream dead checks

0 commit comments

Comments
 (0)