mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-15 13:22:19 +01:00
On a host whose GPU can encode H.264 (a Windows driver through d3d12 on WSL 2 does), KRDP in
the demo guest encoded on the guest's CPUs. The host side was fine (vainfo on the host lists
H.264 and HEVC EncSlice), and virgl can pass a guest's VA-API video to it, but a chain of seven
problems stood between the two. Each is a patch, and the guest takes them all:
QEMU (nix/dev/qemu-virgl-video.patch)
QEMU never asks virglrenderer for video: virglrenderer turns it on only for an embedder that
passes VIRGL_RENDERER_USE_VIDEO and a get_drm_fd callback to open the host's video device on.
The flag is defined only with VIRGL_RENDERER_UNSTABLE_APIS, which the build sets.
virglrenderer 1.3.0, four patches in this order
virglrenderer-chroma-format.patch
the guest's Mesa and virglrenderer number the chroma format of a codec differently: 4:2:0
was read as 4:2:2, and the host's encoder refused every codec it was asked for
virglrenderer-video-memory-upload.patch
the d3d12 driver has no dma-bufs: vaExportSurfaceHandle fails, nothing was uploaded and
the encoder returned an empty bitstream. The planes of the guest's picture are read back
(glReadPixels) and written to the surface through a derived image (vaDeriveImage, with
vaCreateImage and vaPutImage as a fallback)
virglrenderer-h264-packed-sps.patch
Mesa's VA-API takes the level of the stream from a packed SPS, which virglrenderer never
sent: level 0 reached UNREACHABLE in the d3d12 encoder and crashed QEMU
virglrenderer-ubo0-constants.patch
a constant buffer 0 bound as a buffer was never read by the shader, so the colour matrix
of an RGB to YUV conversion was zero and the picture was a solid green
Mesa 26.2.5 in the guest, two patches (hardware.graphics.package)
mesa-virgl-rgb-yuv-csc.patch
the graphics compositor never loaded an RGB to YUV matrix, and multiplied by the YUV to
RGB one of its initial state
mesa-virgl-fence-leak.patch
a surface kept the out-fence (a sync_file) of its last flush and the next flush stored a
new one over it: one file descriptor leaked per converted frame, 44 a second under motion,
and KRDP froze when it reached the limit of 1024, about 28 seconds in
KRDP (krdp.nix) asks for H264Baseline and the guest's driver lists Main and High, so ffmpeg's
h264_vaapi found no profile it could use: it is built with H264Main.
What was checked: a luma ramp and a colour-bar pattern encoded through the guest to the 4090
decode to the right values; the real desktop shows over RDP in Windows' own client with the
right colours; under es2gears motion the descriptors of krdpserver stay flat (3 sync_file, 78
of 1024) where they were 949 of 1024 after 28 seconds.
run-kde sets what the host needs for Mesa and for virglrenderer's libva to find the Windows
driver, on WSL 2 with the GPU on and unless it is set already: GALLIUM_DRIVER=d3d12,
LD_LIBRARY_PATH with /usr/lib/wsl/lib first, LIBVA_DRIVER_NAME=d3d12 and LIBVA_DRIVERS_PATH
(/run/opengl-driver/lib/dri, for the d3d12 video driver, which it looks for). --doctor checks
for the libraries and for that driver, and the tests have a scenario for each.
Known: the software decoder warns "illegal short term buffer state detected" on the stream; the
d3d12 encoder writes Constrained Baseline whatever profile is asked for. There is no binary
cache: the first run builds QEMU and Mesa, which takes about 20 minutes. QEMU, virglrenderer and
Mesa are the runner's and the guest's only, and the VM suites build the Mesa as well.
Not measured yet: whether it is smoother than the CPU encoder under the same load.
108 lines
4.3 KiB
Nix
108 lines
4.3 KiB
Nix
# KRDP, Plasma's RDP server, in the Plasma session of every user who logs in. The session guest
|
|
# of plasma-vm.nix enables it, and so does the VM suite `rdp`, which checks that a client can log
|
|
# in and get a picture of the desktop (docs/dev/PLATFORM.md says what it does not check).
|
|
#
|
|
# The password is not in the repository: the unit starts once the user has written one to
|
|
# ~/.config/komorebi-vm/rdp-password, and a client logs in with the user's name and that password.
|
|
# A guest whose password comes from outside, as the demo guest's does (run-kde.nix), names a file
|
|
# of the guest in `passwordFile`, which the unit reads in place of that one when it is there.
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.komorebi.krdp;
|
|
|
|
# KRDP asks for the H.264 Constrained Baseline profile and nothing else. A guest whose VA-API
|
|
# drives the host's encoder (virgl, with the QEMU of run-kde.nix) lists Main and High and no
|
|
# Constrained Baseline, so ffmpeg's h264_vaapi finds no profile it can use and KRDP encodes on
|
|
# the CPUs. Main is in RDP's H.264 mode as well, and the software encoder takes it too.
|
|
krdp = pkgs.kdePackages.krdp.overrideAttrs (old: {
|
|
postPatch = (old.postPatch or "") + ''
|
|
substituteInPlace src/VideoStreamSurface.cpp --replace-fail H264Baseline H264Main
|
|
'';
|
|
});
|
|
|
|
startKrdp = pkgs.writeShellScript "krdp-start" ''
|
|
set -eu
|
|
dir=$HOME/.config/komorebi-vm
|
|
password_file=$dir/rdp-password
|
|
${lib.optionalString (cfg.passwordFile != null) ''
|
|
if [ -r ${lib.escapeShellArg cfg.passwordFile} ]; then
|
|
password_file=${lib.escapeShellArg cfg.passwordFile}
|
|
fi
|
|
''}
|
|
mkdir -p "$dir"
|
|
# krdpserver refuses to run without a TLS certificate and key (its --help promises a temporary
|
|
# one, but it only checks that the files exist), so make a throwaway self-signed pair
|
|
if [ ! -f "$dir/rdp.crt" ] || [ ! -f "$dir/rdp.key" ]; then
|
|
${pkgs.openssl}/bin/openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj /CN=komorebi-vm \
|
|
-addext subjectAltName=DNS:localhost,IP:127.0.0.1 -keyout "$dir/rdp.key" -out "$dir/rdp.crt"
|
|
chmod 600 "$dir/rdp.key"
|
|
fi
|
|
# --plasma asks KWin for the screen and for input directly, with no portal prompt to click
|
|
exec ${krdp}/bin/krdpserver --plasma --address 0.0.0.0 --port 3389 \
|
|
--certificate "$dir/rdp.crt" --certificate-key "$dir/rdp.key" \
|
|
--username "$USER" --password "$(cat "$password_file")"
|
|
'';
|
|
in
|
|
{
|
|
options.komorebi.krdp = {
|
|
enable = lib.mkEnableOption "KRDP, an RDP server, in every Plasma session";
|
|
|
|
passwordFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "/run/run-kde-guest/rdp-password";
|
|
description = ''
|
|
The path, in the guest, of a file with the password a client logs in with. It is a path in
|
|
the running guest and not a file of the Nix store, so that no password is part of the
|
|
system. The unit reads it in place of `~/.config/komorebi-vm/rdp-password` when it exists,
|
|
and starts when either exists.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
environment.systemPackages = [
|
|
krdp
|
|
# A client, to try the server from inside the guest. FreeRDP decodes H.264 with OpenH264
|
|
# when it is built with it, and OpenH264 shows nothing of the video KRDP sends; without it,
|
|
# FreeRDP decodes with FFmpeg (the VM suite `rdp`, docs/dev/PLATFORM.md).
|
|
(pkgs.freerdp.override { openh264 = null; })
|
|
];
|
|
|
|
# krdp streams the screen through pipewire
|
|
services.pipewire = {
|
|
enable = true;
|
|
wireplumber.enable = true;
|
|
};
|
|
security.rtkit.enable = true;
|
|
|
|
networking.firewall.allowedTCPPorts = [ 3389 ];
|
|
|
|
systemd.user.services.krdp = {
|
|
description = "RDP into this Plasma session";
|
|
wantedBy = [ "plasma-workspace.target" ];
|
|
after = [ "plasma-core.target" ];
|
|
# with no password the unit is skipped, not failed: `systemctl --user start krdp` once the
|
|
# file is there
|
|
unitConfig.ConditionPathExists =
|
|
if cfg.passwordFile == null then
|
|
"%h/.config/komorebi-vm/rdp-password"
|
|
else
|
|
[
|
|
"|%h/.config/komorebi-vm/rdp-password"
|
|
"|${cfg.passwordFile}"
|
|
];
|
|
serviceConfig = {
|
|
ExecStart = startKrdp;
|
|
Restart = "on-failure";
|
|
RestartSec = 3;
|
|
};
|
|
};
|
|
};
|
|
}
|