When a screencast is started we do a roundtrip on get the offered
formats from wlr_screencopy. This roundtrip can fail [1]. In this case
the formats won't be initialized and we will just error out of the
screencast session.
[1] https://github.com/emersion/xdg-desktop-portal-wlr/issues/214
We teardown all existing screencast_instances using the removed output
by destroying the frame, removing all timers and then marking the
instance as ready for teardown so we can destroy it after the last connected
session is closed.
Any wlr_screencopy_frame_v1 has to be destroyed before the connected
output can be removed. Otherwise wlr_screencopy_frame_v1_destroy
segfaults the whole program. To ensure this we will make all frame
callbacks safe to be triggered for a previous destroyed frame.
wl_array is a better fit than wl_list. It's less intensive on memory and
fits nicely with the flow of dmabuf_feedback announcing all format
modifier pairs at once and reseting them on change.
The compositor can announce it's default rendering device via
linux_dmabuf_feedback as the main_device [1]. We should use this device
whenever possible. If aquiring this device fails we are adviced to use
force linear layout on buffers allocated with the implicit api.
With linux_dmabuf_v1 the modifier event is deprecated. Instead the
format_table event in combination with the tranches of
linux_dmabuf_feedback_v1 has to be used.
[1] https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/unstable/linux-dmabuf/feedback.rst
Calling gbm_bo_create_with_modifiers2(...) and setting the modifiers
pointer to NULL for the same result as gmb_bo_create(...) is a MESA hack,
which I missed to remove from 84282e9b5f.
We query the supported modifiers via a wrapper and announce them via
PipeWire.
This wrapper queries the supported formats from wlroots and returns them
as a simple array. The purpose of this wrapper is to ease the use of the
query functions which are kept in the style of the equivalent egl
queries.
Registering a zwp_linux_dmabuf_v1_listener gives access to the supported
format modifier pairs of the compositor.
This handler emits events for each format modifier pair. Those are stored
and will later be used to announce capabilities via PipeWire.
It has shown that the assumption: "Allocation with implicit modifier will
always be available" doesn't hold true in all cases. Thus if allocation
of any dmabuf fails we mark the session to avoid dmabufs, thus falling
back to shm transport.
This implements the modifier fixation procedure. The producer of a
stream has to ensure that it can create a buffer with the negotiated
properties. To do that we will take the result of the intersection of
supported modifiers by PipeWire and select the "best" modifier from that
list.
To do this we do the following allocations and fixate on the
modifier of the created buffer should the allocation suceed.
* Try to allocate a buffer with explicit modifiers using the list
provided by PipeWire
* Walk the list of modifiers and do single allocations for implicit and
linear modifiers using the old api.
If none of the allocations above succeed we fall back to shm buffers.
This is implemented in the next commit.
If an allocation was successfull we fixate the modifier with
fixate_format().
This function creates an EnumFormat like build_format, but will only
announce support for a single modifier.
It is used to finish the negotiation process by announcing it together
with EnumFormats of our full supported formats.
Using SPA_POD_PROP_FLAG_DONT_FIXATE pipewire can return a list of
supported modifiers. In that case are we responsible to select one
modifier and fixate it.
Keeping a buffer when the copy failed has the risk of having this buffer
removed while we wait for the next buffer_done event. To prevent this we
just enqueue the buffer marking it as corrupt. A new buffer will be
dequeued at the buffer_done event.
Dequeuing a new buffer imidiately creates a problem when the buffer is
destroid while renegotiation of a valid modifier because we can end up using
a buffer which was freed while waiting for the buffer_done event. Then
xdpw will segfault when requesting a copy with a nonexisting buffer. To fix
this we can just dequeue a buffer right before we need it. This makes
the fallback via on_process obsolete since we dequeue the buffer at the
lates possible time.
We should log buffer add and remove events in the debug loglevel, since
they signal starts, stops and renegotiations of the buffer transfer.
Additionaly logging the negotiated pwr_format should help classify
issues from the DEBUG loglevel.
Clients have wrongly used the chunk size to detect if a buffer was valid
or not. Since dmabufs might not have a well defined buffer size we will
set an arbitrary value until clients are patched.
Linear buffer layouts are supported by most gpu vendors and as such
should be compatible in mixed mutli gpu setup where the implicit
modifier of one gpu is not supported by the other.
This option should make xdpw work in these environemnts.
The option is marked as experimental since it might be removed if
explicit modifiers provide a better solution.
This is using the older gbm api without support for explicit modifiers.
This is required to support AMD gpus using the GFX8 or older
architecture and older intel gpus.
When announcing support for dmabufs via PipeWire this is done by adding
the SPA_FORMAT_VIDEO_modifier property to an EnumFormat. Producers can
choose between two different ways [1]:
* Supporting only implicit modifiers by adding the property as a sigle
value together with the SPA_POD_PROP_FLAG_MANDATORY flag
* Support for multiple modifiers. This is done by announcing a
SPA_CHOICE_ENUM array together with the use of the
SPA_POD_PROP_FLAG_MANDATORY and SPA_POD_PROP_FLAG_DONT_FIXATE flag
[1] https://docs.pipewire.org/page_dma_buf.html
We want to support WL_SHM and DMABUFS based buffers. The buffer_type
member tracks the type of a xdpw_buffer and screencopy_frame_info of the
screencast_instance will be an array with an element for each buffer type
indexed by the value of the buffer_type enum. Only members of the
xdpw_screencopy_frame_info relevant to the buffer type should be used.
We build EnumFormats from our supported format multiple times. This
commit just creates a function to do that. This makes it easier to
handle the announcement of multiple buffer types.
PipeWire currently will not renegotiate the used format of a stream when
a new client connects. This causes clients to get stuck when they are
incompatible with the format negotiated for the existing stream.
This will be problematic when some clients support dmabuf transport
while others won't. Thus we disable it until a solution is found.
drm format defined by drm_fourcc.h is the standard to describe the
format of a buffer. This will be used when dealing with dmabufs and to
simplify things we should drm_formats for all internal structs.
In case a client doesn't return a buffer early enough we can give it a
second chance by triggering on_process before we pass the buffer to
wlroots in the frame_buffer_done event.
The previous implemented way to use wlr_screencopy events to cicle the
screencast had issues, like halting the stream if it was paused and
resumed before PipeWire triggered a recreation of buffers. This came
from not returning a dequeued buffer. This is now mititgated by
enqueuing the current pw_buffer imidiately on a paused event and trying
to dequeue a buffer just before requesting a screencopy if none is
present.
It showed that handling self contained buffers is much easier then have
the metadata of the buffer seperated from the actual buffer attached to
the screencast instance.
The goal of the following changes is to separate the meta informations
like requested buffer attributes and wlr_screencast data from the actual
buffers.
This enables us to:
* Simplify the flow between the PipeWire loop and the wlroots one
* Track and apply damage to each used buffer (ext_screencopy)
A PipeWire client might pause the stream which enables us to stop
requesting buffers from the compositor. In this case the the quit bool
won't be enacted on and thus we should destroy it directly from here.
We can trigger that with pw_stream_trigger_process when we are the
driver of the stream. Additionally this let's us run passivly with the
consumer driving the stream.
When a buffer is destroyed while used in the copy_buffer request the
screencopy protocoll will answer with the failed event. This can happen
anytime when PipeWire calls the remove_buffer callback, eg. on
renegotiation. This is not fatal, so we don't need to close the
screencast.
cast->err should only be used for fatal errors.
PipeWire can request to destroy the allocated buffers anytime. This
isn't a problem for us, since the screencopy protocol can handle
disappearing buffers. The only thing we have to do is not to use a
destroyed buffer.
The enum xdpw_frame_state is used to track the state of the xdpw_frame through
the screencopy callbacks. xdpw_wlr_stream_finish is used as the new
endpoint of the screencopy callbacks. Here we clean up the
screencopy_frame, enqueue the pipewire buffer and restart the screencopy
loop if needed.
Sometimes it can happen that the first frame of the active stream
triggers the renegotiation and destroy the frame without copy and with
that starting the fps_limit counter. This triggers an assert in
fps_limit_measure_end. To avoid it, we only engage the fps_limiter after
a frame was copied successfully.
Instead of using one wl_buffer to export buffers from wlroots and then
copy the content into the buffer dequed from pipewire, we can create a
wl_buffer for each pipewire buffer directly at allocation time and
attach it to the data attribute. Those wl_buffers can be directly handed
over to the wlroots screencopy protocol and so removing one copy.
Since we gather the information of the currently used buffer on
importing it, we can check if the imported and the announce buffer by
wlroots are compatible.
Also the comparison between the announced buffer properties and the
format used by the pipewire stream was moved to wlr_frame_buffer_done.
This lets us implement all checks in the same callback and makes it
easier to extend those checks for future dmabuf sharing.
Since we are driving the screencast there are no events on the pipewire loop
calling the on_event callback. We want to import and export (if possible) on
every frame of the wlroots loop, so this event is no longer needed.
The default node_id is SPA_ID_INVALID.
We are casting pipewire uint32_t node_id to int for printing since they are
currently staying in the range of low integer numbers. This makes
spotting an uninitialized node_id (casted to -1) much easier. Should be
corrected if that becomes an issue in the future.
This makes xdpw exit when either the D-Bus daemon, the Wayland
compositor or Pipewire is shut down. This avoids having dangling
non-functional xdpw instances, e.g. when restarting the compositor.
To test, start Sway nested in Sway, run xdpw with
WAYLAND_DISPLAY=wayland-2, and exit the nested Sway.
```
../src/core/config.c: In function ‘finish_config’:
../src/core/config.c:26:9: error: ‘free’ called on pointer ‘config_7(D)’ with nonzero offset 16 [-Werror=free-nonheap-object]
26 | free(&config->screencast_conf.exec_before);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../src/core/config.c:27:9: error: ‘free’ called on pointer ‘config_7(D)’ with nonzero offset 24 [-Werror=free-nonheap-object]
27 | free(&config->screencast_conf.exec_after);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../src/core/config.c:28:9: error: ‘free’ called on pointer ‘config_7(D)’ with nonzero offset 32 [-Werror=free-nonheap-object]
28 | free(&config->screencast_conf.chooser_cmd);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
```
Supports "dmenu" chooser type, which is called with a dmenu type list
piped to stdin, "simple" type, which recieves nothing on stdin and
default, which tries the hardcoded choosers.
Choosers are required to return the name of the choosen output as given
by the xdg-output protocol.
Thanks to piater for closing overlooked pipes.
Thanks to ericonr for suggestions regarding fork and pipes.
Make sure that in SPA_POD_CHOICE_ENUM_Id(n_vals, ...) we always pass
exactly n_vals arguments. For this we build the object with separate
spa_pod_builder_add calls, since n_vals is known at runtime.
Fix#102
According to the spec, ~/.config is a fallback used when
XDG_CONFIG_HOME is unset. When the user has explicitly set
XDG_CONFIG_HOME, we shouldn't try to use the fallback.
We were checking both /etc/xdg-desktop-portal-wlr/ and
/etc/xdg/xdg-desktop-portal-wlr/. These two directories serve the same
purpose, let's just settle on a single one.
The goal is to control the rate of capture while in screencast, as it
can represent a performance issue and can cause input lag and the
feeling of having a laggy mouse.
This commit addresses the issue reported in #66.
The code measures the time elapsed to make a single screen capture, and
calculates how much to wait for the next capture to achieve the targeted
frame rate. To delay the capturing of the next frame, the code
introduces timers into the event loop based on the event loop in
https://github.com/emersion/mako
Added a command-line argument and an entry in the config file as well
for the max FPS. The default value is 0, meaning no rate control.
Added code to measure the average FPS every 5 seconds and print it with
DEBUG level.
This allows replacing a running xdpw instance. xdg-desktop-portal
itself has this feature.
This is useful when developing, to stop the currently running
system instance.
In file included from ../src/screencast/wlr_screencast.c:18:
../include/pipewire_screencast.h:7:9: error: 'ALIGN' macro redefined [-Werror,-Wmacro-redefined]
#define ALIGN 16
^
/usr/include/machine/param.h:79:9: note: previous definition is here
#define ALIGN(p) _ALIGN(p)
^
* Revert "Remove unnecessary check from wlr_screencast.c"
This reverts commit cc59abb124.
Fixesemersion/xdg-desktop-portal-wlr#50
* Clarify why the check in wlr_screencast is needed
The error handling at the `error:` label tears down the whole state. Thus, the
state needs to be fully initialized in order for the tear down to succeed.
Currently, if e.g. `sd_bus_open_user()` fails, a `segfault` is triggered by
the tear down. This commit adds individual tear down code that only touches
stuff that until that point was successfully initialized.