aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--88-gsr-coolbits.conf5
-rw-r--r--README.md139
-rw-r--r--TODO95
-rwxr-xr-xbuild.sh62
-rwxr-xr-xdebug-install.sh20
-rw-r--r--extra/gpu-screen-recorder.env9
-rw-r--r--extra/gpu-screen-recorder.service23
-rw-r--r--extra/gsr-nvidia.conf (renamed from gsr-nvidia.conf)0
-rwxr-xr-xextra/install_preserve_video_memory.sh (renamed from install_preserve_video_memory.sh)0
-rw-r--r--include/capture/capture.h46
-rw-r--r--include/capture/kms.h50
-rw-r--r--include/capture/kms_cuda.h20
-rw-r--r--include/capture/kms_vaapi.h11
-rw-r--r--include/capture/nvfbc.h7
-rw-r--r--include/capture/xcomposite.h51
-rw-r--r--include/capture/xcomposite_cuda.h9
-rw-r--r--include/capture/xcomposite_vaapi.h10
-rw-r--r--include/color_conversion.h27
-rw-r--r--include/cuda.h11
-rw-r--r--include/cursor.h6
-rw-r--r--include/defs.h28
-rw-r--r--include/egl.h116
-rw-r--r--include/shader.h2
-rw-r--r--include/sound.hpp2
-rw-r--r--include/utils.h36
-rwxr-xr-xinstall.sh14
-rwxr-xr-xinstall_coolbits.sh10
-rw-r--r--kms/client/kms_client.c291
-rw-r--r--kms/client/kms_client.h13
-rw-r--r--kms/kms_shared.h33
-rw-r--r--kms/server/kms_server.c418
-rw-r--r--kms/server/project.conf3
-rw-r--r--project.conf8
-rwxr-xr-xscripts/record-application-name.sh6
-rwxr-xr-xscripts/replay-application-name.sh6
-rwxr-xr-xscripts/replay.sh2
-rwxr-xr-xscripts/save-replay.sh2
-rwxr-xr-xscripts/start-replay.sh2
-rwxr-xr-xscripts/toggle-recording-selected.sh2
-rw-r--r--src/capture/capture.c361
-rw-r--r--src/capture/kms.c369
-rw-r--r--src/capture/kms_cuda.c181
-rw-r--r--src/capture/kms_vaapi.c582
-rw-r--r--src/capture/nvfbc.c445
-rw-r--r--src/capture/xcomposite.c299
-rw-r--r--src/capture/xcomposite_cuda.c496
-rw-r--r--src/capture/xcomposite_vaapi.c599
-rw-r--r--src/color_conversion.c342
-rw-r--r--src/cuda.c6
-rw-r--r--src/cursor.c28
-rw-r--r--src/egl.c508
-rw-r--r--src/main.cpp1490
-rw-r--r--src/overclock.c48
-rw-r--r--src/shader.c3
-rw-r--r--src/sound.cpp32
-rw-r--r--src/utils.c378
-rw-r--r--src/window_texture.c2
-rw-r--r--study/color_space_transform_matrix.pngbin0 -> 7166 bytes
-rwxr-xr-xstudy/create_matrix.py48
-rwxr-xr-xuninstall.sh9
61 files changed, 5050 insertions, 2776 deletions
diff --git a/.gitignore b/.gitignore
index 8bedc1d..aa01f09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@ compile_commands.json
tests/sibs-build/
tests/compile_commands.json
+external/wlr-export-dmabuf-unstable-v1-client-protocol.h
+external/wlr-export-dmabuf-unstable-v1-protocol.c
+
.clangd/
.cache/
.vscode/
@@ -14,3 +17,5 @@ gsr-kms-server
*.mp4
*.flv
+*.mkv
+*.mov
diff --git a/88-gsr-coolbits.conf b/88-gsr-coolbits.conf
deleted file mode 100644
index be665f1..0000000
--- a/88-gsr-coolbits.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-Section "Device"
- Identifier "Device0"
- Driver "nvidia"
- Option "Coolbits" "12"
-EndSection
diff --git a/README.md b/README.md
index 8ab3c1d..6efce5c 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,91 @@
+![](https://dec05eba.com/images/gpu_screen_recorder_logo_small.png)
+
# GPU Screen Recorder
-This is a screen recorder that has minimal impact on system performance by recording a window using the GPU only,
+This is a screen recorder that has minimal impact on system performance by recording your monitor using the GPU only,
similar to shadowplay on windows. This is the fastest screen recording tool for Linux.
This screen recorder can be used for recording your desktop offline, for live streaming and for nvidia shadowplay-like instant replay,
-where only the last few seconds are saved.
+where only the last few minutes are saved.
+
+Supported video codecs:
+* H264 (default on Intel)
+* HEVC (default on AMD and NVIDIA)
+* AV1 (not currently supported on NVIDIA if you use GPU Screen Recorder flatpak)
+
+Supported audio codecs:
+* Opus (default)
+* AAC
+* FLAC
## Note
-This software works only on X11 (Wayland with Xwayland is NOT supported).\
-If you are using a variable refresh rate monitor then choose to record "screen-direct-force". This will allow variable refresh rate to work when recording fullscreen applications. Note that some applications such as mpv will not work in fullscreen mode. A fix is being developed for this.
+This software works with x11 and wayland, but when using Wayland then only monitors can be recorded.
### TEMPORARY ISSUES
1) screen-direct capture has been temporary disabled as it causes issues with stuttering. This might be a nvfbc bug.
-2) Recording the monitor on steam deck might fail sometimes. This happens even when using ffmpeg directly. This might be a steam deck driver bug. Recording a single window doesn't have this issue.
-3) Videos created on AMD/Intel are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player.
-### AMD/Intel root permission
-When recording a window under AMD/Intel no special user permission is required, however when recording a monitor the program needs root permission (to access KMS).
-To make this safer, the part that needs root access has been moved to its own executable (to make it as small as possible) and a GUI sudo prompt is shown to run this executable as root. The executable is called "gsr-kms-server".
-However this doesn't work if you want to start replay at startup for example. To fix this, run: `sudo setcap cap_sys_admin+ep /usr/bin/gsr-kms-server` if you have installed GPU Screen Recorder.
-
+2) Videos are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player. You can try saving the video into a .mkv file instead as some software may have better support for .mkv files (such as kdenlive). You can use the "-fm cfr" option to to use constant framerate mode.
+3) HDR capture is supported (on wayland), but all GPU drivers have bugs that ignore HDR metadata so the HDR metadata will be missing in the video file. I will eventually patch the video file to workaround these GPU driver issues.
+4) Opus and FLAC audio codecs are disabled at the moment because of temporary issues.
+### AMD/Intel/Wayland root permission
+When recording a window under AMD/Intel no special user permission is required, however when recording a monitor (or when using wayland) the program needs root permission (to access KMS).\
+To make this safer, the part that needs root access has been moved to its own executable (to make it as small as possible).\
+For you as a user this only means that if you installed GPU Screen Recorder as a flatpak then a prompt asking for root password will show up when you start recording.
# Performance
On a system with a i5 4690k CPU and a GTX 1080 GPU:\
When recording Legend of Zelda Breath of the Wild at 4k, fps drops from 30 to 7 when using OBS Studio + nvenc, however when using this screen recorder the fps remains at 30.\
When recording GTA V at 4k on highest settings, fps drops from 60 to 23 when using obs-nvfbc + nvenc, however when using this screen recorder the fps only drops to 58. The quality is also much better when using gpu screen recorder.\
-It is recommended to save the video to a SSD because of the large file size, which a slow HDD might not be fast enough to handle.\
-Note that if you have a very powerful CPU and a not so powerful GPU and play a game that is bottlenecked by your GPU and barely uses your CPU then a CPU based screen recording (such as OBS with libx264 instead of nvenc) might perform slightly better than GPU Screen Recorder. At least on NVIDIA.
+GPU Screen Recorder also produces much smoother videos than OBS when GPU utilization is close to 100%, see comparison here: [https://www.youtube.com/watch?v=zfj4sNVLLLg](https://www.youtube.com/watch?v=zfj4sNVLLLg).\
+It is recommended to save the video to a SSD because of the large file size, which a slow HDD might not be fast enough to handle. Using variable framerate mode (-fm vfr) which is the default is also recommended as this reduces encoding load. Ultra quality is also overkill most of the time, very high (the default) or lower quality is usually enough.
## Note about optimal performance on NVIDIA
NVIDIA driver has a "feature" (read: bug) where it will downclock memory transfer rate when a program uses cuda (or nvenc, which uses cuda), such as GPU Screen Recorder. To work around this bug, GPU Screen Recorder can overclock your GPU memory transfer rate to it's normal optimal level.\
-To enable overclocking for optimal performance use the `-oc` option when running GPU Screen Recorder. You also need to have "Coolbits" NVIDIA X setting set to "12" to enable overclocking. You can automatically add this option if you run `sudo install_coolbits.sh` and then reboot your computer. This script is automatically run if you are using NVIDIA and run `install.sh`.\
+To enable overclocking for optimal performance use the `-oc` option when running GPU Screen Recorder. You also need to have "Coolbits" NVIDIA X setting set to "12" to enable overclocking. You can automatically add this option if you run `sudo nvidia-xconfig --cool-bits=12` and then reboot your computer.\
Note that this only works when Xorg server is running as root, and using this option will only give you a performance boost if the game you are recording is bottlenecked by your GPU.\
Note! use at your own risk!
-## Note about optimal performance on AMD/Intel
-Performance is the same when recording a single window or the monitor, however in some cases, such as when gpu usage is 100%, the video capture rate might be slower than the games fps when recording a single window instead of a monitor. Recording the monitor instead is recommended in such cases.
+# VRR/G-SYNC
+This should work fine on AMD/Intel X11 or Wayland. On Nvidia X11 G-SYNC only works with the -w screen-direct-force option, but because of bugs in the Nvidia driver this option is not always recommended.
+For example it can cause your computer to freeze when recording certain games.
# Installation
If you are running an Arch Linux based distro, then you can find gpu screen recorder on aur under the name gpu-screen-recorder-git (`yay -S gpu-screen-recorder-git`).\
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
You can also install gpu screen recorder ([the gtk gui version](https://git.dec05eba.com/gpu-screen-recorder-gtk/)) from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder), which is the easiest method
-to install GPU Screen Recorder on non-arch based distros.
+to install GPU Screen Recorder on non-arch based distros.\
+The only official ways to install GPU Screen Recorder is either from source, AUR or flathub. If you install GPU Screen Recorder from somewhere else and have an issue then try installing it
+from one of the official sources before reporting it as an issue.
+If you install GPU Screen Recorder flatpak, which is the gtk gui version then you can still run GPU Screen Recorder command line by using the flatpak command option, for example `flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4`. Note that if you want to record your monitor on AMD/Intel then you need to install the flatpak system-wide (like so: `flatpak install flathub --system com.dec05eba.gpu_screen_recorder`).
# Dependencies
## AMD
-`libglvnd (which provides libgl and libegl), mesa, ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter), libx11, libxcomposite, libxrandr, libxfixes, libpulse, libva, libva-mesa-driver, libdrm, libcap, polkit (for pkexec)`.
+libglvnd (which provides libgl and libegl)\
+mesa\
+ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\
+x11 (libx11, libxcomposite, libxrandr, xfixes)\
+libpulse\
+vaapi (libva, libva-mesa-driver)\
+libdrm\
+libcap\
+wayland-client
## Intel
-`libglvnd (which provides libgl and libegl), mesa, ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter), libx11, libxcomposite, libxrandr, libxfixes, libpulse, libva, libva-intel-driver, libdrm, libcap, polkit (for pkexec)`.
+libglvnd (which provides libgl and libegl)\
+mesa\
+ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\
+x11 (libx11, libxcomposite, libxrandr, xfixes)\
+libpulse\
+vaapi (libva, intel-media-driver/libva-intel-driver)\
+libdrm\
+libcap\
+wayland-client
## NVIDIA
-`libglvnd (which provides libgl and libegl), ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter), libx11, libxcomposite, libxrandr, libxfixes, libpulse, cuda (libnvidia-compute), nvenc (libnvidia-encode), libva, libdrm, libcap`. Additionally, you need to have `nvfbc (libnvidia-fbc1)` installed when using nvfbc and `xnvctrl (libxnvctrl0)` when using the `-oc` option.
+libglvnd (which provides libgl and libegl)\
+ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\
+x11 (libx11, libxcomposite, libxrandr, xfixes)\
+libpulse\
+cuda runtime (libcuda.so.1) (libnvidia-compute)\
+nvenc (libnvidia-encode)\
+libva\
+libdrm\
+libcap\
+wayland-client\
+nvfbc (libnvidia-fbc1, when recording the screen on x11)\
+xnvctrl (libxnvctrl0, when using the `-oc` option)
# How to use
Run `gpu-screen-recorder --help` to see all options.
@@ -52,9 +94,13 @@ Here is an example of how to record all monitors and the default audio output: `
## Streaming
Streaming works the same as recording, but the `-o` argument should be path to the live streaming service you want to use (including your live streaming key). Take a look at scripts/twitch-stream.sh to see an example of how to stream to twitch.
## Replay mode
-Run `gpu-screen-recorder` with the `-c mp4` and `-r` option, for example: `gpu-screen-recorder -w screen -f 60 -r 30 -c mp4 -o ~/Videos`. Note that in this case, `-o` should point to a directory (that exists).
-To save a video in replay mode, you need to send signal SIGUSR1 to gpu screen recorder. You can do this by running `killall -SIGUSR1 gpu-screen-recorder`.
-To stop recording, send SIGINT to gpu screen recorder. You can do this by running `killall gpu-screen-recorder` or pressing `Ctrl-C` in the terminal that runs gpu screen recorder.
+Run `gpu-screen-recorder` with the `-c mp4` and `-r` option, for example: `gpu-screen-recorder -w screen -f 60 -r 30 -c mp4 -o ~/Videos`. Note that in this case, `-o` should point to a directory.\
+If `-mf yes` is set, replays are save in folders based on the date.
+To save a video in replay mode, you need to send signal SIGUSR1 to gpu screen recorder. You can do this by running `killall -SIGUSR1 gpu-screen-recorder`.\
+To stop recording send SIGINT to gpu screen recorder. You can do this by running `killall -SIGINT gpu-screen-recorder` or pressing `Ctrl-C` in the terminal that runs gpu screen recorder.\
+To pause/unpause recording send SIGUSR2 to gpu screen recorder. You can do this by running `killall -SIGUSR2 gpu-screen-recorder`. This is only applicable and useful when recording (not streaming nor replay).\
+The file path to the saved replay is output to stdout. All other output from GPU Screen Recorder is output to stderr.\
+The replay buffer is stored in ram (as encoded video), so don't use a too large replay time and/or video quality unless you have enough ram to store it.
## Finding audio device name
You can find the default output audio device (headset, speakers (in other words, desktop audio)) with the command `pactl get-default-sink`. Add `monitor` to the end of that to use that as an audio input in gpu screen recorder.\
You can find the default input audio device (microphone) with the command `pactl get-default-source`. This input should not have `monitor` added to the end when used in gpu screen recorder.\
@@ -66,11 +112,24 @@ for example `-a "$(pactl get-default-sink).monitor|$(pactl get-default-source)"`
There is also a gui for the gpu screen recorder called [gpu-screen-recorder-gtk](https://git.dec05eba.com/gpu-screen-recorder-gtk/).
## Simple way to run replay without gui
Run the script `scripts/start-replay.sh` to start replay and then `scripts/save-replay.sh` to save a replay and `scripts/stop-replay.sh` to stop the replay. The videos are saved to `$HOME/Videos`.
-You can use these scripts to start replay at system startup if you add `scripts/start-replay.sh` to startup (this can be done differently depending on your desktop environment / window manager) and then go into hotkey settings on your system and choose a hotkey to run the script `scripts/save-replay.sh`. Modify `scripts/start-replay.sh` if you want to use other replay options.
+You can use these scripts to start replay at system startup if you add `scripts/start-replay.sh` to startup (this can be done differently depending on your desktop environment / window manager) and then go into
+hotkey settings on your system and choose a hotkey to run the script `scripts/save-replay.sh`. Modify `scripts/start-replay.sh` if you want to use other replay options.
+## Run replay on system startup
+If you installed GPU Screen Recorder from AUR or from source and you are running a distro that uses systemd then you will have a systemd service installed that can be started with `systemctl enable --now --user gpu-screen-recorder`. This systemd service runs GPU Screen Recorder on system startup.\
+It's configured with `$HOME/.config/gpu-screen-recorder.env` (create it if it doesn't exist). You can look at [extra/gpu-screen-recorder.env](https://git.dec05eba.com/gpu-screen-recorder/plain/extra/gpu-screen-recorder.env) to see an example.
+You can see which variables that you can use in the `gpu-screen-recorder.env` file by looking at the `extra/gpu-screen-recorder.service` file. Note that all of the variables are optional, you only have to set the ones that are you interested in.
+You can use the `scripts/save-replay.sh` script to save a replay and by default the systemd service saves videos in `$HOME/Videos`.\
+If you are using a NVIDIA GPU then it's recommended to set PreserveVideoMemoryAllocations=1 as mentioned in the section below.
+## Examples
+Look at the [scripts](https://git.dec05eba.com/gpu-screen-recorder/tree/scripts) directory for script examples. For example if you want to automatically save a recording/replay into a folder with the same name as the game you are recording.
+# Issues
+## NVIDIA
+Nvidia drivers have an issue where CUDA breaks if CUDA is running when suspend/hibernation happens, and it remains broken until you reload the nvidia driver. To fix this, either disable suspend or tell the NVIDIA driver to preserve video memory on suspend/hibernate by using the `NVreg_PreserveVideoMemoryAllocations=1` option. You can run `sudo extra/install_preserve_video_memory.sh` to automatically add that option to your system.
-## Issues
-### NVIDIA
-Nvidia drivers have an issue where CUDA breaks if CUDA is running when suspend/hibernation happens, and it remains broken until you reload the nvidia driver. To fix this, either disable suspend or tell the NVIDIA driver to preserve video memory on suspend/hibernate by using the `NVreg_PreserveVideoMemoryAllocations=1` option. You can run `install_preserve_video_memory.sh` to automatically add that option to your system.
+# Reporting bugs
+Issues are reported on this Github page: [https://github.com/dec05eba/gpu-screen-recorder-issues/issues](https://github.com/dec05eba/gpu-screen-recorder-issues/issues)
+# Contributing patches
+See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about)
# Demo
[![Click here to watch a demo video on youtube](https://img.youtube.com/vi/n5tm0g01n6A/0.jpg)](https://www.youtube.com/watch?v=n5tm0g01n6A)
@@ -84,15 +143,35 @@ The plugin does everything on the GPU and gives the texture to OBS, but OBS does
## How is this different from using FFMPEG with x11grab and nvenc?
FFMPEG only uses the GPU with CUDA when doing transcoding from an input video to an output video, and not when recording the screen when using x11grab. So FFMPEG has the same fps drop issues that OBS has.
## It tells me that my AMD/Intel GPU is not supported or that my GPU doesn't support h264/hevc, but that's not true!
-Some linux distros disable hardware accelerated h264/hevc on AMD/Intel because of "patent license issues". If you are using an arch-based distro then you can install mesa-git instead of mesa and if you are using another distro then you may have to switch to a better distro.
+Some linux distros (such as manjaro and fedora) disable hardware accelerated h264/hevc on AMD/Intel because of "patent license issues". If you are using an arch-based distro then you can install mesa-git instead of mesa and if you are using another distro then you may have to switch to a better distro. On fedora based distros you can follow this: [Hardware Accelerated Codec](https://rpmfusion.org/Howto/Multimedia).\
+If you installed GPU Screen Recorder flatpak then you can try installing mesa-extra freedesktop runtime by running this command: `flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra`
+## I have an old nvidia GPU that supports nvenc but I get a cuda error when trying to record
+Newer ffmpeg versions don't support older nvidia cards. Try installing GPU Screen Recorder flatpak from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) instead. It comes with an older ffmpeg version which might work for your GPU.
+## I get a black screen/glitches while live streaming
+It seems like ffmpeg earlier than version 6.1 has some type of bug. Install ffmpeg 6.1 and then reinstall GPU Screen Recorder to fix this issue. The flatpak version of GPU Screen Recorder comes with ffmpeg 6.1 so no extra steps are needed.
+## I can't play the video in my browser directly or in discord
+Browsers and discord don't support hevc video codec at the moment. Choose h264 video codec instead with the -k h264 option.
+Note that websites such as youtube support hevc so there is no need to choose h264 video codec if you intend to upload the video to youtube or if you want to play the video locally or if you intend to
+edit the video with a video editor. Hevc allows for better video quality (especially at lower file sizes) so hevc (or av1) is recommended for source videos.
+## I get a black bar on the right/bottom in the video
+This is mostly an issue on AMD. For av1 it's a hardware issue, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9185. For hevc it's a software issue that has been fixed but not released yet, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10985.
+If you get black bars then the workaround is to record with h264 video codec instead (using the -k h264 option).
+## The video is glitched, looks like checkerboard pattern
+This is an issue on some intel integrated gpus on wayland caused by power saving option. Right now the only way to fix this is to record on X11 instead.
+## The video doesn't display or has a green/yellow overlay
+This can happen if your video player is missing the H264/HEVC video codecs. Either install the codecs or use mpv.
+## I get stutter in the video
+Try recording to an SSD and make sure it's not using NTFS file system. Also record in variable framerate format.
+## I get a black screen when recording
+This can happen if you use software such as prime-run to run GPU Screen Recorder. Such software should not be used to run GPU Screen Recorder.
+GPU Screen Recorder needs to run on the same GPU that you use to display your monitors graphics to work.
# Donations
-If you really want to donate, you can donate via bitcoin or monero.
+If you want to donate you can donate via bitcoin or monero.
* Bitcoin: bc1qqvuqnwrdyppf707ge27fqz2n9y9gu7lf5ypyuf
* Monero: 4An9kp2qW1C9Gah7ewv4JzcNFQ5TAX7ineGCqXWK6vQnhsGGcRpNgcn8r9EC3tMcgY7vqCKs3nSRXhejMHBaGvFdN2egYet
# TODO
* Dynamically change bitrate/resolution to match desired fps. This would be helpful when streaming for example, where the encode output speed also depends on upload speed to the streaming service.
-* Show cursor when recording a window. Currently the cursor is only visible when recording a monitor.
* Implement opengl injection to capture texture. This fixes VRR without having to use NvFBC direct capture.
* Always use direct capture with NvFBC once the capture issue in mpv fullscreen has been resolved (maybe detect if direct capture fails in nvfbc and switch to non-direct recording. NvFBC says if direct capture fails).
diff --git a/TODO b/TODO
index 2cf6b70..5211e9f 100644
--- a/TODO
+++ b/TODO
@@ -9,7 +9,6 @@ Allow recording a region by recording the compositor proxy window / nvfbc window
Use nvenc directly, which allows removing the use of cuda.
Handle xrandr monitor change in nvfbc.
Implement follow focused in drm.
-Support fullscreen capture on amd/intel using external kms process.
Support amf and qsv.
Disable flipping on nvidia? this might fix some stuttering issues on some setups. See NvCtrlGetAttribute/NvCtrlSetAttributeAndGetStatus NV_CTRL_SYNC_TO_VBLANK https://github.com/NVIDIA/nvidia-settings/blob/d5f022976368cbceb2f20b838ddb0bf992f0cfb9/src/gtk%2B-2.x/ctkopengl.c.
Replays seem to have some issues with audio/video. Why?
@@ -35,23 +34,9 @@ NVreg_RegistryDwords.
Restore nvfbc screen recording on monitor reconfiguration.
Window capture doesn't work properly in _control_ game after going from pause menu to in-game (and back to pause menu). There might be some x11 event we need to catch. Same for vr-video-player.
-Fix constant framerate not working properly on amd/intel because capture framerate gets locked to the same framerate as game framerate, which doesn't work well when you need to encode multiple duplicate frames. We can skip multiple encode if we duplicate frame once and then use that same frame data as the difference between frames will be exactly the same, but hevc complains about that. Is there a way to make hevc shut up?
-
-JPEG color range on amd seems to produce too bright video with h264 but not hevc, why?
-
-Properly handle monitor reconfiguration (kms vaapi, nvfbc).
-
-Better configure vaapi. The file size is too large.
-Better colors for vaapi. It looks a bit off when recording vscode for example.
-
-Clear vaapi surface (for focused window).
-
-Window capture performance on steam deck isn't good when playing the witcher 3 for example. The capture itself is fine but video encoding puts it to 30fps even if the game runs at 57 fps.
-
Monitor capture on steam deck is slightly below the game fps, but only when capturing on the steam deck screen. If capturing on another monitor, there is no issue.
Is this related to the dma buf rotation issue? different modifier being slow? does this always happen?
-Make sure rgb to yuv color conversion is 100% correct.
Fallback to vaapi copy in kms if opengl version fails. This can happen on steam deck for some reason (driver bug?). Also vaapi copy uses less gpu since it uses video codec unit to copy.
Test if vaapi copy version uses less memory than opengl version.
@@ -59,10 +44,80 @@ Intel is a bit weird with monitor capture and multiple monitors. If one of the m
Is that only the case when the primary monitor is rotated? Also the primary monitor becomes position 0, 0 so crtc (x11 randr) position doesn't match the drm pos. Maybe get monitor position and size from drm instead.
How about if multiple monitors are rotated?
-When using multiple monitors kms grab the target monitor instead of the whole screen.
-Enable opus/flac again. It's broken right now when merging audio inputs. The audio gets a lot of static noise!
+Support vp8/vp9. This is especially important on amd which on some distros (such as Manjaro) where hardware accelerated h264/hevc is disabled in the mesa package.
+
+Support screen (all monitors) capture on amd/intel and nvidia wayland when no combined plane is found. Right now screen just takes the first output.
+Use separate plane (which has offset and pitch) from combined plane instead of the combined plane.
+
+Both twitch and youtube support variable bitrate but twitch recommends constant bitrate to reduce stream buffering/dropped frames when going from low motion to high motion: https://help.twitch.tv/s/article/broadcasting-guidelines?language=en_US. Info for youtube: https://support.google.com/youtube/answer/2853702?hl=en#zippy=%2Cvariable-bitrate-with-custom-stream-keys-in-live-control-room%2Ck-p-fps%2Cp-fps.
+
+Limit fps recording with x damage. This is good when running replay mode 24/7 and being afk or when not much is happening on the screen.
+
+On nvidia some games apparently causes the game to appear to stutter (without dropping fps) when recording a monitor but not using
+ when using direct screen capture. Observed in Deus Ex and Apex Legends.
+
+Support "screen" (all monitors) capture on wayland. This should be done by getting all drm fds and multiple EGL_DMA_BUF_PLANEX_FD_EXT to create one egl image with all fds combined.
+
+Support pipewire screen capture?
+CPU usage is pretty high on AMD/Intel/(Nvidia(wayland)), why? opening and closing fds, creating egl, cuda association, is slow when done every frame. Test if desktop portal screencast has better performance.
+
+Capture is broken on amd on wlroots. It's disabled at the moment and instead uses kms capture. Find out why we get a black screen in wlroots.
+
+Support vulkan video encoding. That might workaround forced p2 state nvidia driver "bug". Ffmpeg supports vulkan video encoding if it's encoding with --enable-vulkan
+
+It may be possible to improve color conversion rgb->yuv shader for color edges by biasing colors to an edge, instead of letting color overlaying with bilinear filtering handle it.
+
+When webcam is supported mention that nvidia_drm.modeset=1 must be set on nvidia x11 (it's required on wayland so it's not needed there. Or does eglstream work without it??). Check if this really is the case.
+ Support green screen removal, cropping, shader effects in general (circle mask, rounded corners, etc).
+
+Preset is set to p5 for now but it should ideally be p6 or p7.
+ This change is needed because for certain sizes of a window (or monitor?) such as 971x780 causes encoding to freeze
+ when using h264 codec. This is a new(?) nvidia driver bug.
+ Maybe dont choose p6 or p7 again? it causes micro stutter for some users (?).
+
+For low latency, see https://developer.download.nvidia.com/compute/nvenc/v4.0/NVENC_VideoEncoder_API_ProgGuide.pdf (section 7.1).
+Remove follow focused option.
+
+Exit if X11/Wayland killed (if drm plane dead or something?)
+
+Use SRC_W and SRC_H for screen plane instead of crtc_w and crtc_h.
+
+Make it possible to select which /dev/dri/card* to use, but that requires opengl to also use the same card. Not sure if that is possible for amd, intel and nvidia without using vulkan instead.
+
+Support I915_FORMAT_MOD_Y_TILED_CCS (and other power saving modifiers, see https://trac.ffmpeg.org/ticket/8542). The only fix may be to use desktop portal for recording. This issue doesn't appear on x11 since these modifiers are not used by xorg server.
+
+Test if p2 state can be worked around by using pure nvenc api and overwriting cuInit/cuCtxCreate* to not do anything. Cuda might be loaded when using nvenc but it might not be used, with certain record options? (such as h264 p5).
+ nvenc uses cuda when using b frames and rgb->yuv conversion, so convert the image ourselves instead.-
+
+Mesa doesn't support global headers (AV_CODEC_FLAG_GLOBAL_HEADER) with h264... which also breaks mkv since mkv requires global header. Right now gpu screen recorder will forcefully set video codec to hevc when h264 is requested for mkv files.
+
+Drop frames if live streaming cant keep up with target fps, or dynamically change resolution/quality.
+
+Support low power option (does it even work with vaapi in ffmpeg??). Would be very useful for steam deck.
+
+Instead of sending a big list of drm data back to kms client, send the monitor we want to record to kms server and the server should respond with only the matching monitor, and cursor.
+
+Tonemap hdr to sdr when hdr is enabled and when hevc_hdr/av1_hdr is not used.
+
+Add 10 bit record option, h264_10bit, hevc_10bit and av1_10bit.
+
+Rotate cursor texture properly (around top left origin).
+
+Setup hardware video context so we can query constraints and capabilities for better default and better error messages.
+
+Use CAP_SYS_NICE in flatpak too on the main gpu screen recorder binary. It makes recording smoother, especially with constant framerate.
+
+Show error when using compressed kms plane which isn't supported. Also do that in the gui.
+
+Modify ffmpeg to accept opengl texture for nvenc encoding. Removes extra buffers and copies.
+
+When vulkan encode is added, mention minimum nvidia driver required. (550.54.14?).
+
+Support drm plane rotation. Neither X11 nor any Wayland compositor currently rotates drm planes so this might not be needed.
+
+Investigate if there is a way to do gpu->gpu copy directly without touching system ram to enable video encoding on a different gpu. On nvidia this is possible with cudaMemcpyPeer, but how about from an intel/amd gpu to an nvidia gpu or the other way around or any combination of iGPU and dedicated GPU?
+ Maybe something with clEnqueueMigrateMemObjects? on AMD something with DirectGMA maybe?
-Support vp8/vp9/av1. This is especially important on amd which on some distros (such as Manjaro) where hardware accelerated h264/hevc is disabled in the mesa package.
+Fix opus/flac ( variable framerate audio :( ). Going back to constant framerate audio should fix the issue with skipped frames when recording for some people (issue only reproducable with pulseaudio, and only for some users?).
-Support screen (all monitors) capture on amd/intel when no combined plane is found.
-Use separate plane (which has offset and pitch) from combined plane instead of the combined plane. \ No newline at end of file
+Go back to using pure vaapi without opengl for video encoding? rotation (transpose) can be done if its done after (rgb to yuv) color conversion.
diff --git a/build.sh b/build.sh
index 27e4268..b353406 100755
--- a/build.sh
+++ b/build.sh
@@ -1,41 +1,53 @@
#!/bin/sh -e
-opts="-O2 -g0 -DNDEBUG -Wall -Wextra -s"
-[ -n "$DEBUG" ] && opts="-O0 -g3 -Wall -Wextra";
+script_dir=$(dirname "$0")
+cd "$script_dir"
+
+CC=${CC:-gcc}
+CXX=${CXX:-g++}
+
+opts="-O2 -g0 -DNDEBUG -Wall -Wextra -Wshadow"
+[ -n "$DEBUG" ] && opts="-O0 -g3 -Wall -Wextra -Wshadow";
build_gsr_kms_server() {
+ # TODO: -fcf-protection=full, not supported on arm
+ extra_opts="-fstack-protector-all"
dependencies="libdrm"
includes="$(pkg-config --cflags $dependencies)"
libs="$(pkg-config --libs $dependencies) -ldl"
- gcc -c kms/server/kms_server.c $opts $includes
- gcc -o gsr-kms-server -O2 kms_server.o $libs $opts
+ $CC -c kms/server/kms_server.c $opts $extra_opts $includes
+ $CC -o gsr-kms-server kms_server.o $libs $opts $extra_opts
}
build_gsr() {
- dependencies="libavcodec libavformat libavutil x11 xcomposite xrandr xfixes libpulse libswresample libavfilter libva libcap"
+ dependencies="libavcodec libavformat libavutil x11 xcomposite xrandr xfixes libpulse libswresample libavfilter libva libcap libdrm wayland-egl wayland-client"
includes="$(pkg-config --cflags $dependencies)"
libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm"
- gcc -c src/capture/capture.c $opts $includes
- gcc -c src/capture/nvfbc.c $opts $includes
- gcc -c src/capture/xcomposite_cuda.c $opts $includes
- gcc -c src/capture/xcomposite_vaapi.c $opts $includes
- gcc -c src/capture/kms_vaapi.c $opts $includes
- gcc -c kms/client/kms_client.c $opts $includes
- gcc -c src/egl.c $opts $includes
- gcc -c src/cuda.c $opts $includes
- gcc -c src/xnvctrl.c $opts $includes
- gcc -c src/overclock.c $opts $includes
- gcc -c src/window_texture.c $opts $includes
- gcc -c src/shader.c $opts $includes
- gcc -c src/color_conversion.c $opts $includes
- gcc -c src/cursor.c $opts $includes
- gcc -c src/utils.c $opts $includes
- gcc -c src/library_loader.c $opts $includes
- g++ -c src/sound.cpp $opts $includes
- g++ -c src/main.cpp $opts $includes
- g++ -o gpu-screen-recorder -O2 capture.o nvfbc.o kms_client.o egl.o cuda.o xnvctrl.o overclock.o window_texture.o shader.o color_conversion.o cursor.o utils.o library_loader.o xcomposite_cuda.o xcomposite_vaapi.o kms_vaapi.o sound.o main.o $libs $opts
+ $CC -c src/capture/capture.c $opts $includes
+ $CC -c src/capture/nvfbc.c $opts $includes
+ $CC -c src/capture/xcomposite.c $opts $includes
+ $CC -c src/capture/xcomposite_cuda.c $opts $includes
+ $CC -c src/capture/xcomposite_vaapi.c $opts $includes
+ $CC -c src/capture/kms_vaapi.c $opts $includes
+ $CC -c src/capture/kms_cuda.c $opts $includes
+ $CC -c src/capture/kms.c $opts $includes
+ $CC -c kms/client/kms_client.c $opts $includes
+ $CC -c src/egl.c $opts $includes
+ $CC -c src/cuda.c $opts $includes
+ $CC -c src/xnvctrl.c $opts $includes
+ $CC -c src/overclock.c $opts $includes
+ $CC -c src/window_texture.c $opts $includes
+ $CC -c src/shader.c $opts $includes
+ $CC -c src/color_conversion.c $opts $includes
+ $CC -c src/utils.c $opts $includes
+ $CC -c src/library_loader.c $opts $includes
+ $CC -c src/cursor.c $opts $includes
+ $CXX -c src/sound.cpp $opts $includes
+ $CXX -c src/main.cpp $opts $includes
+ $CXX -o gpu-screen-recorder capture.o nvfbc.o kms_client.o egl.o cuda.o xnvctrl.o overclock.o window_texture.o shader.o \
+ color_conversion.o utils.o library_loader.o cursor.o xcomposite.o xcomposite_cuda.o xcomposite_vaapi.o kms_vaapi.o kms_cuda.o kms.o sound.o main.o $libs $opts
}
build_gsr_kms_server
build_gsr
-echo "Successfully built gpu-screen-recorder" \ No newline at end of file
+echo "Successfully built gpu-screen-recorder"
diff --git a/debug-install.sh b/debug-install.sh
new file mode 100755
index 0000000..096eefa
--- /dev/null
+++ b/debug-install.sh
@@ -0,0 +1,20 @@
+#!/bin/sh -e
+
+script_dir=$(dirname "$0")
+cd "$script_dir"
+
+[ $(id -u) -ne 0 ] && echo "You need root privileges to run the install script" && exit 1
+
+DEBUG=1 ./build.sh
+
+install -Dm755 "gsr-kms-server" "/usr/bin/gsr-kms-server"
+install -Dm755 "gpu-screen-recorder" "/usr/bin/gpu-screen-recorder"
+if [ -d "/usr/lib/systemd/user" ]; then
+ install -Dm644 "extra/gpu-screen-recorder.service" "/usr/lib/systemd/user/gpu-screen-recorder.service"
+fi
+# Not necessary, but removes the password prompt when trying to record a monitor on amd/intel or nvidia wayland
+setcap cap_sys_admin+ep /usr/bin/gsr-kms-server
+# Not necessary, but allows use of EGL_CONTEXT_PRIORITY_LEVEL_IMG which allows gpu screen recorder to run without being limited to game fps under heavy load on AMD/Intel
+setcap cap_sys_nice+ep /usr/bin/gpu-screen-recorder
+
+echo "Successfully installed gpu-screen-recorder (debug)"
diff --git a/extra/gpu-screen-recorder.env b/extra/gpu-screen-recorder.env
new file mode 100644
index 0000000..c4728cc
--- /dev/null
+++ b/extra/gpu-screen-recorder.env
@@ -0,0 +1,9 @@
+WINDOW=screen
+CONTAINER=mp4
+QUALITY=very_high
+CODEC=hevc
+AUDIO_CODEC=opus
+AUDIO_DEVICE=alsa_output.pci-0000_0a_00.4.iec958-stereo.monitor
+FRAMERATE=60
+REPLAYDURATION=60
+OUTPUTDIR=/run/media/dec05eba/SSD1TB/Videos/aaaa \ No newline at end of file
diff --git a/extra/gpu-screen-recorder.service b/extra/gpu-screen-recorder.service
new file mode 100644
index 0000000..0ba621b
--- /dev/null
+++ b/extra/gpu-screen-recorder.service
@@ -0,0 +1,23 @@
+[Unit]
+Description=GPU Screen Recorder Service
+
+[Service]
+EnvironmentFile=-%h/.config/gpu-screen-recorder.env
+Environment=WINDOW=screen
+Environment=CONTAINER=mp4
+Environment=QUALITY=very_high
+Environment=CODEC=auto
+Environment=AUDIO_CODEC=opus
+Environment=AUDIO_DEVICE=
+Environment=FRAMERATE=60
+Environment=REPLAYDURATION=30
+Environment=OUTPUTDIR=%h/Videos
+Environment=MAKEFOLDERS=no
+Environment=COLOR_RANGE=limited
+ExecStart=/bin/sh -c 'AUDIO="${AUDIO_DEVICE:-$(pactl get-default-sink).monitor}"; gpu-screen-recorder -v no -w $WINDOW -c $CONTAINER -q $QUALITY -k $CODEC -ac $AUDIO_CODEC -a "$AUDIO" -f $FRAMERATE -r $REPLAYDURATION -o "$OUTPUTDIR" -mf $MAKEFOLDERS $ADDITIONAL_ARGS -cr $COLOR_RANGE'
+KillSignal=SIGINT
+Restart=on-failure
+RestartSec=5s
+
+[Install]
+WantedBy=default.target
diff --git a/gsr-nvidia.conf b/extra/gsr-nvidia.conf
index 10cbf7d..10cbf7d 100644
--- a/gsr-nvidia.conf
+++ b/extra/gsr-nvidia.conf
diff --git a/install_preserve_video_memory.sh b/extra/install_preserve_video_memory.sh
index c5cf658..c5cf658 100755
--- a/install_preserve_video_memory.sh
+++ b/extra/install_preserve_video_memory.sh
diff --git a/include/capture/capture.h b/include/capture/capture.h
index edcc14d..2eb8e42 100644
--- a/include/capture/capture.h
+++ b/include/capture/capture.h
@@ -1,30 +1,68 @@
#ifndef GSR_CAPTURE_CAPTURE_H
#define GSR_CAPTURE_CAPTURE_H
+#include "../color_conversion.h"
#include <stdbool.h>
typedef struct AVCodecContext AVCodecContext;
typedef struct AVFrame AVFrame;
+typedef void* VADisplay;
+typedef struct _VADRMPRIMESurfaceDescriptor VADRMPRIMESurfaceDescriptor;
+typedef struct gsr_cuda gsr_cuda;
+typedef struct AVFrame AVFrame;
+typedef struct CUgraphicsResource_st *CUgraphicsResource;
+typedef struct CUarray_st *CUarray;
+typedef struct CUctx_st *CUcontext;
+typedef struct CUstream_st *CUstream;
typedef struct gsr_capture gsr_capture;
struct gsr_capture {
/* These methods should not be called manually. Call gsr_capture_* instead */
- int (*start)(gsr_capture *cap, AVCodecContext *video_codec_context);
- void (*tick)(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame); /* can be NULL */
+ int (*start)(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame);
+ void (*tick)(gsr_capture *cap, AVCodecContext *video_codec_context); /* can be NULL */
bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL */
int (*capture)(gsr_capture *cap, AVFrame *frame);
+ void (*capture_end)(gsr_capture *cap, AVFrame *frame); /* can be NULL */
void (*destroy)(gsr_capture *cap, AVCodecContext *video_codec_context);
void *priv; /* can be NULL */
bool started;
};
-int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context);
-void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame);
+typedef struct gsr_capture_base gsr_capture_base;
+
+struct gsr_capture_base {
+ gsr_egl *egl;
+
+ unsigned int input_texture;
+ unsigned int target_textures[2];
+ unsigned int cursor_texture;
+
+ gsr_color_conversion color_conversion;
+
+ AVCodecContext *video_codec_context;
+};
+
+typedef struct {
+ gsr_cuda *cuda;
+ CUgraphicsResource *cuda_graphics_resources;
+ CUarray *mapped_arrays;
+} gsr_cuda_context;
+
+int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame);
+void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context);
bool gsr_capture_should_stop(gsr_capture *cap, bool *err);
int gsr_capture_capture(gsr_capture *cap, AVFrame *frame);
+void gsr_capture_end(gsr_capture *cap, AVFrame *frame);
/* Calls |gsr_capture_stop| as well */
void gsr_capture_destroy(gsr_capture *cap, AVCodecContext *video_codec_context);
+bool gsr_capture_base_setup_vaapi_textures(gsr_capture_base *self, AVFrame *frame, VADisplay va_dpy, VADRMPRIMESurfaceDescriptor *prime, gsr_color_range color_range);
+bool gsr_capture_base_setup_cuda_textures(gsr_capture_base *self, AVFrame *frame, gsr_cuda_context *cuda_context, gsr_color_range color_range, gsr_source_color source_color, bool hdr);
+void gsr_capture_base_stop(gsr_capture_base *self);
+
+bool drm_create_codec_context(const char *card_path, AVCodecContext *video_codec_context, int width, int height, bool hdr, VADisplay *va_dpy);
+bool cuda_create_codec_context(CUcontext cu_ctx, AVCodecContext *video_codec_context, int width, int height, bool hdr, CUstream *cuda_stream);
+
#endif /* GSR_CAPTURE_CAPTURE_H */
diff --git a/include/capture/kms.h b/include/capture/kms.h
new file mode 100644
index 0000000..674813a
--- /dev/null
+++ b/include/capture/kms.h
@@ -0,0 +1,50 @@
+#ifndef GSR_CAPTURE_KMS_H
+#define GSR_CAPTURE_KMS_H
+
+#include "capture.h"
+#include "../../kms/client/kms_client.h"
+#include "../color_conversion.h"
+#include "../vec2.h"
+#include "../defs.h"
+#include <stdbool.h>
+
+typedef struct AVCodecContext AVCodecContext;
+typedef struct AVMasteringDisplayMetadata AVMasteringDisplayMetadata;
+typedef struct AVContentLightMetadata AVContentLightMetadata;
+typedef struct gsr_capture_kms gsr_capture_kms;
+typedef struct gsr_egl gsr_egl;
+typedef struct AVFrame AVFrame;
+
+#define MAX_CONNECTOR_IDS 32
+
+typedef struct {
+ uint32_t connector_ids[MAX_CONNECTOR_IDS];
+ int num_connector_ids;
+} MonitorId;
+
+struct gsr_capture_kms {
+ gsr_capture_base base;
+
+ bool should_stop;
+ bool stop_is_error;
+
+ gsr_kms_client kms_client;
+ gsr_kms_response kms_response;
+
+ vec2i capture_pos;
+ vec2i capture_size;
+ MonitorId monitor_id;
+
+ AVMasteringDisplayMetadata *mastering_display_metadata;
+ AVContentLightMetadata *light_metadata;
+
+ gsr_monitor_rotation monitor_rotation;
+};
+
+/* Returns 0 on success */
+int gsr_capture_kms_start(gsr_capture_kms *self, const char *display_to_capture, gsr_egl *egl, AVCodecContext *video_codec_context, AVFrame *frame);
+void gsr_capture_kms_stop(gsr_capture_kms *self);
+bool gsr_capture_kms_capture(gsr_capture_kms *self, AVFrame *frame, bool hdr, bool screen_plane_use_modifiers, bool cursor_texture_is_external, bool record_cursor);
+void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self);
+
+#endif /* GSR_CAPTURE_KMS_H */
diff --git a/include/capture/kms_cuda.h b/include/capture/kms_cuda.h
new file mode 100644
index 0000000..fd0d396
--- /dev/null
+++ b/include/capture/kms_cuda.h
@@ -0,0 +1,20 @@
+#ifndef GSR_CAPTURE_KMS_CUDA_H
+#define GSR_CAPTURE_KMS_CUDA_H
+
+#include "../vec2.h"
+#include "../utils.h"
+#include "../color_conversion.h"
+#include "capture.h"
+
+typedef struct {
+ gsr_egl *egl;
+ const char *display_to_capture; /* if this is "screen", then the first monitor is captured. A copy is made of this */
+ gsr_gpu_info gpu_inf;
+ bool hdr;
+ gsr_color_range color_range;
+ bool record_cursor;
+} gsr_capture_kms_cuda_params;
+
+gsr_capture* gsr_capture_kms_cuda_create(const gsr_capture_kms_cuda_params *params);
+
+#endif /* GSR_CAPTURE_KMS_CUDA_H */
diff --git a/include/capture/kms_vaapi.h b/include/capture/kms_vaapi.h
index 741d74b..196b597 100644
--- a/include/capture/kms_vaapi.h
+++ b/include/capture/kms_vaapi.h
@@ -3,15 +3,16 @@
#include "../vec2.h"
#include "../utils.h"
+#include "../color_conversion.h"
#include "capture.h"
-#include <X11/X.h>
-
-typedef struct _XDisplay Display;
typedef struct {
- const char *display_to_capture; /* if this is "screen", then the entire x11 screen is captured (all displays). A copy is made of this */
+ gsr_egl *egl;
+ const char *display_to_capture; /* if this is "screen", then the first monitor is captured. A copy is made of this */
gsr_gpu_info gpu_inf;
- const char *card_path; /* reference */
+ bool hdr;
+ gsr_color_range color_range;
+ bool record_cursor;
} gsr_capture_kms_vaapi_params;
gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *params);
diff --git a/include/capture/nvfbc.h b/include/capture/nvfbc.h
index 431777b..36bc2b6 100644
--- a/include/capture/nvfbc.h
+++ b/include/capture/nvfbc.h
@@ -4,16 +4,17 @@
#include "capture.h"
#include "../vec2.h"
-typedef struct _XDisplay Display;
-
typedef struct {
- Display *dpy;
+ gsr_egl *egl;
const char *display_to_capture; /* if this is "screen", then the entire x11 screen is captured (all displays). A copy is made of this */
int fps;
vec2i pos;
vec2i size;
bool direct_capture;
bool overclock;
+ bool hdr;
+ gsr_color_range color_range;
+ bool record_cursor;
} gsr_capture_nvfbc_params;
gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params);
diff --git a/include/capture/xcomposite.h b/include/capture/xcomposite.h
new file mode 100644
index 0000000..ce0dbad
--- /dev/null
+++ b/include/capture/xcomposite.h
@@ -0,0 +1,51 @@
+#ifndef GSR_CAPTURE_XCOMPOSITE_H
+#define GSR_CAPTURE_XCOMPOSITE_H
+
+#include "capture.h"
+#include "../egl.h"
+#include "../vec2.h"
+#include "../color_conversion.h"
+#include "../window_texture.h"
+#include "../cursor.h"
+
+typedef struct {
+ gsr_egl *egl;
+ Window window;
+ bool follow_focused; /* If this is set then |window| is ignored */
+ vec2i region_size; /* This is currently only used with |follow_focused| */
+ gsr_color_range color_range;
+ bool record_cursor;
+} gsr_capture_xcomposite_params;
+
+typedef struct {
+ gsr_capture_base base;
+ gsr_capture_xcomposite_params params;
+ XEvent xev;
+
+ bool should_stop;
+ bool stop_is_error;
+ bool window_resized;
+ bool follow_focused_initialized;
+
+ Window window;
+ vec2i window_size;
+ vec2i texture_size;
+ double window_resize_timer;
+
+ WindowTexture window_texture;
+
+ Atom net_active_window_atom;
+
+ gsr_cursor cursor;
+ bool clear_next_frame;
+} gsr_capture_xcomposite;
+
+void gsr_capture_xcomposite_init(gsr_capture_xcomposite *self, const gsr_capture_xcomposite_params *params);
+
+int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context, AVFrame *frame);
+void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self);
+void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context);
+bool gsr_capture_xcomposite_should_stop(gsr_capture_xcomposite *self, bool *err);
+int gsr_capture_xcomposite_capture(gsr_capture_xcomposite *self, AVFrame *frame);
+
+#endif /* GSR_CAPTURE_XCOMPOSITE_H */
diff --git a/include/capture/xcomposite_cuda.h b/include/capture/xcomposite_cuda.h
index 20d358e..b93c6de 100644
--- a/include/capture/xcomposite_cuda.h
+++ b/include/capture/xcomposite_cuda.h
@@ -2,15 +2,10 @@
#define GSR_CAPTURE_XCOMPOSITE_CUDA_H
#include "capture.h"
-#include "../vec2.h"
-#include <X11/X.h>
-
-typedef struct _XDisplay Display;
+#include "xcomposite.h"
typedef struct {
- Window window;
- bool follow_focused; /* If this is set then |window| is ignored */
- vec2i region_size; /* This is currently only used with |follow_focused| */
+ gsr_capture_xcomposite_params base;
bool overclock;
} gsr_capture_xcomposite_cuda_params;
diff --git a/include/capture/xcomposite_vaapi.h b/include/capture/xcomposite_vaapi.h
index 6cf30c3..5d4b338 100644
--- a/include/capture/xcomposite_vaapi.h
+++ b/include/capture/xcomposite_vaapi.h
@@ -2,16 +2,10 @@
#define GSR_CAPTURE_XCOMPOSITE_VAAPI_H
#include "capture.h"
-#include "../vec2.h"
-#include <X11/X.h>
-
-typedef struct _XDisplay Display;
+#include "xcomposite.h"
typedef struct {
- Window window;
- bool follow_focused; /* If this is set then |window| is ignored */
- vec2i region_size; /* This is currently only used with |follow_focused| */
- const char *card_path; /* reference */
+ gsr_capture_xcomposite_params base;
} gsr_capture_xcomposite_vaapi_params;
gsr_capture* gsr_capture_xcomposite_vaapi_create(const gsr_capture_xcomposite_vaapi_params *params);
diff --git a/include/color_conversion.h b/include/color_conversion.h
index 1fe488b..d05df6a 100644
--- a/include/color_conversion.h
+++ b/include/color_conversion.h
@@ -3,16 +3,29 @@
#include "shader.h"
#include "vec2.h"
+#include <stdbool.h>
typedef enum {
- GSR_SOURCE_COLOR_RGB
+ GSR_COLOR_RANGE_LIMITED,
+ GSR_COLOR_RANGE_FULL
+} gsr_color_range;
+
+typedef enum {
+ GSR_SOURCE_COLOR_RGB,
+ GSR_SOURCE_COLOR_BGR
} gsr_source_color;
typedef enum {
- GSR_DESTINATION_COLOR_NV12
+ GSR_DESTINATION_COLOR_NV12, /* YUV420, BT709, 8-bit */
+ GSR_DESTINATION_COLOR_P010 /* YUV420, BT2020, 10-bit */
} gsr_destination_color;
typedef struct {
+ int offset;
+ int rotation;
+} gsr_color_uniforms;
+
+typedef struct {
gsr_egl *egl;
gsr_source_color source_color;
@@ -20,12 +33,15 @@ typedef struct {
unsigned int destination_textures[2];
int num_destination_textures;
+
+ gsr_color_range color_range;
+ bool load_external_image_shader;
} gsr_color_conversion_params;
typedef struct {
gsr_color_conversion_params params;
- int rotation_uniforms[2];
- gsr_shader shaders[2];
+ gsr_color_uniforms uniforms[4];
+ gsr_shader shaders[4];
unsigned int framebuffers[2];
@@ -36,6 +52,7 @@ typedef struct {
int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conversion_params *params);
void gsr_color_conversion_deinit(gsr_color_conversion *self);
-int gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i source_pos, vec2i source_size, vec2i texture_pos, vec2i texture_size, float rotation);
+void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i source_pos, vec2i source_size, vec2i texture_pos, vec2i texture_size, float rotation, bool external_texture);
+void gsr_color_conversion_clear(gsr_color_conversion *self);
#endif /* GSR_COLOR_CONVERSION_H */
diff --git a/include/cuda.h b/include/cuda.h
index ab07429..fd1f9f9 100644
--- a/include/cuda.h
+++ b/include/cuda.h
@@ -73,7 +73,8 @@ typedef CUDA_MEMCPY2D_v2 CUDA_MEMCPY2D;
typedef struct CUgraphicsResource_st *CUgraphicsResource;
-typedef struct {
+typedef struct gsr_cuda gsr_cuda;
+struct gsr_cuda {
gsr_overclock overclock;
bool do_overclock;
@@ -88,16 +89,18 @@ typedef struct {
CUresult (*cuCtxPushCurrent_v2)(CUcontext ctx);
CUresult (*cuCtxPopCurrent_v2)(CUcontext *pctx);
CUresult (*cuGetErrorString)(CUresult error, const char **pStr);
- CUresult (*cuMemsetD8_v2)(CUdeviceptr dstDevice, unsigned char uc, size_t N);
CUresult (*cuMemcpy2D_v2)(const CUDA_MEMCPY2D *pCopy);
+ CUresult (*cuMemcpy2DAsync_v2)(const CUDA_MEMCPY2D *pcopy, CUstream hStream);
+ CUresult (*cuStreamSynchronize)(CUstream hStream);
- CUresult (*cuGraphicsGLRegisterImage)(CUgraphicsResource *pCudaResource, unsigned int image, unsigned int target, unsigned int Flags);
+ CUresult (*cuGraphicsGLRegisterImage)(CUgraphicsResource *pCudaResource, unsigned int image, unsigned int target, unsigned int flags);
+ CUresult (*cuGraphicsEGLRegisterImage)(CUgraphicsResource *pCudaResource, void *image, unsigned int flags);
CUresult (*cuGraphicsResourceSetMapFlags)(CUgraphicsResource resource, unsigned int flags);
CUresult (*cuGraphicsMapResources)(unsigned int count, CUgraphicsResource *resources, CUstream hStream);
CUresult (*cuGraphicsUnmapResources)(unsigned int count, CUgraphicsResource *resources, CUstream hStream);
CUresult (*cuGraphicsUnregisterResource)(CUgraphicsResource resource);
CUresult (*cuGraphicsSubResourceGetMappedArray)(CUarray *pArray, CUgraphicsResource resource, unsigned int arrayIndex, unsigned int mipLevel);
-} gsr_cuda;
+};
bool gsr_cuda_load(gsr_cuda *self, Display *display, bool overclock);
void gsr_cuda_unload(gsr_cuda *self);
diff --git a/include/cursor.h b/include/cursor.h
index a189ec3..b1ec6bd 100644
--- a/include/cursor.h
+++ b/include/cursor.h
@@ -4,12 +4,9 @@
#include "egl.h"
#include "vec2.h"
-#include <X11/Xlib.h>
-
typedef struct {
gsr_egl *egl;
Display *display;
- Window window;
int x_fixes_event_base;
unsigned int texture_id;
@@ -23,8 +20,7 @@ typedef struct {
int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display);
void gsr_cursor_deinit(gsr_cursor *self);
-int gsr_cursor_change_window_target(gsr_cursor *self, Window window);
void gsr_cursor_update(gsr_cursor *self, XEvent *xev);
-void gsr_cursor_tick(gsr_cursor *self);
+void gsr_cursor_tick(gsr_cursor *self, Window relative_to);
#endif /* GSR_CURSOR_H */
diff --git a/include/defs.h b/include/defs.h
new file mode 100644
index 0000000..473583c
--- /dev/null
+++ b/include/defs.h
@@ -0,0 +1,28 @@
+#ifndef GSR_DEFS_H
+#define GSR_DEFS_H
+
+typedef enum {
+ GSR_GPU_VENDOR_AMD,
+ GSR_GPU_VENDOR_INTEL,
+ GSR_GPU_VENDOR_NVIDIA
+} gsr_gpu_vendor;
+
+typedef struct {
+ gsr_gpu_vendor vendor;
+ int gpu_version; /* 0 if unknown */
+} gsr_gpu_info;
+
+typedef enum {
+ GSR_MONITOR_ROT_0,
+ GSR_MONITOR_ROT_90,
+ GSR_MONITOR_ROT_180,
+ GSR_MONITOR_ROT_270
+} gsr_monitor_rotation;
+
+typedef enum {
+ GSR_CONNECTION_X11,
+ GSR_CONNECTION_WAYLAND,
+ GSR_CONNECTION_DRM
+} gsr_connection_type;
+
+#endif /* GSR_DEFS_H */
diff --git a/include/egl.h b/include/egl.h
index e410958..afdb5a9 100644
--- a/include/egl.h
+++ b/include/egl.h
@@ -7,6 +7,8 @@
#include <X11/Xutil.h>
#include <stdbool.h>
#include <stdint.h>
+#include "vec2.h"
+#include "defs.h"
#ifdef _WIN64
typedef signed long long int khronos_intptr_t;
@@ -32,11 +34,16 @@ typedef void* EGLImage;
typedef void* EGLImageKHR;
typedef void *GLeglImageOES;
typedef void (*__eglMustCastToProperFunctionPointerType)(void);
+typedef struct __GLXFBConfigRec *GLXFBConfig;
+typedef struct __GLXcontextRec *GLXContext;
+typedef XID GLXDrawable;
+typedef void(*__GLXextFuncPtr)(void);
#define EGL_SUCCESS 0x3000
#define EGL_BUFFER_SIZE 0x3020
#define EGL_RENDERABLE_TYPE 0x3040
-#define EGL_OPENGL_ES2_BIT 0x0004
+#define EGL_OPENGL_API 0x30A2
+#define EGL_OPENGL_BIT 0x0008
#define EGL_NONE 0x3038
#define EGL_CONTEXT_CLIENT_VERSION 0x3098
#define EGL_BACK_BUFFER 0x3084
@@ -50,16 +57,39 @@ typedef void (*__eglMustCastToProperFunctionPointerType)(void);
#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272
#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273
#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274
+#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443
+#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444
#define EGL_LINUX_DMA_BUF_EXT 0x3270
+#define EGL_RED_SIZE 0x3024
+#define EGL_ALPHA_SIZE 0x3021
+#define EGL_BLUE_SIZE 0x3022
+#define EGL_GREEN_SIZE 0x3023
+#define EGL_CONTEXT_PRIORITY_LEVEL_IMG 0x3100
+#define EGL_CONTEXT_PRIORITY_HIGH_IMG 0x3101
+#define EGL_CONTEXT_PRIORITY_MEDIUM_IMG 0x3102
+#define EGL_CONTEXT_PRIORITY_LOW_IMG 0x3103
+#define EGL_DEVICE_EXT 0x322C
+#define EGL_DRM_DEVICE_FILE_EXT 0x3233
#define GL_FLOAT 0x1406
#define GL_FALSE 0
#define GL_TRUE 1
#define GL_TRIANGLES 0x0004
#define GL_TEXTURE_2D 0x0DE1
+#define GL_TEXTURE_EXTERNAL_OES 0x8D65
+#define GL_RED 0x1903
+#define GL_GREEN 0x1904
+#define GL_BLUE 0x1905
+#define GL_ALPHA 0x1906
+#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46
+#define GL_RG 0x8227
#define GL_RGB 0x1907
#define GL_RGBA 0x1908
#define GL_RGBA8 0x8058
+#define GL_R8 0x8229
+#define GL_RG8 0x822B
+#define GL_R16 0x822A
+#define GL_RG16 0x822C
#define GL_UNSIGNED_BYTE 0x1401
#define GL_COLOR_BUFFER_BIT 0x00004000
#define GL_TEXTURE_WRAP_S 0x2802
@@ -79,6 +109,7 @@ typedef void (*__eglMustCastToProperFunctionPointerType)(void);
#define GL_BLEND 0x0BE2
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
+#define GL_DEBUG_OUTPUT 0x92E0
#define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01
@@ -93,15 +124,66 @@ typedef void (*__eglMustCastToProperFunctionPointerType)(void);
typedef unsigned int (*FUNC_eglExportDMABUFImageQueryMESA)(EGLDisplay dpy, EGLImageKHR image, int *fourcc, int *num_planes, uint64_t *modifiers);
typedef unsigned int (*FUNC_eglExportDMABUFImageMESA)(EGLDisplay dpy, EGLImageKHR image, int *fds, int32_t *strides, int32_t *offsets);
typedef void (*FUNC_glEGLImageTargetTexture2DOES)(unsigned int target, GLeglImageOES image);
+typedef GLXContext (*FUNC_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list);
+typedef void (*FUNC_glXSwapIntervalEXT)(Display * dpy, GLXDrawable drawable, int interval);
+typedef int (*FUNC_glXSwapIntervalMESA)(unsigned int interval);
+typedef int (*FUNC_glXSwapIntervalSGI)(int interval);
+typedef void (*GLDEBUGPROC)(unsigned int source, unsigned int type, unsigned int id, unsigned int severity, int length, const char *message, const void *userParam);
+typedef int (*FUNC_eglQueryDisplayAttribEXT)(EGLDisplay dpy, int32_t attribute, intptr_t *value);
+typedef const char* (*FUNC_eglQueryDeviceStringEXT)(void *device, int32_t name);
+
+#define GSR_MAX_OUTPUTS 32
+
+typedef struct {
+ Display *dpy;
+ Window window;
+} gsr_x11;
typedef struct {
+ uint32_t wl_name;
+ void *output;
+ vec2i pos;
+ vec2i size;
+ int32_t transform;
+ char *name;
+} gsr_wayland_output;
+
+typedef struct {
+ void *dpy;
+ void *window;
+ void *registry;
+ void *surface;
+ void *compositor;
+ gsr_wayland_output outputs[GSR_MAX_OUTPUTS];
+ int num_outputs;
+} gsr_wayland;
+
+typedef enum {
+ GSR_GL_CONTEXT_TYPE_EGL,
+ GSR_GL_CONTEXT_TYPE_GLX
+} gsr_gl_context_type;
+
+typedef struct gsr_egl gsr_egl;
+struct gsr_egl {
void *egl_library;
+ void *glx_library;
void *gl_library;
- Display *dpy;
+
+ gsr_gl_context_type context_type;
+
EGLDisplay egl_display;
EGLSurface egl_surface;
EGLContext egl_context;
- Window window;
+ const char *dri_card_path;
+
+ void *glx_context;
+ void *glx_fb_config;
+
+ gsr_gpu_info gpu_info;
+
+ gsr_x11 x11;
+ gsr_wayland wayland;
+ char card_path[128];
int32_t (*eglGetError)(void);
EGLDisplay (*eglGetDisplay)(EGLNativeDisplayType display_id);
@@ -117,20 +199,40 @@ typedef struct {
unsigned int (*eglDestroyImage)(EGLDisplay dpy, EGLImage image);
unsigned int (*eglSwapInterval)(EGLDisplay dpy, int32_t interval);
unsigned int (*eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
+ unsigned int (*eglBindAPI)(unsigned int api);
__eglMustCastToProperFunctionPointerType (*eglGetProcAddress)(const char *procname);
FUNC_eglExportDMABUFImageQueryMESA eglExportDMABUFImageQueryMESA;
FUNC_eglExportDMABUFImageMESA eglExportDMABUFImageMESA;
FUNC_glEGLImageTargetTexture2DOES glEGLImageTargetTexture2DOES;
+ FUNC_eglQueryDisplayAttribEXT eglQueryDisplayAttribEXT;
+ FUNC_eglQueryDeviceStringEXT eglQueryDeviceStringEXT;
+
+ __GLXextFuncPtr (*glXGetProcAddress)(const unsigned char *procName);
+ GLXFBConfig* (*glXChooseFBConfig)(Display *dpy, int screen, const int *attribList, int *nitems);
+ Bool (*glXMakeContextCurrent)(Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx);
+ // TODO: Remove
+ GLXContext (*glXCreateNewContext)(Display *dpy, GLXFBConfig config, int renderType, GLXContext shareList, Bool direct);
+ void (*glXDestroyContext)(Display *dpy, GLXContext ctx);
+ void (*glXSwapBuffers)(Display *dpy, GLXDrawable drawable);
+ FUNC_glXCreateContextAttribsARB glXCreateContextAttribsARB;
+
+ /* Optional */
+ FUNC_glXSwapIntervalEXT glXSwapIntervalEXT;
+ FUNC_glXSwapIntervalMESA glXSwapIntervalMESA;
+ FUNC_glXSwapIntervalSGI glXSwapIntervalSGI;
unsigned int (*glGetError)(void);
const unsigned char* (*glGetString)(unsigned int name);
+ void (*glFlush)(void);
+ void (*glFinish)(void);
void (*glClear)(unsigned int mask);
void (*glClearColor)(float red, float green, float blue, float alpha);
void (*glGenTextures)(int n, unsigned int *textures);
void (*glDeleteTextures)(int n, const unsigned int *texture);
void (*glBindTexture)(unsigned int target, unsigned int texture);
void (*glTexParameteri)(unsigned int target, unsigned int pname, int param);
+ void (*glTexParameteriv)(unsigned int target, unsigned int pname, const int *params);
void (*glGetTexLevelParameteriv)(unsigned int target, int level, unsigned int pname, int *params);
void (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels);
void (*glCopyImageSubData)(unsigned int srcName, unsigned int srcTarget, int srcLevel, int srcX, int srcY, int srcZ, unsigned int dstName, unsigned int dstTarget, int dstLevel, int dstX, int dstY, int dstZ, int srcWidth, int srcHeight, int srcDepth);
@@ -171,9 +273,13 @@ typedef struct {
void (*glBlendFunc)(unsigned int sfactor, unsigned int dfactor);
int (*glGetUniformLocation)(unsigned int program, const char *name);
void (*glUniform1f)(int location, float v0);
-} gsr_egl;
+ void (*glUniform2f)(int location, float v0, float v1);
+ void (*glDebugMessageCallback)(GLDEBUGPROC callback, const void *userParam);
+};
-bool gsr_egl_load(gsr_egl *self, Display *dpy);
+bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland, bool is_monitor_capture);
void gsr_egl_unload(gsr_egl *self);
+void gsr_egl_update(gsr_egl *self);
+
#endif /* GSR_EGL_H */
diff --git a/include/shader.h b/include/shader.h
index 37f4c09..57d1096 100644
--- a/include/shader.h
+++ b/include/shader.h
@@ -1,7 +1,7 @@
#ifndef GSR_SHADER_H
#define GSR_SHADER_H
-#include "egl.h"
+typedef struct gsr_egl gsr_egl;
typedef struct {
gsr_egl *egl;
diff --git a/include/sound.hpp b/include/sound.hpp
index 6873e90..77bec99 100644
--- a/include/sound.hpp
+++ b/include/sound.hpp
@@ -55,7 +55,7 @@ void sound_device_close(SoundDevice *device);
Returns the next chunk of audio into @buffer.
Returns the number of frames read, or a negative value on failure.
*/
-int sound_device_read_next_chunk(SoundDevice *device, void **buffer);
+int sound_device_read_next_chunk(SoundDevice *device, void **buffer, double timeout_sec, double *latency_seconds);
std::vector<AudioInput> get_pulseaudio_inputs();
diff --git a/include/utils.h b/include/utils.h
index 7d76a2d..74fdd59 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -2,23 +2,21 @@
#define GSR_UTILS_H
#include "vec2.h"
+#include "../include/egl.h"
+#include "../include/defs.h"
#include <stdbool.h>
+#include <stdint.h>
#include <X11/extensions/Xrandr.h>
-typedef enum {
- GSR_GPU_VENDOR_AMD,
- GSR_GPU_VENDOR_INTEL,
- GSR_GPU_VENDOR_NVIDIA
-} gsr_gpu_vendor;
-
-typedef struct {
- gsr_gpu_vendor vendor;
- int gpu_version; /* 0 if unknown */
-} gsr_gpu_info;
-
typedef struct {
+ const char *name;
+ int name_len;
vec2i pos;
vec2i size;
+ XRRCrtcInfo *crt_info; /* Only on x11 */
+ uint32_t connector_id; /* Only on x11 and drm */
+ gsr_monitor_rotation rotation; /* Only on x11 and wayland */
+ uint32_t monitor_identifier; /* Only on drm and wayland */
} gsr_monitor;
typedef struct {
@@ -30,13 +28,19 @@ typedef struct {
double clock_get_monotonic_seconds(void);
-typedef void (*active_monitor_callback)(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata);
-void for_each_active_monitor_output(Display *display, active_monitor_callback callback, void *userdata);
-bool get_monitor_by_name(Display *display, const char *name, gsr_monitor *monitor);
+typedef void (*active_monitor_callback)(const gsr_monitor *monitor, void *userdata);
+void for_each_active_monitor_output_x11(Display *display, active_monitor_callback callback, void *userdata);
+void for_each_active_monitor_output(const gsr_egl *egl, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata);
+bool get_monitor_by_name(const gsr_egl *egl, gsr_connection_type connection_type, const char *name, gsr_monitor *monitor);
+gsr_monitor_rotation drm_monitor_get_display_server_rotation(const gsr_egl *egl, const gsr_monitor *monitor);
-bool gl_get_gpu_info(Display *dpy, gsr_gpu_info *info);
+bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info);
/* |output| should be at least 128 bytes in size */
-bool gsr_get_valid_card_path(char *output);
+bool gsr_get_valid_card_path(gsr_egl *egl, char *output);
+/* |render_path| should be at least 128 bytes in size */
+bool gsr_card_path_get_render_path(const char *card_path, char *render_path);
+
+int even_number_ceil(int value);
#endif /* GSR_UTILS_H */
diff --git a/install.sh b/install.sh
index f898534..0833307 100755
--- a/install.sh
+++ b/install.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh -e
script_dir=$(dirname "$0")
cd "$script_dir"
@@ -6,9 +6,17 @@ cd "$script_dir"
[ $(id -u) -ne 0 ] && echo "You need root privileges to run the install script" && exit 1
./build.sh
-rm -f "/usr/local/bin/gpu-screen-recorder"
+strip gsr-kms-server
+strip gpu-screen-recorder
+
install -Dm755 "gsr-kms-server" "/usr/bin/gsr-kms-server"
install -Dm755 "gpu-screen-recorder" "/usr/bin/gpu-screen-recorder"
+if [ -d "/usr/lib/systemd/user" ]; then
+ install -Dm644 "extra/gpu-screen-recorder.service" "/usr/lib/systemd/user/gpu-screen-recorder.service"
+fi
+# Not necessary, but removes the password prompt when trying to record a monitor on amd/intel or nvidia wayland
+setcap cap_sys_admin+ep /usr/bin/gsr-kms-server
+# Not necessary, but allows use of EGL_CONTEXT_PRIORITY_LEVEL_IMG which allows gpu screen recorder to run without being limited to game fps under heavy load on AMD/Intel
+setcap cap_sys_nice+ep /usr/bin/gpu-screen-recorder
-[ -f "/proc/driver/nvidia/version" ] && ./install_coolbits.sh
echo "Successfully installed gpu-screen-recorder"
diff --git a/install_coolbits.sh b/install_coolbits.sh
deleted file mode 100755
index 053e8ab..0000000
--- a/install_coolbits.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-
-script_dir=$(dirname "$0")
-cd "$script_dir"
-
-[ $(id -u) -ne 0 ] && echo "You need root privileges to run the install script" && exit 1
-
-for xorg_conf_d in "/etc/X11/xorg.conf.d" "/usr/share/X11/xorg.conf.d" "/usr/lib/X11/xorg.conf.d"; do
- [ -d "$xorg_conf_d" ] && install -Dm644 "88-gsr-coolbits.conf" "$xorg_conf_d/88-gsr-coolbits.conf" && exit 0
-done
diff --git a/kms/client/kms_client.c b/kms/client/kms_client.c
index 587dda3..57c6ccf 100644
--- a/kms/client/kms_client.c
+++ b/kms/client/kms_client.c
@@ -10,8 +10,15 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
+#include <sys/stat.h>
#include <sys/capability.h>
+#define GSR_SOCKET_PAIR_LOCAL 0
+#define GSR_SOCKET_PAIR_REMOTE 1
+
+static void cleanup_socket(gsr_kms_client *self, bool kill_server);
+static int gsr_kms_client_replace_connection(gsr_kms_client *self);
+
static bool generate_random_characters(char *buffer, int buffer_size, const char *alphabet, size_t alphabet_size) {
int fd = open("/dev/urandom", O_RDONLY);
if(fd == -1) {
@@ -34,16 +41,42 @@ static bool generate_random_characters(char *buffer, int buffer_size, const char
return true;
}
+static void close_fds(gsr_kms_response *response) {
+ for(int i = 0; i < response->num_fds; ++i) {
+ if(response->fds[i].fd > 0)
+ close(response->fds[i].fd);
+ response->fds[i].fd = 0;
+ }
+}
+
static int send_msg_to_server(int server_fd, gsr_kms_request *request) {
struct iovec iov;
iov.iov_base = request;
iov.iov_len = sizeof(*request);
- struct msghdr request_message = {0};
- request_message.msg_iov = &iov;
- request_message.msg_iovlen = 1;
+ struct msghdr response_message = {0};
+ response_message.msg_iov = &iov;
+ response_message.msg_iovlen = 1;
+
+ char cmsgbuf[CMSG_SPACE(sizeof(int) * 1)];
+ memset(cmsgbuf, 0, sizeof(cmsgbuf));
- return sendmsg(server_fd, &request_message, 0);
+ if(request->new_connection_fd > 0) {
+ response_message.msg_control = cmsgbuf;
+ response_message.msg_controllen = sizeof(cmsgbuf);
+
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&response_message);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int) * 1);
+
+ int *fds = (int*)CMSG_DATA(cmsg);
+ fds[0] = request->new_connection_fd;
+
+ response_message.msg_controllen = cmsg->cmsg_len;
+ }
+
+ return sendmsg(server_fd, &response_message, 0);
}
static int recv_msg_from_server(int server_fd, gsr_kms_response *response) {
@@ -72,9 +105,7 @@ static int recv_msg_from_server(int server_fd, gsr_kms_response *response) {
response->fds[i].fd = fds[i];
}
} else {
- for(int i = 0; i < response->num_fds; ++i) {
- response->fds[i].fd = 0;
- }
+ close_fds(response);
}
}
@@ -105,69 +136,114 @@ static void strncpy_safe(char *dst, const char *src, int len) {
dst[min_len] = '\0';
}
+static bool find_program_in_path(const char *program_name, char *filepath, int filepath_len) {
+ const char *path = getenv("PATH");
+ if(!path)
+ return false;
+
+ int program_name_len = strlen(program_name);
+ const char *end = path + strlen(path);
+ while(path != end) {
+ const char *part_end = strchr(path, ':');
+ const char *next = part_end;
+ if(part_end) {
+ next = part_end + 1;
+ } else {
+ part_end = end;
+ next = end;
+ }
+
+ int len = part_end - path;
+ if(len + 1 + program_name_len < filepath_len) {
+ memcpy(filepath, path, len);
+ filepath[len] = '/';
+ memcpy(filepath + len + 1, program_name, program_name_len);
+ filepath[len + 1 + program_name_len] = '\0';
+
+ if(access(filepath, F_OK) == 0)
+ return true;
+ }
+
+ path = next;
+ }
+
+ return false;
+}
+
int gsr_kms_client_init(gsr_kms_client *self, const char *card_path) {
+ int result = -1;
self->kms_server_pid = -1;
- self->socket_fd = -1;
- self->client_fd = -1;
- self->socket_path[0] = '\0';
+ self->initial_socket_fd = -1;
+ self->initial_client_fd = -1;
+ self->initial_socket_path[0] = '\0';
+ self->socket_pair[0] = -1;
+ self->socket_pair[1] = -1;
struct sockaddr_un local_addr = {0};
struct sockaddr_un remote_addr = {0};
- if(!create_socket_path(self->socket_path, sizeof(self->socket_path))) {
+ if(!create_socket_path(self->initial_socket_path, sizeof(self->initial_socket_path))) {
fprintf(stderr, "gsr error: gsr_kms_client_init: failed to create path to kms socket\n");
return -1;
}
- // This doesn't work on nixos, but we dont want to use $PATH because we want to make this as safe as possible by running pkexec
- // on a path that only root can modify. If we use "gsr-kms-server" instead then $PATH can be modified in ~/.bashrc for example
- // which will overwrite the path to gsr-kms-server and the user can end up running a malicious program that pretends to be gsr-kms-server.
- // If there is a safe way to do this on nixos, then please tell me; or use gpu-screen-recorder flatpak instead.
- const char *server_filepath = "/usr/bin/gsr-kms-server";
- bool has_perm = 0;
+ char server_filepath[PATH_MAX];
+ if(!find_program_in_path("gsr-kms-server", server_filepath, sizeof(server_filepath))) {
+ fprintf(stderr, "gsr error: gsr_kms_client_init: gsr-kms-server is not installed\n");
+ return -1;
+ }
+
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
- if(!inside_flatpak) {
- if(access("/usr/bin/gsr-kms-server", F_OK) != 0) {
- fprintf(stderr, "gsr error: gsr_kms_client_init: /usr/bin/gsr-kms-server not found, please install gpu-screen-recorder first\n");
- return -1;
- }
+ const char *home = getenv("HOME");
+ if(!home)
+ home = "/tmp";
- if(geteuid() == 0) {
- has_perm = true;
- } else {
- cap_t kms_server_cap = cap_get_file(server_filepath);
- if(kms_server_cap) {
- cap_flag_value_t res = 0;
- cap_get_flag(kms_server_cap, CAP_SYS_ADMIN, CAP_PERMITTED, &res);
- if(res == CAP_SET) {
- //fprintf(stderr, "has permission!\n");
- has_perm = true;
- } else {
- //fprintf(stderr, "No permission:(\n");
- }
- cap_free(kms_server_cap);
+ bool has_perm = 0;
+ if(geteuid() == 0) {
+ has_perm = true;
+ } else {
+ cap_t kms_server_cap = cap_get_file(server_filepath);
+ if(kms_server_cap) {
+ cap_flag_value_t res = CAP_CLEAR;
+ cap_get_flag(kms_server_cap, CAP_SYS_ADMIN, CAP_PERMITTED, &res);
+ if(res == CAP_SET) {
+ //fprintf(stderr, "has permission!\n");
+ has_perm = true;
} else {
- if(errno == ENODATA)
- fprintf(stderr, "gsr info: gsr_kms_client_init: gsr-kms-server is missing sys_admin cap and will require root authentication. To bypass this automatically, run: sudo setcap cap_sys_admin+ep '%s'\n", server_filepath);
- else
- fprintf(stderr, "gsr info: gsr_kms_client_init: failed to get cap\n");
+ //fprintf(stderr, "No permission:(\n");
}
+ cap_free(kms_server_cap);
+ } else if(!inside_flatpak) {
+ if(errno == ENODATA)
+ fprintf(stderr, "gsr info: gsr_kms_client_init: gsr-kms-server is missing sys_admin cap and will require root authentication. To bypass this automatically, run: sudo setcap cap_sys_admin+ep '%s'\n", server_filepath);
+ else
+ fprintf(stderr, "gsr info: gsr_kms_client_init: failed to get cap\n");
}
}
- self->socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if(self->socket_fd == -1) {
+ if(socketpair(AF_UNIX, SOCK_STREAM, 0, self->socket_pair) == -1) {
+ fprintf(stderr, "gsr error: gsr_kms_client_init: socketpair failed, error: %s\n", strerror(errno));
+ goto err;
+ }
+
+ self->initial_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(self->initial_socket_fd == -1) {
fprintf(stderr, "gsr error: gsr_kms_client_init: socket failed, error: %s\n", strerror(errno));
goto err;
}
local_addr.sun_family = AF_UNIX;
- strncpy_safe(local_addr.sun_path, self->socket_path, sizeof(local_addr.sun_path));
- if(bind(self->socket_fd, (struct sockaddr*)&local_addr, sizeof(local_addr.sun_family) + strlen(local_addr.sun_path)) == -1) {
+ strncpy_safe(local_addr.sun_path, self->initial_socket_path, sizeof(local_addr.sun_path));
+
+ const mode_t prev_mask = umask(0000);
+ const int bind_res = bind(self->initial_socket_fd, (struct sockaddr*)&local_addr, sizeof(local_addr.sun_family) + strlen(local_addr.sun_path));
+ umask(prev_mask);
+
+ if(bind_res == -1) {
fprintf(stderr, "gsr error: gsr_kms_client_init: failed to bind socket, error: %s\n", strerror(errno));
goto err;
}
- if(listen(self->socket_fd, 1) == -1) {
+ if(listen(self->initial_socket_fd, 1) == -1) {
fprintf(stderr, "gsr error: gsr_kms_client_init: failed to listen on socket, error: %s\n", strerror(errno));
goto err;
}
@@ -178,13 +254,13 @@ int gsr_kms_client_init(gsr_kms_client *self, const char *card_path) {
goto err;
} else if(pid == 0) { /* child */
if(inside_flatpak) {
- const char *args[] = { "flatpak-spawn", "--host", "pkexec", "flatpak", "run", "--command=gsr-kms-server", "com.dec05eba.gpu_screen_recorder", self->socket_path, card_path, NULL };
+ const char *args[] = { "flatpak-spawn", "--host", "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/kms-server-proxy", self->initial_socket_path, card_path, home, NULL };
execvp(args[0], (char *const*)args);
} else if(has_perm) {
- const char *args[] = { server_filepath, self->socket_path, card_path, NULL };
+ const char *args[] = { server_filepath, self->initial_socket_path, card_path, NULL };
execvp(args[0], (char *const*)args);
} else {
- const char *args[] = { "pkexec", server_filepath, self->socket_path, card_path, NULL };
+ const char *args[] = { "pkexec", server_filepath, self->initial_socket_path, card_path, NULL };
execvp(args[0], (char *const*)args);
}
fprintf(stderr, "gsr error: gsr_kms_client_init: execvp failed, error: %s\n", strerror(errno));
@@ -198,80 +274,151 @@ int gsr_kms_client_init(gsr_kms_client *self, const char *card_path) {
struct timeval tv;
fd_set rfds;
FD_ZERO(&rfds);
- FD_SET(self->socket_fd, &rfds);
+ FD_SET(self->initial_socket_fd, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 100 * 1000; // 100 ms
- int select_res = select(1 + self->socket_fd, &rfds, NULL, NULL, &tv);
+ int select_res = select(1 + self->initial_socket_fd, &rfds, NULL, NULL, &tv);
if(select_res > 0) {
socklen_t sock_len = 0;
- self->client_fd = accept(self->socket_fd, (struct sockaddr*)&remote_addr, &sock_len);
- if(self->client_fd == -1) {
+ self->initial_client_fd = accept(self->initial_socket_fd, (struct sockaddr*)&remote_addr, &sock_len);
+ if(self->initial_client_fd == -1) {
fprintf(stderr, "gsr error: gsr_kms_client_init: accept failed on socket, error: %s\n", strerror(errno));
goto err;
}
break;
} else {
- int status;
+ int status = 0;
int wait_result = waitpid(self->kms_server_pid, &status, WNOHANG);
if(wait_result != 0) {
- fprintf(stderr, "gsr error: gsr_kms_client_init: kms server died or never started, error: %s\n", strerror(errno));
+ int exit_code = -1;
+ if(WIFEXITED(status))
+ exit_code = WEXITSTATUS(status);
+ fprintf(stderr, "gsr error: gsr_kms_client_init: kms server died or never started, exit code: %d\n", exit_code);
self->kms_server_pid = -1;
+ if(exit_code != 0)
+ result = exit_code;
goto err;
}
}
}
fprintf(stderr, "gsr info: gsr_kms_client_init: server connected\n");
+ fprintf(stderr, "gsr info: replacing file-backed unix domain socket with socketpair\n");
+ if(gsr_kms_client_replace_connection(self) != 0)
+ goto err;
+
+ cleanup_socket(self, false);
+ fprintf(stderr, "gsr info: using socketpair\n");
+
return 0;
err:
gsr_kms_client_deinit(self);
- return -1;
+ return result;
}
-void gsr_kms_client_deinit(gsr_kms_client *self) {
- if(self->client_fd != -1) {
- close(self->client_fd);
- self->client_fd = -1;
+void cleanup_socket(gsr_kms_client *self, bool kill_server) {
+ if(self->initial_client_fd != -1) {
+ close(self->initial_client_fd);
+ self->initial_client_fd = -1;
+ }
+
+ if(self->initial_socket_fd != -1) {
+ close(self->initial_socket_fd);
+ self->initial_socket_fd = -1;
}
- if(self->socket_fd != -1) {
- close(self->socket_fd);
- self->socket_fd = -1;
+ if(kill_server) {
+ for(int i = 0; i < 2; ++i) {
+ if(self->socket_pair[i] > 0) {
+ close(self->socket_pair[i]);
+ self->socket_pair[i] = -1;
+ }
+ }
}
- if(self->kms_server_pid != -1) {
- kill(self->kms_server_pid, SIGINT);
- int status;
- waitpid(self->kms_server_pid, &status, 0);
+ if(kill_server && self->kms_server_pid != -1) {
+ kill(self->kms_server_pid, SIGKILL);
+ //int status;
+ //waitpid(self->kms_server_pid, &status, 0);
self->kms_server_pid = -1;
}
- if(self->socket_path[0] != '\0') {
- remove(self->socket_path);
- self->socket_path[0] = '\0';
+ if(self->initial_socket_path[0] != '\0') {
+ remove(self->initial_socket_path);
+ self->initial_socket_path[0] = '\0';
+ }
+}
+
+void gsr_kms_client_deinit(gsr_kms_client *self) {
+ cleanup_socket(self, true);
+}
+
+int gsr_kms_client_replace_connection(gsr_kms_client *self) {
+ gsr_kms_response response;
+ response.version = 0;
+ response.result = KMS_RESULT_FAILED_TO_SEND;
+ response.err_msg[0] = '\0';
+
+ gsr_kms_request request;
+ request.version = GSR_KMS_PROTOCOL_VERSION;
+ request.type = KMS_REQUEST_TYPE_REPLACE_CONNECTION;
+ request.new_connection_fd = self->socket_pair[GSR_SOCKET_PAIR_REMOTE];
+ if(send_msg_to_server(self->initial_client_fd, &request) == -1) {
+ fprintf(stderr, "gsr error: gsr_kms_client_replace_connection: failed to send request message to server\n");
+ return -1;
+ }
+
+ const int recv_res = recv_msg_from_server(self->socket_pair[GSR_SOCKET_PAIR_LOCAL], &response);
+ if(recv_res == 0) {
+ fprintf(stderr, "gsr warning: gsr_kms_client_replace_connection: kms server shut down\n");
+ return -1;
+ } else if(recv_res == -1) {
+ fprintf(stderr, "gsr error: gsr_kms_client_replace_connection: failed to receive response\n");
+ return -1;
}
+
+ if(response.version != GSR_KMS_PROTOCOL_VERSION) {
+ fprintf(stderr, "gsr error: gsr_kms_client_replace_connection: expected gsr-kms-server protocol version to be %u, but it's %u\n", GSR_KMS_PROTOCOL_VERSION, response.version);
+ /*close_fds(response);*/
+ return -1;
+ }
+
+ return 0;
}
int gsr_kms_client_get_kms(gsr_kms_client *self, gsr_kms_response *response) {
+ response->version = 0;
response->result = KMS_RESULT_FAILED_TO_SEND;
- strcpy(response->err_msg, "failed to send");
+ response->err_msg[0] = '\0';
gsr_kms_request request;
+ request.version = GSR_KMS_PROTOCOL_VERSION;
request.type = KMS_REQUEST_TYPE_GET_KMS;
- if(send_msg_to_server(self->client_fd, &request) == -1) {
+ request.new_connection_fd = 0;
+ if(send_msg_to_server(self->socket_pair[GSR_SOCKET_PAIR_LOCAL], &request) == -1) {
fprintf(stderr, "gsr error: gsr_kms_client_get_kms: failed to send request message to server\n");
+ strcpy(response->err_msg, "failed to send");
return -1;
}
- const int recv_res = recv_msg_from_server(self->client_fd, response);
+ const int recv_res = recv_msg_from_server(self->socket_pair[GSR_SOCKET_PAIR_LOCAL], response);
if(recv_res == 0) {
fprintf(stderr, "gsr warning: gsr_kms_client_get_kms: kms server shut down\n");
+ strcpy(response->err_msg, "failed to receive");
return -1;
} else if(recv_res == -1) {
fprintf(stderr, "gsr error: gsr_kms_client_get_kms: failed to receive response\n");
+ strcpy(response->err_msg, "failed to receive");
+ return -1;
+ }
+
+ if(response->version != GSR_KMS_PROTOCOL_VERSION) {
+ fprintf(stderr, "gsr error: gsr_kms_client_get_kms: expected gsr-kms-server protocol version to be %u, but it's %u\n", GSR_KMS_PROTOCOL_VERSION, response->version);
+ /*close_fds(response);*/
+ strcpy(response->err_msg, "mismatching protocol version");
return -1;
}
diff --git a/kms/client/kms_client.h b/kms/client/kms_client.h
index 254637b..2d18848 100644
--- a/kms/client/kms_client.h
+++ b/kms/client/kms_client.h
@@ -5,12 +5,15 @@
#include <sys/types.h>
#include <limits.h>
-typedef struct {
+typedef struct gsr_kms_client gsr_kms_client;
+
+struct gsr_kms_client {
pid_t kms_server_pid;
- int socket_fd;
- int client_fd;
- char socket_path[PATH_MAX];
-} gsr_kms_client;
+ int initial_socket_fd;
+ int initial_client_fd;
+ char initial_socket_path[PATH_MAX];
+ int socket_pair[2];
+};
/* |card_path| should be a path to card, for example /dev/dri/card0 */
int gsr_kms_client_init(gsr_kms_client *self, const char *card_path);
diff --git a/kms/kms_shared.h b/kms/kms_shared.h
index e0687b2..4fa9c38 100644
--- a/kms/kms_shared.h
+++ b/kms/kms_shared.h
@@ -3,10 +3,16 @@
#include <stdint.h>
#include <stdbool.h>
+#include <drm_mode.h>
-#define GSR_KMS_MAX_PLANES 32
+#define GSR_KMS_PROTOCOL_VERSION 2
+#define GSR_KMS_MAX_PLANES 10
+
+typedef struct gsr_kms_response_fd gsr_kms_response_fd;
+typedef struct gsr_kms_response gsr_kms_response;
typedef enum {
+ KMS_REQUEST_TYPE_REPLACE_CONNECTION,
KMS_REQUEST_TYPE_GET_KMS
} gsr_kms_request_type;
@@ -14,14 +20,17 @@ typedef enum {
KMS_RESULT_OK,
KMS_RESULT_INVALID_REQUEST,
KMS_RESULT_FAILED_TO_GET_PLANE,
+ KMS_RESULT_FAILED_TO_GET_PLANES,
KMS_RESULT_FAILED_TO_SEND
} gsr_kms_result;
typedef struct {
- int type; /* gsr_kms_request_type */
+ uint32_t version; /* GSR_KMS_PROTOCOL_VERSION */
+ int type; /* gsr_kms_request_type */
+ int new_connection_fd;
} gsr_kms_request;
-typedef struct {
+struct gsr_kms_response_fd {
int fd;
uint32_t width;
uint32_t height;
@@ -31,13 +40,21 @@ typedef struct {
uint64_t modifier;
uint32_t connector_id; /* 0 if unknown */
bool is_combined_plane;
-} gsr_kms_response_fd;
-
-typedef struct {
- int result; /* gsr_kms_result */
+ bool is_cursor;
+ bool has_hdr_metadata;
+ int x;
+ int y;
+ int src_w;
+ int src_h;
+ struct hdr_output_metadata hdr_metadata;
+};
+
+struct gsr_kms_response {
+ uint32_t version; /* GSR_KMS_PROTOCOL_VERSION */
+ int result; /* gsr_kms_result */
char err_msg[128];
gsr_kms_response_fd fds[GSR_KMS_MAX_PLANES];
int num_fds;
-} gsr_kms_response;
+};
#endif /* #define GSR_KMS_SHARED_H */
diff --git a/kms/server/kms_server.c b/kms/server/kms_server.c
index 5aa6590..09deaab 100644
--- a/kms/server/kms_server.c
+++ b/kms/server/kms_server.c
@@ -13,20 +13,19 @@
#include <xf86drm.h>
#include <xf86drmMode.h>
-#include <libdrm/drm_mode.h>
+#include <drm_mode.h>
#define MAX_CONNECTORS 32
typedef struct {
int drmfd;
- uint32_t plane_ids[GSR_KMS_MAX_PLANES];
- uint32_t connector_ids[GSR_KMS_MAX_PLANES];
- size_t num_plane_ids;
+ drmModePlaneResPtr planes;
} gsr_drm;
typedef struct {
uint32_t connector_id;
uint64_t crtc_id;
+ uint64_t hdr_metadata_blob_id;
} connector_crtc_pair;
typedef struct {
@@ -70,6 +69,40 @@ static int send_msg_to_client(int client_fd, gsr_kms_response *response) {
return sendmsg(client_fd, &response_message, 0);
}
+static int recv_msg_from_client(int client_fd, gsr_kms_request *request) {
+ struct iovec iov;
+ iov.iov_base = request;
+ iov.iov_len = sizeof(*request);
+
+ struct msghdr response_message = {0};
+ response_message.msg_iov = &iov;
+ response_message.msg_iovlen = 1;
+
+ char cmsgbuf[CMSG_SPACE(sizeof(int) * 1)];
+ memset(cmsgbuf, 0, sizeof(cmsgbuf));
+ response_message.msg_control = cmsgbuf;
+ response_message.msg_controllen = sizeof(cmsgbuf);
+
+ int res = recvmsg(client_fd, &response_message, MSG_WAITALL);
+ if(res <= 0)
+ return res;
+
+ if(request->new_connection_fd > 0) {
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&response_message);
+ if(cmsg) {
+ int *fds = (int*)CMSG_DATA(cmsg);
+ request->new_connection_fd = fds[0];
+ } else {
+ if(request->new_connection_fd > 0) {
+ close(request->new_connection_fd);
+ request->new_connection_fd = 0;
+ }
+ }
+ }
+
+ return res;
+}
+
static bool connector_get_property_by_name(int drmfd, drmModeConnectorPtr props, const char *name, uint64_t *result) {
for(int i = 0; i < props->count_props; ++i) {
drmModePropertyPtr prop = drmModeGetProperty(drmfd, props->props[i]);
@@ -85,149 +118,168 @@ static bool connector_get_property_by_name(int drmfd, drmModeConnectorPtr props,
return false;
}
-static bool plane_is_cursor_plane(int drmfd, uint32_t plane_id) {
+typedef enum {
+ PLANE_PROPERTY_X = 1 << 0,
+ PLANE_PROPERTY_Y = 1 << 1,
+ PLANE_PROPERTY_SRC_X = 1 << 2,
+ PLANE_PROPERTY_SRC_Y = 1 << 3,
+ PLANE_PROPERTY_SRC_W = 1 << 4,
+ PLANE_PROPERTY_SRC_H = 1 << 5,
+ PLANE_PROPERTY_IS_CURSOR = 1 << 6,
+ PLANE_PROPERTY_IS_PRIMARY = 1 << 7,
+} plane_property_mask;
+
+/* Returns plane_property_mask */
+static uint32_t plane_get_properties(int drmfd, uint32_t plane_id, int *x, int *y, int *src_x, int *src_y, int *src_w, int *src_h) {
+ *x = 0;
+ *y = 0;
+ *src_x = 0;
+ *src_y = 0;
+ *src_w = 0;
+ *src_h = 0;
+
+ plane_property_mask property_mask = 0;
+
drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drmfd, plane_id, DRM_MODE_OBJECT_PLANE);
if(!props)
- return false;
+ return property_mask;
+ // TODO: Dont do this every frame
for(uint32_t i = 0; i < props->count_props; ++i) {
drmModePropertyPtr prop = drmModeGetProperty(drmfd, props->props[i]);
- if(prop) {
- if(strcmp(prop->name, "type") == 0) {
- const uint64_t current_enum_value = props->prop_values[i];
- bool is_cursor = false;
-
- for(int j = 0; j < prop->count_enums; ++j) {
- if(prop->enums[j].value == current_enum_value && strcmp(prop->enums[j].name, "Cursor") == 0) {
- is_cursor = true;
- break;
- }
+ if(!prop)
+ continue;
+ // SRC_* values are fixed 16.16 points
+ const uint32_t type = prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE);
+ if((type & DRM_MODE_PROP_SIGNED_RANGE) && strcmp(prop->name, "CRTC_X") == 0) {
+ *x = (int)props->prop_values[i];
+ property_mask |= PLANE_PROPERTY_X;
+ } else if((type & DRM_MODE_PROP_SIGNED_RANGE) && strcmp(prop->name, "CRTC_Y") == 0) {
+ *y = (int)props->prop_values[i];
+ property_mask |= PLANE_PROPERTY_Y;
+ } else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "SRC_X") == 0) {
+ *src_x = (int)(props->prop_values[i] >> 16);
+ property_mask |= PLANE_PROPERTY_SRC_X;
+ } else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "SRC_Y") == 0) {
+ *src_y = (int)(props->prop_values[i] >> 16);
+ property_mask |= PLANE_PROPERTY_SRC_Y;
+ } else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "SRC_W") == 0) {
+ *src_w = (int)(props->prop_values[i] >> 16);
+ property_mask |= PLANE_PROPERTY_SRC_W;
+ } else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "SRC_H") == 0) {
+ *src_h = (int)(props->prop_values[i] >> 16);
+ property_mask |= PLANE_PROPERTY_SRC_H;
+ } else if((type & DRM_MODE_PROP_ENUM) && strcmp(prop->name, "type") == 0) {
+ const uint64_t current_enum_value = props->prop_values[i];
+ for(int j = 0; j < prop->count_enums; ++j) {
+ if(prop->enums[j].value == current_enum_value && strcmp(prop->enums[j].name, "Primary") == 0) {
+ property_mask |= PLANE_PROPERTY_IS_PRIMARY;
+ break;
+ } else if(prop->enums[j].value == current_enum_value && strcmp(prop->enums[j].name, "Cursor") == 0) {
+ property_mask |= PLANE_PROPERTY_IS_CURSOR;
+ break;
}
-
- drmModeFreeProperty(prop);
- return is_cursor;
}
- drmModeFreeProperty(prop);
}
+
+ drmModeFreeProperty(prop);
}
drmModeFreeObjectProperties(props);
- return false;
+ return property_mask;
}
/* Returns 0 if not found */
-static uint32_t get_connector_by_crtc_id(const connector_to_crtc_map *c2crtc_map, uint32_t crtc_id) {
+static const connector_crtc_pair* get_connector_pair_by_crtc_id(const connector_to_crtc_map *c2crtc_map, uint32_t crtc_id) {
for(int i = 0; i < c2crtc_map->num_maps; ++i) {
if(c2crtc_map->maps[i].crtc_id == crtc_id)
- return c2crtc_map->maps[i].connector_id;
+ return &c2crtc_map->maps[i];
}
- return 0;
+ return NULL;
}
-static int kms_get_plane_ids(gsr_drm *drm) {
- drmModePlaneResPtr planes = NULL;
- drmModeResPtr resources = NULL;
- int result = -1;
-
- connector_to_crtc_map c2crtc_map;
- c2crtc_map.num_maps = 0;
-
- if(drmSetClientCap(drm->drmfd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) != 0) {
- fprintf(stderr, "kms server error: drmSetClientCap DRM_CLIENT_CAP_UNIVERSAL_PLANES failed, error: %s\n", strerror(errno));
- goto error;
- }
+static void map_crtc_to_connector_ids(gsr_drm *drm, connector_to_crtc_map *c2crtc_map) {
+ c2crtc_map->num_maps = 0;
+ drmModeResPtr resources = drmModeGetResources(drm->drmfd);
+ if(!resources)
+ return;
- if(drmSetClientCap(drm->drmfd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) {
- fprintf(stderr, "kms server warning: drmSetClientCap DRM_CLIENT_CAP_ATOMIC failed, error: %s. The wrong monitor may be captured as a result\n", strerror(errno));
- }
+ for(int i = 0; i < resources->count_connectors && c2crtc_map->num_maps < MAX_CONNECTORS; ++i) {
+ drmModeConnectorPtr connector = drmModeGetConnectorCurrent(drm->drmfd, resources->connectors[i]);
+ if(!connector)
+ continue;
- planes = drmModeGetPlaneResources(drm->drmfd);
- if(!planes) {
- fprintf(stderr, "kms server error: failed to access planes, error: %s\n", strerror(errno));
- goto error;
- }
+ uint64_t crtc_id = 0;
+ connector_get_property_by_name(drm->drmfd, connector, "CRTC_ID", &crtc_id);
- resources = drmModeGetResources(drm->drmfd);
- if(resources) {
- for(int i = 0; i < resources->count_connectors && c2crtc_map.num_maps < MAX_CONNECTORS; ++i) {
- drmModeConnectorPtr connector = drmModeGetConnectorCurrent(drm->drmfd, resources->connectors[i]);
- if(connector) {
- uint64_t crtc_id = 0;
- connector_get_property_by_name(drm->drmfd, connector, "CRTC_ID", &crtc_id);
+ uint64_t hdr_output_metadata_blob_id = 0;
+ connector_get_property_by_name(drm->drmfd, connector, "HDR_OUTPUT_METADATA", &hdr_output_metadata_blob_id);
- c2crtc_map.maps[c2crtc_map.num_maps].connector_id = connector->connector_id;
- c2crtc_map.maps[c2crtc_map.num_maps].crtc_id = crtc_id;
- ++c2crtc_map.num_maps;
+ c2crtc_map->maps[c2crtc_map->num_maps].connector_id = connector->connector_id;
+ c2crtc_map->maps[c2crtc_map->num_maps].crtc_id = crtc_id;
+ c2crtc_map->maps[c2crtc_map->num_maps].hdr_metadata_blob_id = hdr_output_metadata_blob_id;
+ ++c2crtc_map->num_maps;
- drmModeFreeConnector(connector);
- }
- }
- drmModeFreeResources(resources);
+ drmModeFreeConnector(connector);
}
+ drmModeFreeResources(resources);
+}
- for(uint32_t i = 0; i < planes->count_planes && drm->num_plane_ids < GSR_KMS_MAX_PLANES; ++i) {
- drmModePlanePtr plane = drmModeGetPlane(drm->drmfd, planes->planes[i]);
- if(!plane) {
- fprintf(stderr, "kms server warning: failed to get drmModePlanePtr for plane %#x: %s (%d)\n", planes->planes[i], strerror(errno), errno);
+static void drm_mode_cleanup_handles(int drmfd, drmModeFB2Ptr drmfb) {
+ for(int i = 0; i < 4; ++i) {
+ if(!drmfb->handles[i])
continue;
- }
- if(!plane->fb_id) {
- drmModeFreePlane(plane);
- continue;
+ bool already_closed = false;
+ for(int j = 0; j < i; ++j) {
+ if(drmfb->handles[i] == drmfb->handles[j]) {
+ already_closed = true;
+ break;
+ }
}
- if(plane_is_cursor_plane(drm->drmfd, plane->plane_id))
+ if(already_closed)
continue;
- // TODO: Fallback to getfb(1)?
- drmModeFB2Ptr drmfb = drmModeGetFB2(drm->drmfd, plane->fb_id);
- if(drmfb) {
- drm->plane_ids[drm->num_plane_ids] = plane->plane_id;
- drm->connector_ids[drm->num_plane_ids] = get_connector_by_crtc_id(&c2crtc_map, plane->crtc_id);
- ++drm->num_plane_ids;
- drmModeFreeFB2(drmfb);
- }
- drmModeFreePlane(plane);
+ drmCloseBufferHandle(drmfd, drmfb->handles[i]);
}
+}
- result = 0;
-
- error:
- if(planes)
- drmModeFreePlaneResources(planes);
+static bool get_hdr_metadata(int drm_fd, uint64_t hdr_metadata_blob_id, struct hdr_output_metadata *hdr_metadata) {
+ drmModePropertyBlobPtr hdr_metadata_blob = drmModeGetPropertyBlob(drm_fd, hdr_metadata_blob_id);
+ if(!hdr_metadata_blob)
+ return false;
- return result;
-}
+ if(hdr_metadata_blob->length >= sizeof(struct hdr_output_metadata))
+ *hdr_metadata = *(struct hdr_output_metadata*)hdr_metadata_blob->data;
-static bool drmfb_has_multiple_handles(drmModeFB2 *drmfb) {
- int num_handles = 0;
- for(uint32_t handle_index = 0; handle_index < 4 && drmfb->handles[handle_index]; ++handle_index) {
- ++num_handles;
- }
- return num_handles > 1;
+ drmModeFreePropertyBlob(hdr_metadata_blob);
+ return true;
}
-static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response) {
+static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response, connector_to_crtc_map *c2crtc_map) {
int result = -1;
response->result = KMS_RESULT_OK;
response->err_msg[0] = '\0';
response->num_fds = 0;
- for(size_t i = 0; i < drm->num_plane_ids && response->num_fds < GSR_KMS_MAX_PLANES; ++i) {
+ for(uint32_t i = 0; i < drm->planes->count_planes && response->num_fds < GSR_KMS_MAX_PLANES; ++i) {
drmModePlanePtr plane = NULL;
- drmModeFB2 *drmfb = NULL;
+ drmModeFB2Ptr drmfb = NULL;
- plane = drmModeGetPlane(drm->drmfd, drm->plane_ids[i]);
+ plane = drmModeGetPlane(drm->drmfd, drm->planes->planes[i]);
if(!plane) {
response->result = KMS_RESULT_FAILED_TO_GET_PLANE;
- snprintf(response->err_msg, sizeof(response->err_msg), "failed to get drm plane with id %u, error: %s\n", drm->plane_ids[i], strerror(errno));
+ snprintf(response->err_msg, sizeof(response->err_msg), "failed to get drm plane with id %u, error: %s\n", drm->planes->planes[i], strerror(errno));
fprintf(stderr, "kms server error: %s\n", response->err_msg);
goto next;
}
+ if(!plane->fb_id)
+ goto next;
+
drmfb = drmModeGetFB2(drm->drmfd, plane->fb_id);
if(!drmfb) {
// Commented out for now because we get here if the cursor is moved to another monitor and we dont care about the cursor
@@ -241,7 +293,7 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response) {
response->result = KMS_RESULT_FAILED_TO_GET_PLANE;
snprintf(response->err_msg, sizeof(response->err_msg), "drmfb handle is NULL");
fprintf(stderr, "kms server error: %s\n", response->err_msg);
- goto next;
+ goto cleanup_handles;
}
// TODO: Check if dimensions have changed by comparing width and height to previous time this was called.
@@ -253,19 +305,49 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response) {
response->result = KMS_RESULT_FAILED_TO_GET_PLANE;
snprintf(response->err_msg, sizeof(response->err_msg), "failed to get fd from drm handle, error: %s", strerror(errno));
fprintf(stderr, "kms server error: %s\n", response->err_msg);
- continue;
+ goto cleanup_handles;
+ }
+
+ const int fd_index = response->num_fds;
+
+ int x = 0, y = 0, src_x = 0, src_y = 0, src_w = 0, src_h = 0;
+ plane_property_mask property_mask = plane_get_properties(drm->drmfd, plane->plane_id, &x, &y, &src_x, &src_y, &src_w, &src_h);
+ if((property_mask & PLANE_PROPERTY_IS_PRIMARY) || (property_mask & PLANE_PROPERTY_IS_CURSOR)) {
+ const connector_crtc_pair *crtc_pair = get_connector_pair_by_crtc_id(c2crtc_map, plane->crtc_id);
+ if(crtc_pair && crtc_pair->hdr_metadata_blob_id) {
+ response->fds[fd_index].has_hdr_metadata = get_hdr_metadata(drm->drmfd, crtc_pair->hdr_metadata_blob_id, &response->fds[fd_index].hdr_metadata);
+ } else {
+ response->fds[fd_index].has_hdr_metadata = false;
+ }
+
+ response->fds[fd_index].fd = fb_fd;
+ response->fds[fd_index].width = drmfb->width;
+ response->fds[fd_index].height = drmfb->height;
+ response->fds[fd_index].pitch = drmfb->pitches[0];
+ response->fds[fd_index].offset = drmfb->offsets[0];
+ response->fds[fd_index].pixel_format = drmfb->pixel_format;
+ response->fds[fd_index].modifier = drmfb->modifier;
+ response->fds[fd_index].connector_id = crtc_pair ? crtc_pair->connector_id : 0;
+ response->fds[fd_index].is_cursor = property_mask & PLANE_PROPERTY_IS_CURSOR;
+ response->fds[fd_index].is_combined_plane = false;
+ if(property_mask & PLANE_PROPERTY_IS_CURSOR) {
+ response->fds[fd_index].x = x;
+ response->fds[fd_index].y = y;
+ response->fds[fd_index].src_w = 0;
+ response->fds[fd_index].src_h = 0;
+ } else {
+ response->fds[fd_index].x = src_x;
+ response->fds[fd_index].y = src_y;
+ response->fds[fd_index].src_w = src_w;
+ response->fds[fd_index].src_h = src_h;
+ }
+ ++response->num_fds;
+ } else {
+ close(fb_fd);
}
- response->fds[response->num_fds].fd = fb_fd;
- response->fds[response->num_fds].width = drmfb->width;
- response->fds[response->num_fds].height = drmfb->height;
- response->fds[response->num_fds].pitch = drmfb->pitches[0];
- response->fds[response->num_fds].offset = drmfb->offsets[0];
- response->fds[response->num_fds].pixel_format = drmfb->pixel_format;
- response->fds[response->num_fds].modifier = drmfb->modifier;
- response->fds[response->num_fds].connector_id = drm->connector_ids[i];
- response->fds[response->num_fds].is_combined_plane = drmfb_has_multiple_handles(drmfb);
- ++response->num_fds;
+ cleanup_handles:
+ drm_mode_cleanup_handles(drm->drmfd, drmfb);
next:
if(drmfb)
@@ -274,7 +356,10 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response) {
drmModeFreePlane(plane);
}
- if(response->num_fds > 0 || response->result == KMS_RESULT_OK) {
+ if(response->num_fds > 0)
+ response->result = KMS_RESULT_OK;
+
+ if(response->result == KMS_RESULT_OK) {
result = 0;
} else {
for(int i = 0; i < response->num_fds; ++i) {
@@ -304,13 +389,19 @@ static void strncpy_safe(char *dst, const char *src, int len) {
}
int main(int argc, char **argv) {
+ int res = 0;
+ int socket_fd = 0;
+ gsr_drm drm;
+ drm.drmfd = 0;
+ drm.planes = NULL;
+
if(argc != 3) {
- fprintf(stderr, "usage: kms_server <domain_socket_path> <card_path>\n");
+ fprintf(stderr, "usage: gsr-kms-server <domain_socket_path> <card_path>\n");
return 1;
}
const char *domain_socket_path = argv[1];
- int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if(socket_fd == -1) {
fprintf(stderr, "kms server error: failed to create socket, error: %s\n", strerror(errno));
return 2;
@@ -318,19 +409,34 @@ int main(int argc, char **argv) {
const char *card_path = argv[2];
- gsr_drm drm;
- drm.num_plane_ids = 0;
drm.drmfd = open(card_path, O_RDONLY);
if(drm.drmfd < 0) {
fprintf(stderr, "kms server error: failed to open %s, error: %s", card_path, strerror(errno));
- return 2;
+ res = 2;
+ goto done;
}
- if(kms_get_plane_ids(&drm) != 0) {
- close(drm.drmfd);
- return 2;
+ if(drmSetClientCap(drm.drmfd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) != 0) {
+ fprintf(stderr, "kms server error: drmSetClientCap DRM_CLIENT_CAP_UNIVERSAL_PLANES failed, error: %s\n", strerror(errno));
+ res = 2;
+ goto done;
}
+ if(drmSetClientCap(drm.drmfd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) {
+ fprintf(stderr, "kms server warning: drmSetClientCap DRM_CLIENT_CAP_ATOMIC failed, error: %s. The wrong monitor may be captured as a result\n", strerror(errno));
+ }
+
+ drm.planes = drmModeGetPlaneResources(drm.drmfd);
+ if(!drm.planes) {
+ fprintf(stderr, "kms server error: failed to get plane resources, error: %s\n", strerror(errno));
+ res = 2;
+ goto done;
+ }
+
+ connector_to_crtc_map c2crtc_map;
+ c2crtc_map.num_maps = 0;
+ map_crtc_to_connector_ids(&drm, &c2crtc_map);
+
fprintf(stderr, "kms server info: connecting to the client\n");
bool connected = false;
const double connect_timeout_sec = 5.0;
@@ -349,8 +455,8 @@ int main(int argc, char **argv) {
}
fprintf(stderr, "kms server error: connect failed, error: %s (%d)\n", strerror(errno), errno);
- close(drm.drmfd);
- return 2;
+ res = 2;
+ goto done;
}
next:
@@ -361,21 +467,17 @@ int main(int argc, char **argv) {
fprintf(stderr, "kms server info: connected to the client\n");
} else {
fprintf(stderr, "kms server error: failed to connect to the client in %f seconds\n", connect_timeout_sec);
- close(drm.drmfd);
- return 2;
+ res = 2;
+ goto done;
}
- int res = 0;
for(;;) {
gsr_kms_request request;
- struct iovec iov;
- iov.iov_base = &request;
- iov.iov_len = sizeof(request);
-
- struct msghdr request_message = {0};
- request_message.msg_iov = &iov;
- request_message.msg_iovlen = 1;
- const int recv_res = recvmsg(socket_fd, &request_message, MSG_WAITALL);
+ request.version = 0;
+ request.type = -1;
+ request.new_connection_fd = 0;
+
+ const int recv_res = recv_msg_from_client(socket_fd, &request);
if(recv_res == 0) {
fprintf(stderr, "kms server info: kms client shutdown, shutting down the server\n");
res = 3;
@@ -391,40 +493,80 @@ int main(int argc, char **argv) {
continue;
}
+ if(request.version != GSR_KMS_PROTOCOL_VERSION) {
+ fprintf(stderr, "kms server error: expected gpu screen recorder protocol version to be %u, but it's %u\n", GSR_KMS_PROTOCOL_VERSION, request.version);
+ /*
+ if(request.new_connection_fd > 0)
+ close(request.new_connection_fd);
+ */
+ continue;
+ }
+
switch(request.type) {
+ case KMS_REQUEST_TYPE_REPLACE_CONNECTION: {
+ gsr_kms_response response;
+ response.version = GSR_KMS_PROTOCOL_VERSION;
+ response.num_fds = 0;
+
+ if(request.new_connection_fd > 0) {
+ if(socket_fd > 0)
+ close(socket_fd);
+ socket_fd = request.new_connection_fd;
+
+ response.result = KMS_RESULT_OK;
+ if(send_msg_to_client(socket_fd, &response) == -1)
+ fprintf(stderr, "kms server error: failed to respond to client KMS_REQUEST_TYPE_REPLACE_CONNECTION request\n");
+ } else {
+ response.result = KMS_RESULT_INVALID_REQUEST;
+ snprintf(response.err_msg, sizeof(response.err_msg), "received invalid connection fd");
+ fprintf(stderr, "kms server error: %s\n", response.err_msg);
+ if(send_msg_to_client(socket_fd, &response) == -1)
+ fprintf(stderr, "kms server error: failed to respond to client request\n");
+ }
+
+ break;
+ }
case KMS_REQUEST_TYPE_GET_KMS: {
gsr_kms_response response;
+ response.version = GSR_KMS_PROTOCOL_VERSION;
+ response.num_fds = 0;
- if(kms_get_fb(&drm, &response) == 0) {
+ if(kms_get_fb(&drm, &response, &c2crtc_map) == 0) {
if(send_msg_to_client(socket_fd, &response) == -1)
fprintf(stderr, "kms server error: failed to respond to client KMS_REQUEST_TYPE_GET_KMS request\n");
-
- for(int i = 0; i < response.num_fds; ++i) {
- close(response.fds[i].fd);
- }
} else {
if(send_msg_to_client(socket_fd, &response) == -1)
fprintf(stderr, "kms server error: failed to respond to client KMS_REQUEST_TYPE_GET_KMS request\n");
}
+ for(int i = 0; i < response.num_fds; ++i) {
+ close(response.fds[i].fd);
+ }
+
break;
}
default: {
gsr_kms_response response;
+ response.version = GSR_KMS_PROTOCOL_VERSION;
response.result = KMS_RESULT_INVALID_REQUEST;
+ response.num_fds = 0;
+
snprintf(response.err_msg, sizeof(response.err_msg), "invalid request type %d, expected %d (%s)", request.type, KMS_REQUEST_TYPE_GET_KMS, "KMS_REQUEST_TYPE_GET_KMS");
fprintf(stderr, "kms server error: %s\n", response.err_msg);
- if(send_msg_to_client(socket_fd, &response) == -1) {
+ if(send_msg_to_client(socket_fd, &response) == -1)
fprintf(stderr, "kms server error: failed to respond to client request\n");
- break;
- }
+
break;
}
}
}
done:
- close(drm.drmfd);
- close(socket_fd);
+ if(drm.planes)
+ drmModeFreePlaneResources(drm.planes);
+ if(drm.drmfd > 0)
+ close(drm.drmfd);
+ if(socket_fd > 0)
+ close(socket_fd);
return res;
}
diff --git a/kms/server/project.conf b/kms/server/project.conf
index cf863c1..26a1947 100644
--- a/kms/server/project.conf
+++ b/kms/server/project.conf
@@ -4,5 +4,8 @@ type = "executable"
version = "1.0.0"
platforms = ["posix"]
+[config]
+error_on_warning = "true"
+
[dependencies]
libdrm = ">=2"
diff --git a/project.conf b/project.conf
index ee1d00b..a046427 100644
--- a/project.conf
+++ b/project.conf
@@ -1,11 +1,12 @@
[package]
name = "gpu-screen-recorder"
type = "executable"
-version = "2.1.0"
+version = "3.0.0"
platforms = ["posix"]
[config]
ignore_dirs = ["kms/server"]
+error_on_warning = "true"
[dependencies]
libavcodec = ">=58"
@@ -14,9 +15,12 @@ libavutil = ">=56.2"
x11 = ">=1"
xcomposite = ">=0.2"
xrandr = ">=1"
+xfixes = ">=2"
libpulse = ">=13"
libswresample = ">=3"
libavfilter = ">=5"
libva = ">=1"
libcap = ">=2"
-xfixes = ">=2" \ No newline at end of file
+libdrm = ">=2"
+wayland-egl = ">=15"
+wayland-client = ">=1"
diff --git a/scripts/record-application-name.sh b/scripts/record-application-name.sh
new file mode 100755
index 0000000..0411781
--- /dev/null
+++ b/scripts/record-application-name.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+window=$(xdotool selectwindow)
+window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "game")
+window_name="$(echo "$window_name" | tr '/\\' '_')"
+gpu-screen-recorder -w "$window" -f 60 -a "$(pactl get-default-sink).monitor" -o "$HOME/Videos/recording/$window_name/$(date +"Video_%Y-%m-%d_%H-%M-%S.mp4")"
diff --git a/scripts/replay-application-name.sh b/scripts/replay-application-name.sh
new file mode 100755
index 0000000..826ca3b
--- /dev/null
+++ b/scripts/replay-application-name.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+window=$(xdotool selectwindow)
+window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "game")
+window_name="$(echo "$window_name" | tr '/\\' '_')"
+gpu-screen-recorder -w "$window" -f 60 -c mkv -a "$(pactl get-default-sink).monitor" -r 60 -o "$HOME/Videos/replay/$window_name"
diff --git a/scripts/replay.sh b/scripts/replay.sh
index cf6c494..2781e1e 100755
--- a/scripts/replay.sh
+++ b/scripts/replay.sh
@@ -3,4 +3,4 @@
[ "$#" -ne 4 ] && echo "usage: replay.sh <window_id> <fps> <replay_time_sec> <output_directory>" && exit 1
active_sink="$(pactl get-default-sink).monitor"
mkdir -p "$4"
-gpu-screen-recorder -w "$1" -c mp4 -f "$2" -a "$active_sink" -r "$3" -o "$4"
+gpu-screen-recorder -w "$1" -c mkv -f "$2" -a "$active_sink" -r "$3" -o "$4"
diff --git a/scripts/save-replay.sh b/scripts/save-replay.sh
index eac9141..4c62750 100755
--- a/scripts/save-replay.sh
+++ b/scripts/save-replay.sh
@@ -1,4 +1,4 @@
#!/bin/sh -e
killall -SIGUSR1 gpu-screen-recorder
-notify-send -t 5000 -u low -- "GPU Screen Recorder" "Replay saved"
+notify-send -t 1500 -u low -- "GPU Screen Recorder" "Replay saved"
diff --git a/scripts/start-replay.sh b/scripts/start-replay.sh
index 29ff67a..e36d59d 100755
--- a/scripts/start-replay.sh
+++ b/scripts/start-replay.sh
@@ -2,4 +2,4 @@
video_path="$HOME/Videos"
mkdir -p "$video_path"
-gpu-screen-recorder -w screen -f 60 -a "$(pactl get-default-sink).monitor" -c mp4 -r 30 -o "$video_path"
+gpu-screen-recorder -w screen -f 60 -a "$(pactl get-default-sink).monitor" -c mkv -r 30 -o "$video_path"
diff --git a/scripts/toggle-recording-selected.sh b/scripts/toggle-recording-selected.sh
index f87f71c..dcc1ac4 100755
--- a/scripts/toggle-recording-selected.sh
+++ b/scripts/toggle-recording-selected.sh
@@ -5,5 +5,5 @@ window=$(xdotool selectwindow)
active_sink="$(pactl get-default-sink).monitor"
mkdir -p "$HOME/Videos"
video="$HOME/Videos/$(date +"Video_%Y-%m-%d_%H-%M-%S.mp4")"
-notify-send -t 5000 -u low 'GPU Screen Recorder' "Started recording video to $video"
+notify-send -t 1500 -u low 'GPU Screen Recorder' "Started recording video to $video"
gpu-screen-recorder -w "$window" -c mp4 -f 60 -a "$active_sink" -o "$video"
diff --git a/src/capture/capture.c b/src/capture/capture.c
index 699745a..cec0b0d 100644
--- a/src/capture/capture.c
+++ b/src/capture/capture.c
@@ -1,25 +1,38 @@
#include "../../include/capture/capture.h"
+#include "../../include/egl.h"
+#include "../../include/cuda.h"
+#include "../../include/utils.h"
#include <stdio.h>
+#include <stdint.h>
+#include <va/va.h>
+#include <va/va_drmcommon.h>
+#include <libavutil/frame.h>
+#include <libavutil/hwcontext_vaapi.h>
+#include <libavutil/hwcontext_cuda.h>
+#include <libavcodec/avcodec.h>
-int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
+#define FOURCC_NV12 842094158
+#define FOURCC_P010 808530000
+
+int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
if(cap->started)
return -1;
- int res = cap->start(cap, video_codec_context);
+ int res = cap->start(cap, video_codec_context, frame);
if(res == 0)
cap->started = true;
return res;
}
-void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
+void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context) {
if(!cap->started) {
fprintf(stderr, "gsr error: gsp_capture_tick failed: the gsr capture has not been started\n");
return;
}
if(cap->tick)
- cap->tick(cap, video_codec_context, frame);
+ cap->tick(cap, video_codec_context);
}
bool gsr_capture_should_stop(gsr_capture *cap, bool *err) {
@@ -42,6 +55,346 @@ int gsr_capture_capture(gsr_capture *cap, AVFrame *frame) {
return cap->capture(cap, frame);
}
+void gsr_capture_end(gsr_capture *cap, AVFrame *frame) {
+ if(!cap->started) {
+ fprintf(stderr, "gsr error: gsr_capture_end failed: the gsr capture has not been started\n");
+ return;
+ }
+
+ if(!cap->capture_end)
+ return;
+
+ cap->capture_end(cap, frame);
+}
+
void gsr_capture_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
cap->destroy(cap, video_codec_context);
}
+
+static uint32_t fourcc(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
+ return (d << 24) | (c << 16) | (b << 8) | a;
+}
+
+bool gsr_capture_base_setup_vaapi_textures(gsr_capture_base *self, AVFrame *frame, VADisplay va_dpy, VADRMPRIMESurfaceDescriptor *prime, gsr_color_range color_range) {
+ const int res = av_hwframe_get_buffer(self->video_codec_context->hw_frames_ctx, frame, 0);
+ if(res < 0) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: av_hwframe_get_buffer failed: %d\n", res);
+ return false;
+ }
+
+ VASurfaceID target_surface_id = (uintptr_t)frame->data[3];
+
+ VAStatus va_status = vaExportSurfaceHandle(va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_WRITE_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, prime);
+ if(va_status != VA_STATUS_SUCCESS) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: vaExportSurfaceHandle failed, error: %d\n", va_status);
+ return false;
+ }
+ vaSyncSurface(va_dpy, target_surface_id);
+
+ self->egl->glGenTextures(1, &self->input_texture);
+ self->egl->glBindTexture(GL_TEXTURE_2D, self->input_texture);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ self->egl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ self->egl->glGenTextures(1, &self->cursor_texture);
+ self->egl->glBindTexture(GL_TEXTURE_2D, self->cursor_texture);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ self->egl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ const uint32_t formats_nv12[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') };
+ const uint32_t formats_p010[2] = { fourcc('R', '1', '6', ' '), fourcc('G', 'R', '3', '2') };
+
+ if(prime->fourcc == FOURCC_NV12 || prime->fourcc == FOURCC_P010) {
+ const uint32_t *formats = prime->fourcc == FOURCC_NV12 ? formats_nv12 : formats_p010;
+ const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
+
+ self->egl->glGenTextures(2, self->target_textures);
+ for(int i = 0; i < 2; ++i) {
+ const int layer = i;
+ const int plane = 0;
+
+ //const uint64_t modifier = prime->objects[prime->layers[layer].object_index[plane]].drm_format_modifier;
+
+ const intptr_t img_attr[] = {
+ EGL_LINUX_DRM_FOURCC_EXT, formats[i],
+ EGL_WIDTH, prime->width / div[i],
+ EGL_HEIGHT, prime->height / div[i],
+ EGL_DMA_BUF_PLANE0_FD_EXT, prime->objects[prime->layers[layer].object_index[plane]].fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, prime->layers[layer].offset[plane],
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, prime->layers[layer].pitch[plane],
+ // TODO:
+ //EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, modifier & 0xFFFFFFFFULL,
+ //EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, modifier >> 32ULL,
+ EGL_NONE
+ };
+
+ while(self->egl->eglGetError() != EGL_SUCCESS){}
+ EGLImage image = self->egl->eglCreateImage(self->egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
+ if(!image) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: failed to create egl image from drm fd for output drm fd, error: %d\n", self->egl->eglGetError());
+ return false;
+ }
+
+ self->egl->glBindTexture(GL_TEXTURE_2D, self->target_textures[i]);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ while(self->egl->glGetError()) {}
+ while(self->egl->eglGetError() != EGL_SUCCESS){}
+ self->egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+ if(self->egl->glGetError() != 0 || self->egl->eglGetError() != EGL_SUCCESS) {
+ // TODO: Get the error properly
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: failed to bind egl image to gl texture, error: %d\n", self->egl->eglGetError());
+ self->egl->eglDestroyImage(self->egl->egl_display, image);
+ self->egl->glBindTexture(GL_TEXTURE_2D, 0);
+ return false;
+ }
+
+ self->egl->eglDestroyImage(self->egl->egl_display, image);
+ self->egl->glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ gsr_color_conversion_params color_conversion_params = {0};
+ color_conversion_params.color_range = color_range;
+ color_conversion_params.egl = self->egl;
+ color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB;
+ if(prime->fourcc == FOURCC_NV12)
+ color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12;
+ else
+ color_conversion_params.destination_color = GSR_DESTINATION_COLOR_P010;
+
+ color_conversion_params.destination_textures[0] = self->target_textures[0];
+ color_conversion_params.destination_textures[1] = self->target_textures[1];
+ color_conversion_params.num_destination_textures = 2;
+
+ if(gsr_color_conversion_init(&self->color_conversion, &color_conversion_params) != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: failed to create color conversion\n");
+ return false;
+ }
+
+ return true;
+ } else {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_vaapi_textures: unexpected fourcc %u for output drm fd, expected nv12 or p010\n", prime->fourcc);
+ return false;
+ }
+}
+
+static unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format) {
+ unsigned int texture_id = 0;
+ egl->glGenTextures(1, &texture_id);
+ egl->glBindTexture(GL_TEXTURE_2D, texture_id);
+ egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
+
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+ egl->glBindTexture(GL_TEXTURE_2D, 0);
+ return texture_id;
+}
+
+static bool cuda_register_opengl_texture(gsr_cuda *cuda, CUgraphicsResource *cuda_graphics_resource, CUarray *mapped_array, unsigned int texture_id) {
+ CUresult res;
+ res = cuda->cuGraphicsGLRegisterImage(cuda_graphics_resource, texture_id, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_NONE);
+ if (res != CUDA_SUCCESS) {
+ const char *err_str = "unknown";
+ cuda->cuGetErrorString(res, &err_str);
+ fprintf(stderr, "gsr error: cuda_register_opengl_texture: cuGraphicsGLRegisterImage failed, error: %s, texture " "id: %u\n", err_str, texture_id);
+ return false;
+ }
+
+ res = cuda->cuGraphicsResourceSetMapFlags(*cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE);
+ res = cuda->cuGraphicsMapResources(1, cuda_graphics_resource, 0);
+
+ res = cuda->cuGraphicsSubResourceGetMappedArray(mapped_array, *cuda_graphics_resource, 0, 0);
+ return true;
+}
+
+bool gsr_capture_base_setup_cuda_textures(gsr_capture_base *self, AVFrame *frame, gsr_cuda_context *cuda_context, gsr_color_range color_range, gsr_source_color source_color, bool hdr) {
+ // TODO:
+ const int res = av_hwframe_get_buffer(self->video_codec_context->hw_frames_ctx, frame, 0);
+ if(res < 0) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_cuda_textures: av_hwframe_get_buffer failed: %d\n", res);
+ return false;
+ }
+
+ self->egl->glGenTextures(1, &self->input_texture);
+ self->egl->glBindTexture(GL_TEXTURE_2D, self->input_texture);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ self->egl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ self->egl->glGenTextures(1, &self->cursor_texture);
+ self->egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, self->cursor_texture);
+ self->egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ self->egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ self->egl->glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ self->egl->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
+
+ const unsigned int internal_formats_nv12[2] = { GL_R8, GL_RG8 };
+ const unsigned int internal_formats_p010[2] = { GL_R16, GL_RG16 };
+ const unsigned int formats[2] = { GL_RED, GL_RG };
+ const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
+
+ for(int i = 0; i < 2; ++i) {
+ self->target_textures[i] = gl_create_texture(self->egl, self->video_codec_context->width / div[i], self->video_codec_context->height / div[i], !hdr ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]);
+ if(self->target_textures[i] == 0) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_cuda_textures: failed to create opengl texture\n");
+ return false;
+ }
+
+ if(!cuda_register_opengl_texture(cuda_context->cuda, &cuda_context->cuda_graphics_resources[i], &cuda_context->mapped_arrays[i], self->target_textures[i])) {
+ return false;
+ }
+ }
+
+ gsr_color_conversion_params color_conversion_params = {0};
+ color_conversion_params.color_range = color_range;
+ color_conversion_params.egl = self->egl;
+ color_conversion_params.source_color = source_color;
+ if(!hdr)
+ color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12;
+ else
+ color_conversion_params.destination_color = GSR_DESTINATION_COLOR_P010;
+
+ color_conversion_params.destination_textures[0] = self->target_textures[0];
+ color_conversion_params.destination_textures[1] = self->target_textures[1];
+ color_conversion_params.num_destination_textures = 2;
+ color_conversion_params.load_external_image_shader = true;
+
+ if(gsr_color_conversion_init(&self->color_conversion, &color_conversion_params) != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_setup_cuda_textures: failed to create color conversion\n");
+ return false;
+ }
+
+ return true;
+}
+
+void gsr_capture_base_stop(gsr_capture_base *self) {
+ gsr_color_conversion_deinit(&self->color_conversion);
+
+ if(self->egl->egl_context) {
+ if(self->input_texture) {
+ self->egl->glDeleteTextures(1, &self->input_texture);
+ self->input_texture = 0;
+ }
+
+ if(self->cursor_texture) {
+ self->egl->glDeleteTextures(1, &self->cursor_texture);
+ self->cursor_texture = 0;
+ }
+
+ self->egl->glDeleteTextures(2, self->target_textures);
+ self->target_textures[0] = 0;
+ self->target_textures[1] = 0;
+ }
+
+ if(self->video_codec_context->hw_device_ctx)
+ av_buffer_unref(&self->video_codec_context->hw_device_ctx);
+ if(self->video_codec_context->hw_frames_ctx)
+ av_buffer_unref(&self->video_codec_context->hw_frames_ctx);
+}
+
+bool drm_create_codec_context(const char *card_path, AVCodecContext *video_codec_context, int width, int height, bool hdr, VADisplay *va_dpy) {
+ char render_path[128];
+ if(!gsr_card_path_get_render_path(card_path, render_path)) {
+ fprintf(stderr, "gsr error: failed to get /dev/dri/renderDXXX file from %s\n", card_path);
+ return false;
+ }
+
+ AVBufferRef *device_ctx;
+ if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, render_path, NULL, 0) < 0) {
+ fprintf(stderr, "Error: Failed to create hardware device context\n");
+ return false;
+ }
+
+ AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
+ if(!frame_context) {
+ fprintf(stderr, "Error: Failed to create hwframe context\n");
+ av_buffer_unref(&device_ctx);
+ return false;
+ }
+
+ AVHWFramesContext *hw_frame_context =
+ (AVHWFramesContext *)frame_context->data;
+ hw_frame_context->width = width;
+ hw_frame_context->height = height;
+ hw_frame_context->sw_format = hdr ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12;
+ hw_frame_context->format = video_codec_context->pix_fmt;
+ hw_frame_context->device_ref = device_ctx;
+ hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
+
+ //hw_frame_context->initial_pool_size = 20;
+
+ AVVAAPIDeviceContext *vactx =((AVHWDeviceContext*)device_ctx->data)->hwctx;
+ *va_dpy = vactx->display;
+
+ if (av_hwframe_ctx_init(frame_context) < 0) {
+ fprintf(stderr, "Error: Failed to initialize hardware frame context "
+ "(note: ffmpeg version needs to be > 4.0)\n");
+ av_buffer_unref(&device_ctx);
+ //av_buffer_unref(&frame_context);
+ return false;
+ }
+
+ video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx);
+ video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context);
+ return true;
+}
+
+bool cuda_create_codec_context(CUcontext cu_ctx, AVCodecContext *video_codec_context, int width, int height, bool hdr, CUstream *cuda_stream) {
+ AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
+ if(!device_ctx) {
+ fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hardware device context\n");
+ return false;
+ }
+
+ AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data;
+ AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx;
+ cuda_device_context->cuda_ctx = cu_ctx;
+ if(av_hwdevice_ctx_init(device_ctx) < 0) {
+ fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hardware device context\n");
+ av_buffer_unref(&device_ctx);
+ return false;
+ }
+
+ AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
+ if(!frame_context) {
+ fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hwframe context\n");
+ av_buffer_unref(&device_ctx);
+ return false;
+ }
+
+ AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)frame_context->data;
+ hw_frame_context->width = width;
+ hw_frame_context->height = height;
+ hw_frame_context->sw_format = hdr ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12;
+ hw_frame_context->format = video_codec_context->pix_fmt;
+ hw_frame_context->device_ref = device_ctx;
+ hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
+
+ if (av_hwframe_ctx_init(frame_context) < 0) {
+ fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to initialize hardware frame context "
+ "(note: ffmpeg version needs to be > 4.0)\n");
+ av_buffer_unref(&device_ctx);
+ //av_buffer_unref(&frame_context);
+ return false;
+ }
+
+ *cuda_stream = cuda_device_context->stream;
+ video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx);
+ video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context);
+ return true;
+}
diff --git a/src/capture/kms.c b/src/capture/kms.c
new file mode 100644
index 0000000..16b20b7
--- /dev/null
+++ b/src/capture/kms.c
@@ -0,0 +1,369 @@
+#include "../../include/capture/kms.h"
+#include "../../include/capture/capture.h"
+#include "../../include/utils.h"
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/mastering_display_metadata.h>
+
+#define HDMI_STATIC_METADATA_TYPE1 0
+#define HDMI_EOTF_SMPTE_ST2084 2
+
+/* TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc. */
+
+typedef struct {
+ MonitorId *monitor_id;
+ const char *monitor_to_capture;
+ int monitor_to_capture_len;
+ int num_monitors;
+} MonitorCallbackUserdata;
+
+static void monitor_callback(const gsr_monitor *monitor, void *userdata) {
+ MonitorCallbackUserdata *monitor_callback_userdata = userdata;
+ ++monitor_callback_userdata->num_monitors;
+
+ if(monitor_callback_userdata->monitor_to_capture_len != monitor->name_len || memcmp(monitor_callback_userdata->monitor_to_capture, monitor->name, monitor->name_len) != 0)
+ return;
+
+ if(monitor_callback_userdata->monitor_id->num_connector_ids < MAX_CONNECTOR_IDS) {
+ monitor_callback_userdata->monitor_id->connector_ids[monitor_callback_userdata->monitor_id->num_connector_ids] = monitor->connector_id;
+ ++monitor_callback_userdata->monitor_id->num_connector_ids;
+ }
+
+ if(monitor_callback_userdata->monitor_id->num_connector_ids == MAX_CONNECTOR_IDS)
+ fprintf(stderr, "gsr warning: reached max connector ids\n");
+}
+
+static int max_int(int a, int b) {
+ return a > b ? a : b;
+}
+
+int gsr_capture_kms_start(gsr_capture_kms *self, const char *display_to_capture, gsr_egl *egl, AVCodecContext *video_codec_context, AVFrame *frame) {
+ memset(self, 0, sizeof(*self));
+ self->base.video_codec_context = video_codec_context;
+ self->base.egl = egl;
+
+ gsr_monitor monitor;
+ self->monitor_id.num_connector_ids = 0;
+
+ int kms_init_res = gsr_kms_client_init(&self->kms_client, egl->card_path);
+ if(kms_init_res != 0)
+ return kms_init_res;
+
+ MonitorCallbackUserdata monitor_callback_userdata = {
+ &self->monitor_id,
+ display_to_capture, strlen(display_to_capture),
+ 0,
+ };
+ for_each_active_monitor_output(egl, GSR_CONNECTION_DRM, monitor_callback, &monitor_callback_userdata);
+
+ if(!get_monitor_by_name(egl, GSR_CONNECTION_DRM, display_to_capture, &monitor)) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_start: failed to find monitor by name \"%s\"\n", display_to_capture);
+ return -1;
+ }
+
+ monitor.name = display_to_capture;
+ self->monitor_rotation = drm_monitor_get_display_server_rotation(egl, &monitor);
+
+ self->capture_pos = monitor.pos;
+ if(self->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270) {
+ self->capture_size.x = monitor.size.y;
+ self->capture_size.y = monitor.size.x;
+ } else {
+ self->capture_size = monitor.size;
+ }
+
+ /* Disable vsync */
+ egl->eglSwapInterval(egl->egl_display, 0);
+
+ self->base.video_codec_context->width = max_int(2, even_number_ceil(self->capture_size.x));
+ self->base.video_codec_context->height = max_int(2, even_number_ceil(self->capture_size.y));
+
+ frame->width = self->base.video_codec_context->width;
+ frame->height = self->base.video_codec_context->height;
+ return 0;
+}
+
+void gsr_capture_kms_stop(gsr_capture_kms *self) {
+ gsr_capture_kms_cleanup_kms_fds(self);
+ gsr_kms_client_deinit(&self->kms_client);
+ gsr_capture_base_stop(&self->base);
+}
+
+static float monitor_rotation_to_radians(gsr_monitor_rotation rot) {
+ switch(rot) {
+ case GSR_MONITOR_ROT_0: return 0.0f;
+ case GSR_MONITOR_ROT_90: return M_PI_2;
+ case GSR_MONITOR_ROT_180: return M_PI;
+ case GSR_MONITOR_ROT_270: return M_PI + M_PI_2;
+ }
+ return 0.0f;
+}
+
+/* Prefer non combined planes */
+static gsr_kms_response_fd* find_drm_by_connector_id(gsr_kms_response *kms_response, uint32_t connector_id) {
+ int index_combined = -1;
+ for(int i = 0; i < kms_response->num_fds; ++i) {
+ if(kms_response->fds[i].connector_id == connector_id && !kms_response->fds[i].is_cursor) {
+ if(kms_response->fds[i].is_combined_plane)
+ index_combined = i;
+ else
+ return &kms_response->fds[i];
+ }
+ }
+
+ if(index_combined != -1)
+ return &kms_response->fds[index_combined];
+ else
+ return NULL;
+}
+
+static gsr_kms_response_fd* find_first_combined_drm(gsr_kms_response *kms_response) {
+ for(int i = 0; i < kms_response->num_fds; ++i) {
+ if(kms_response->fds[i].is_combined_plane && !kms_response->fds[i].is_cursor)
+ return &kms_response->fds[i];
+ }
+ return NULL;
+}
+
+static gsr_kms_response_fd* find_largest_drm(gsr_kms_response *kms_response) {
+ if(kms_response->num_fds == 0)
+ return NULL;
+
+ int64_t largest_size = 0;
+ gsr_kms_response_fd *largest_drm = &kms_response->fds[0];
+ for(int i = 0; i < kms_response->num_fds; ++i) {
+ const int64_t size = (int64_t)kms_response->fds[i].width * (int64_t)kms_response->fds[i].height;
+ if(size > largest_size && !kms_response->fds[i].is_cursor) {
+ largest_size = size;
+ largest_drm = &kms_response->fds[i];
+ }
+ }
+ return largest_drm;
+}
+
+static gsr_kms_response_fd* find_cursor_drm(gsr_kms_response *kms_response) {
+ for(int i = 0; i < kms_response->num_fds; ++i) {
+ if(kms_response->fds[i].is_cursor)
+ return &kms_response->fds[i];
+ }
+ return NULL;
+}
+
+static bool hdr_metadata_is_supported_format(const struct hdr_output_metadata *hdr_metadata) {
+ return hdr_metadata->metadata_type == HDMI_STATIC_METADATA_TYPE1 &&
+ hdr_metadata->hdmi_metadata_type1.metadata_type == HDMI_STATIC_METADATA_TYPE1 &&
+ hdr_metadata->hdmi_metadata_type1.eotf == HDMI_EOTF_SMPTE_ST2084;
+}
+
+static void gsr_kms_set_hdr_metadata(gsr_capture_kms *self, AVFrame *frame, gsr_kms_response_fd *drm_fd) {
+ if(!self->mastering_display_metadata)
+ self->mastering_display_metadata = av_mastering_display_metadata_create_side_data(frame);
+
+ if(!self->light_metadata)
+ self->light_metadata = av_content_light_metadata_create_side_data(frame);
+
+ if(self->mastering_display_metadata) {
+ for(int i = 0; i < 3; ++i) {
+ self->mastering_display_metadata->display_primaries[i][0] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.display_primaries[i].x, 50000);
+ self->mastering_display_metadata->display_primaries[i][1] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.display_primaries[i].y, 50000);
+ }
+
+ self->mastering_display_metadata->white_point[0] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.white_point.x, 50000);
+ self->mastering_display_metadata->white_point[1] = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.white_point.y, 50000);
+
+ self->mastering_display_metadata->min_luminance = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance, 10000);
+ self->mastering_display_metadata->max_luminance = av_make_q(drm_fd->hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance, 1);
+
+ self->mastering_display_metadata->has_primaries = self->mastering_display_metadata->display_primaries[0][0].num > 0;
+ self->mastering_display_metadata->has_luminance = self->mastering_display_metadata->max_luminance.num > 0;
+ }
+
+ if(self->light_metadata) {
+ self->light_metadata->MaxCLL = drm_fd->hdr_metadata.hdmi_metadata_type1.max_cll;
+ self->light_metadata->MaxFALL = drm_fd->hdr_metadata.hdmi_metadata_type1.max_fall;
+ }
+}
+
+static vec2i swap_vec2i(vec2i value) {
+ int tmp = value.x;
+ value.x = value.y;
+ value.y = tmp;
+ return value;
+}
+
+bool gsr_capture_kms_capture(gsr_capture_kms *self, AVFrame *frame, bool hdr, bool screen_plane_use_modifiers, bool cursor_texture_is_external, bool record_cursor) {
+ //egl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ self->base.egl->glClear(0);
+
+ gsr_capture_kms_cleanup_kms_fds(self);
+
+ gsr_kms_response_fd *drm_fd = NULL;
+ gsr_kms_response_fd *cursor_drm_fd = NULL;
+ bool capture_is_combined_plane = false;
+
+ if(gsr_kms_client_get_kms(&self->kms_client, &self->kms_response) != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_capture: failed to get kms, error: %d (%s)\n", self->kms_response.result, self->kms_response.err_msg);
+ return false;
+ }
+
+ if(self->kms_response.num_fds == 0) {
+ static bool error_shown = false;
+ if(!error_shown) {
+ error_shown = true;
+ fprintf(stderr, "gsr error: no drm found, capture will fail\n");
+ }
+ return false;
+ }
+
+ for(int i = 0; i < self->monitor_id.num_connector_ids; ++i) {
+ drm_fd = find_drm_by_connector_id(&self->kms_response, self->monitor_id.connector_ids[i]);
+ if(drm_fd)
+ break;
+ }
+
+ // Will never happen on wayland unless the target monitor has been disconnected
+ if(!drm_fd) {
+ drm_fd = find_first_combined_drm(&self->kms_response);
+ if(!drm_fd)
+ drm_fd = find_largest_drm(&self->kms_response);
+ capture_is_combined_plane = true;
+ }
+
+ cursor_drm_fd = find_cursor_drm(&self->kms_response);
+
+ if(!drm_fd)
+ return false;
+
+ if(!capture_is_combined_plane && cursor_drm_fd && cursor_drm_fd->connector_id != drm_fd->connector_id)
+ cursor_drm_fd = NULL;
+
+ if(drm_fd->has_hdr_metadata && hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata))
+ gsr_kms_set_hdr_metadata(self, frame, drm_fd);
+
+ // TODO: This causes a crash sometimes on steam deck, why? is it a driver bug? a vaapi pure version doesn't cause a crash.
+ // Even ffmpeg kmsgrab causes this crash. The error is:
+ // amdgpu: Failed to allocate a buffer:
+ // amdgpu: size : 28508160 bytes
+ // amdgpu: alignment : 2097152 bytes
+ // amdgpu: domains : 4
+ // amdgpu: flags : 4
+ // amdgpu: Failed to allocate a buffer:
+ // amdgpu: size : 28508160 bytes
+ // amdgpu: alignment : 2097152 bytes
+ // amdgpu: domains : 4
+ // amdgpu: flags : 4
+ // EE ../jupiter-mesa/src/gallium/drivers/radeonsi/radeon_vcn_enc.c:516 radeon_create_encoder UVD - Can't create CPB buffer.
+ // [hevc_vaapi @ 0x55ea72b09840] Failed to upload encode parameters: 2 (resource allocation failed).
+ // [hevc_vaapi @ 0x55ea72b09840] Encode failed: -5.
+ // Error: avcodec_send_frame failed, error: Input/output error
+ // Assertion pic->display_order == pic->encode_order failed at libavcodec/vaapi_encode_h265.c:765
+ // kms server info: kms client shutdown, shutting down the server
+ intptr_t img_attr[18] = {
+ EGL_LINUX_DRM_FOURCC_EXT, drm_fd->pixel_format,
+ EGL_WIDTH, drm_fd->width,
+ EGL_HEIGHT, drm_fd->height,
+ EGL_DMA_BUF_PLANE0_FD_EXT, drm_fd->fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, drm_fd->offset,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, drm_fd->pitch,
+ };
+
+ if(screen_plane_use_modifiers) {
+ img_attr[12] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+ img_attr[13] = drm_fd->modifier & 0xFFFFFFFFULL;
+
+ img_attr[14] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+ img_attr[15] = drm_fd->modifier >> 32ULL;
+
+ img_attr[16] = EGL_NONE;
+ img_attr[17] = EGL_NONE;
+ } else {
+ img_attr[12] = EGL_NONE;
+ img_attr[13] = EGL_NONE;
+ }
+
+ EGLImage image = self->base.egl->eglCreateImage(self->base.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
+ self->base.egl->glBindTexture(GL_TEXTURE_2D, self->base.input_texture);
+ self->base.egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
+ self->base.egl->eglDestroyImage(self->base.egl->egl_display, image);
+ self->base.egl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ vec2i capture_pos = self->capture_pos;
+ if(!capture_is_combined_plane)
+ capture_pos = (vec2i){drm_fd->x, drm_fd->y};
+
+ const float texture_rotation = monitor_rotation_to_radians(self->monitor_rotation);
+
+ gsr_color_conversion_draw(&self->base.color_conversion, self->base.input_texture,
+ (vec2i){0, 0}, self->capture_size,
+ capture_pos, self->capture_size,
+ texture_rotation, false);
+
+ if(record_cursor && cursor_drm_fd) {
+ const vec2i cursor_size = {cursor_drm_fd->width, cursor_drm_fd->height};
+ vec2i cursor_pos = {cursor_drm_fd->x, cursor_drm_fd->y};
+ switch(self->monitor_rotation) {
+ case GSR_MONITOR_ROT_0:
+ break;
+ case GSR_MONITOR_ROT_90:
+ cursor_pos = swap_vec2i(cursor_pos);
+ cursor_pos.x = self->capture_size.x - cursor_pos.x;
+ // TODO: Remove this horrible hack
+ cursor_pos.x -= cursor_size.x;
+ break;
+ case GSR_MONITOR_ROT_180:
+ cursor_pos.x = self->capture_size.x - cursor_pos.x;
+ cursor_pos.y = self->capture_size.y - cursor_pos.y;
+ // TODO: Remove this horrible hack
+ cursor_pos.x -= cursor_size.x;
+ cursor_pos.y -= cursor_size.y;
+ break;
+ case GSR_MONITOR_ROT_270:
+ cursor_pos = swap_vec2i(cursor_pos);
+ cursor_pos.y = self->capture_size.y - cursor_pos.y;
+ // TODO: Remove this horrible hack
+ cursor_pos.y -= cursor_size.y;
+ break;
+ }
+
+ const intptr_t img_attr_cursor[] = {
+ EGL_LINUX_DRM_FOURCC_EXT, cursor_drm_fd->pixel_format,
+ EGL_WIDTH, cursor_drm_fd->width,
+ EGL_HEIGHT, cursor_drm_fd->height,
+ EGL_DMA_BUF_PLANE0_FD_EXT, cursor_drm_fd->fd,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, cursor_drm_fd->offset,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT, cursor_drm_fd->pitch,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, cursor_drm_fd->modifier & 0xFFFFFFFFULL,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, cursor_drm_fd->modifier >> 32ULL,
+ EGL_NONE
+ };
+
+ EGLImage cursor_image = self->base.egl->eglCreateImage(self->base.egl->egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr_cursor);
+ const int target = cursor_texture_is_external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
+ self->base.egl->glBindTexture(target, self->base.cursor_texture);
+ self->base.egl->glEGLImageTargetTexture2DOES(target, cursor_image);
+ self->base.egl->eglDestroyImage(self->base.egl->egl_display, cursor_image);
+ self->base.egl->glBindTexture(target, 0);
+
+ gsr_color_conversion_draw(&self->base.color_conversion, self->base.cursor_texture,
+ cursor_pos, cursor_size,
+ (vec2i){0, 0}, cursor_size,
+ texture_rotation, cursor_texture_is_external);
+ }
+
+ self->base.egl->eglSwapBuffers(self->base.egl->egl_display, self->base.egl->egl_surface);
+ //self->base.egl->glFlush();
+ //self->base.egl->glFinish();
+
+ return true;
+}
+
+void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) {
+ for(int i = 0; i < self->kms_response.num_fds; ++i) {
+ if(self->kms_response.fds[i].fd > 0)
+ close(self->kms_response.fds[i].fd);
+ self->kms_response.fds[i].fd = 0;
+ }
+ self->kms_response.num_fds = 0;
+}
diff --git a/src/capture/kms_cuda.c b/src/capture/kms_cuda.c
new file mode 100644
index 0000000..a9f1f8e
--- /dev/null
+++ b/src/capture/kms_cuda.c
@@ -0,0 +1,181 @@
+#include "../../include/capture/kms_cuda.h"
+#include "../../include/capture/kms.h"
+#include "../../include/cuda.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_cuda.h>
+#include <libavcodec/avcodec.h>
+
+typedef struct {
+ gsr_capture_kms kms;
+
+ gsr_capture_kms_cuda_params params;
+
+ gsr_cuda cuda;
+ CUgraphicsResource cuda_graphics_resources[2];
+ CUarray mapped_arrays[2];
+ CUstream cuda_stream;
+} gsr_capture_kms_cuda;
+
+static void gsr_capture_kms_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context);
+
+static int gsr_capture_kms_cuda_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
+ gsr_capture_kms_cuda *cap_kms = cap->priv;
+
+ const int res = gsr_capture_kms_start(&cap_kms->kms, cap_kms->params.display_to_capture, cap_kms->params.egl, video_codec_context, frame);
+ if(res != 0) {
+ gsr_capture_kms_cuda_stop(cap, video_codec_context);
+ return res;
+ }
+
+ // TODO: overclocking is not supported on wayland...
+ if(!gsr_cuda_load(&cap_kms->cuda, NULL, false)) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_cuda_start: failed to load cuda\n");
+ gsr_capture_kms_cuda_stop(cap, video_codec_context);
+ return -1;
+ }
+
+ if(!cuda_create_codec_context(cap_kms->cuda.cu_ctx, video_codec_context, video_codec_context->width, video_codec_context->height, cap_kms->params.hdr, &cap_kms->cuda_stream)) {
+ gsr_capture_kms_cuda_stop(cap, video_codec_context);
+ return -1;
+ }
+
+ gsr_cuda_context cuda_context = {
+ .cuda = &cap_kms->cuda,
+ .cuda_graphics_resources = cap_kms->cuda_graphics_resources,
+ .mapped_arrays = cap_kms->mapped_arrays
+ };
+
+ if(!gsr_capture_base_setup_cuda_textures(&cap_kms->kms.base, frame, &cuda_context, cap_kms->params.color_range, GSR_SOURCE_COLOR_RGB, cap_kms->params.hdr)) {
+ gsr_capture_kms_cuda_stop(cap, video_codec_context);
+ return -1;
+ }
+
+ return 0;
+}
+
+static bool gsr_capture_kms_cuda_should_stop(gsr_capture *cap, bool *err) {
+ gsr_capture_kms_cuda *cap_kms = cap->priv;
+ if(cap_kms->kms.should_stop) {
+ if(err)
+ *err = cap_kms->kms.stop_is_error;
+ return true;
+ }
+
+ if(err)
+ *err = false;
+ return false;
+}
+
+static void gsr_capture_kms_unload_cuda_graphics(gsr_capture_kms_cuda *cap_kms) {
+ if(cap_kms->cuda.cu_ctx) {
+ for(int i = 0; i < 2; ++i) {
+ if(cap_kms->cuda_graphics_resources[i]) {
+ cap_kms->cuda.cuGraphicsUnmapResources(1, &cap_kms->cuda_graphics_resources[i], 0);
+ cap_kms->cuda.cuGraphicsUnregisterResource(cap_kms->cuda_graphics_resources[i]);
+ cap_kms->cuda_graphics_resources[i] = 0;
+ }
+ }
+ }
+}
+
+static int gsr_capture_kms_cuda_capture(gsr_capture *cap, AVFrame *frame) {
+ gsr_capture_kms_cuda *cap_kms = cap->priv;
+
+ gsr_capture_kms_capture(&cap_kms->kms, frame, cap_kms->params.hdr, true, true, cap_kms->params.record_cursor);
+
+ const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
+ for(int i = 0; i < 2; ++i) {
+ CUDA_MEMCPY2D memcpy_struct;
+ memcpy_struct.srcXInBytes = 0;
+ memcpy_struct.srcY = 0;
+ memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY;
+
+ memcpy_struct.dstXInBytes = 0;
+ memcpy_struct.dstY = 0;
+ memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE;
+
+ memcpy_struct.srcArray = cap_kms->mapped_arrays[i];
+ memcpy_struct.srcPitch = frame->width / div[i];
+ memcpy_struct.dstDevice = (CUdeviceptr)frame->data[i];
+ memcpy_struct.dstPitch = frame->linesize[i];
+ memcpy_struct.WidthInBytes = frame->width * (cap_kms->params.hdr ? 2 : 1);
+ memcpy_struct.Height = frame->height / div[i];
+ // TODO: Remove this copy if possible
+ cap_kms->cuda.cuMemcpy2DAsync_v2(&memcpy_struct, cap_kms->cuda_stream);
+ }
+
+ // TODO: needed?
+ cap_kms->cuda.cuStreamSynchronize(cap_kms->cuda_stream);
+
+ return 0;
+}
+
+static void gsr_capture_kms_cuda_capture_end(gsr_capture *cap, AVFrame *frame) {
+ (void)frame;
+ gsr_capture_kms_cuda *cap_kms = cap->priv;
+ gsr_capture_kms_cleanup_kms_fds(&cap_kms->kms);
+}
+
+static void gsr_capture_kms_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context) {
+ (void)video_codec_context;
+ gsr_capture_kms_cuda *cap_kms = cap->priv;
+ gsr_capture_kms_unload_cuda_graphics(cap_kms);
+ gsr_cuda_unload(&cap_kms->cuda);
+ gsr_capture_kms_stop(&cap_kms->kms);
+}
+
+static void gsr_capture_kms_cuda_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
+ (void)video_codec_context;
+ gsr_capture_kms_cuda *cap_kms = cap->priv;
+ if(cap->priv) {
+ gsr_capture_kms_cuda_stop(cap, video_codec_context);
+ free((void*)cap_kms->params.display_to_capture);
+ cap_kms->params.display_to_capture = NULL;
+ free(cap->priv);
+ cap->priv = NULL;
+ }
+ free(cap);
+}
+
+gsr_capture* gsr_capture_kms_cuda_create(const gsr_capture_kms_cuda_params *params) {
+ if(!params) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_cuda_create params is NULL\n");
+ return NULL;
+ }
+
+ gsr_capture *cap = calloc(1, sizeof(gsr_capture));
+ if(!cap)
+ return NULL;
+
+ gsr_capture_kms_cuda *cap_kms = calloc(1, sizeof(gsr_capture_kms_cuda));
+ if(!cap_kms) {
+ free(cap);
+ return NULL;
+ }
+
+ const char *display_to_capture = strdup(params->display_to_capture);
+ if(!display_to_capture) {
+ free(cap);
+ free(cap_kms);
+ return NULL;
+ }
+
+ cap_kms->params = *params;
+ cap_kms->params.display_to_capture = display_to_capture;
+
+ *cap = (gsr_capture) {
+ .start = gsr_capture_kms_cuda_start,
+ .tick = NULL,
+ .should_stop = gsr_capture_kms_cuda_should_stop,
+ .capture = gsr_capture_kms_cuda_capture,
+ .capture_end = gsr_capture_kms_cuda_capture_end,
+ .destroy = gsr_capture_kms_cuda_destroy,
+ .priv = cap_kms
+ };
+
+ return cap;
+}
diff --git a/src/capture/kms_vaapi.c b/src/capture/kms_vaapi.c
index 16c746c..a7e8182 100644
--- a/src/capture/kms_vaapi.c
+++ b/src/capture/kms_vaapi.c
@@ -1,401 +1,52 @@
#include "../../include/capture/kms_vaapi.h"
-#include "../../kms/client/kms_client.h"
-#include "../../include/egl.h"
-#include "../../include/utils.h"
-#include "../../include/color_conversion.h"
-#include "../../include/cursor.h"
+#include "../../include/capture/kms.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
-#include <X11/Xlib.h>
-#include <X11/Xatom.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_vaapi.h>
-#include <libavutil/frame.h>
#include <libavcodec/avcodec.h>
-#include <va/va.h>
#include <va/va_drmcommon.h>
-#define MAX_CONNECTOR_IDS 32
-
typedef struct {
- uint32_t connector_ids[MAX_CONNECTOR_IDS];
- int num_connector_ids;
-} MonitorId;
-
-typedef enum {
- X11_ROT_0 = 1 << 0,
- X11_ROT_90 = 1 << 1,
- X11_ROT_180 = 1 << 2,
- X11_ROT_270 = 1 << 3
-} X11Rotation;
+ gsr_capture_kms kms;
-typedef struct {
gsr_capture_kms_vaapi_params params;
- Display *dpy;
- XEvent xev;
-
- bool should_stop;
- bool stop_is_error;
- bool created_hw_frame;
-
- gsr_egl egl;
-
- gsr_kms_client kms_client;
- gsr_kms_response kms_response;
-
- vec2i screen_size;
- vec2i capture_pos;
- vec2i capture_size;
- bool screen_capture;
- MonitorId monitor_id;
VADisplay va_dpy;
-
- bool requires_rotation;
- X11Rotation x11_rot;
-
VADRMPRIMESurfaceDescriptor prime;
-
- unsigned int input_texture;
- unsigned int target_textures[2];
-
- gsr_color_conversion color_conversion;
-
- gsr_cursor cursor;
} gsr_capture_kms_vaapi;
-static int max_int(int a, int b) {
- return a > b ? a : b;
-}
-
static void gsr_capture_kms_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context);
-static bool drm_create_codec_context(gsr_capture_kms_vaapi *cap_kms, AVCodecContext *video_codec_context) {
- AVBufferRef *device_ctx;
- if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, cap_kms->params.card_path, NULL, 0) < 0) {
- fprintf(stderr, "Error: Failed to create hardware device context\n");
- return false;
- }
-
- AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
- if(!frame_context) {
- fprintf(stderr, "Error: Failed to create hwframe context\n");
- av_buffer_unref(&device_ctx);
- return false;
- }
-
- AVHWFramesContext *hw_frame_context =
- (AVHWFramesContext *)frame_context->data;
- hw_frame_context->width = video_codec_context->width;
- hw_frame_context->height = video_codec_context->height;
- hw_frame_context->sw_format = AV_PIX_FMT_NV12;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_YUV420P;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_NV12;
- hw_frame_context->format = video_codec_context->pix_fmt;
- hw_frame_context->device_ref = device_ctx;
- hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
-
- hw_frame_context->initial_pool_size = 1; // TODO: (and in other places)
-
- AVVAAPIDeviceContext *vactx =((AVHWDeviceContext*)device_ctx->data)->hwctx;
- cap_kms->va_dpy = vactx->display;
-
- if (av_hwframe_ctx_init(frame_context) < 0) {
- fprintf(stderr, "Error: Failed to initialize hardware frame context "
- "(note: ffmpeg version needs to be > 4.0)\n");
- av_buffer_unref(&device_ctx);
- //av_buffer_unref(&frame_context);
- return false;
- }
-
- video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx);
- video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context);
- return true;
-}
-
-#define DRM_FORMAT_MOD_INVALID 72057594037927935
-
-// TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc.
-
-typedef struct {
- gsr_capture_kms_vaapi *cap_kms;
- const Atom randr_connector_id_atom;
- const char *monitor_to_capture;
- int monitor_to_capture_len;
- int num_monitors;
- int rotation;
-} MonitorCallbackUserdata;
-
-static bool properties_has_atom(Atom *props, int nprop, Atom atom) {
- for(int i = 0; i < nprop; ++i) {
- if(props[i] == atom)
- return true;
- }
- return false;
-}
-
-static void monitor_callback(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata) {
- (void)mode_info;
- MonitorCallbackUserdata *monitor_callback_userdata = userdata;
- ++monitor_callback_userdata->num_monitors;
-
- if(strcmp(monitor_callback_userdata->monitor_to_capture, "screen") == 0)
- monitor_callback_userdata->rotation = crt_info->rotation;
-
- if(monitor_callback_userdata->monitor_to_capture_len != output_info->nameLen || memcmp(monitor_callback_userdata->monitor_to_capture, output_info->name, output_info->nameLen) != 0)
- return;
-
- monitor_callback_userdata->rotation = crt_info->rotation;
- for(int i = 0; i < crt_info->noutput && monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids < MAX_CONNECTOR_IDS; ++i) {
- int nprop = 0;
- Atom *props = XRRListOutputProperties(monitor_callback_userdata->cap_kms->dpy, crt_info->outputs[i], &nprop);
- if(!props)
- continue;
-
- if(!properties_has_atom(props, nprop, monitor_callback_userdata->randr_connector_id_atom)) {
- XFree(props);
- continue;
- }
-
- Atom type = 0;
- int format = 0;
- unsigned long bytes_after = 0;
- unsigned long nitems = 0;
- unsigned char *prop = NULL;
- XRRGetOutputProperty(monitor_callback_userdata->cap_kms->dpy, crt_info->outputs[i],
- monitor_callback_userdata->randr_connector_id_atom,
- 0, 128, false, false, AnyPropertyType,
- &type, &format, &nitems, &bytes_after, &prop);
-
- if(type == XA_INTEGER && format == 32) {
- monitor_callback_userdata->cap_kms->monitor_id.connector_ids[monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids] = *(long*)prop;
- ++monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids;
- }
-
- XFree(props);
- }
-
- if(monitor_callback_userdata->cap_kms->monitor_id.num_connector_ids == MAX_CONNECTOR_IDS)
- fprintf(stderr, "gsr warning: reached max connector ids\n");
-}
-
-static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
+static int gsr_capture_kms_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
gsr_capture_kms_vaapi *cap_kms = cap->priv;
- if(gsr_kms_client_init(&cap_kms->kms_client, cap_kms->params.card_path) != 0) {
- return -1;
- }
-
- const Atom randr_connector_id_atom = XInternAtom(cap_kms->dpy, "CONNECTOR_ID", False);
- cap_kms->monitor_id.num_connector_ids = 0;
- MonitorCallbackUserdata monitor_callback_userdata = {
- cap_kms, randr_connector_id_atom,
- cap_kms->params.display_to_capture, strlen(cap_kms->params.display_to_capture),
- 0,
- X11_ROT_0
- };
- for_each_active_monitor_output(cap_kms->dpy, monitor_callback, &monitor_callback_userdata);
-
- cap_kms->screen_size.x = WidthOfScreen(DefaultScreenOfDisplay(cap_kms->dpy));
- cap_kms->screen_size.y = HeightOfScreen(DefaultScreenOfDisplay(cap_kms->dpy));
-
- gsr_monitor monitor;
- if(strcmp(cap_kms->params.display_to_capture, "screen") == 0) {
- monitor.pos.x = 0;
- monitor.pos.y = 0;
- monitor.size = cap_kms->screen_size;
- cap_kms->screen_capture = true;
- } else if(!get_monitor_by_name(cap_kms->dpy, cap_kms->params.display_to_capture, &monitor)) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_start: failed to find monitor by name \"%s\"\n", cap_kms->params.display_to_capture);
- gsr_capture_kms_vaapi_stop(cap, video_codec_context);
- return -1;
- }
-
- // TODO: Find a better way to do this. Is this info available somewhere in drm? it should be!
-
- // Note: workaround AMD/Intel issue. If there is one monitor enabled and it's rotated then
- // the drm buf will also be rotated. This only happens when you only have one monitor enabled.
- cap_kms->x11_rot = monitor_callback_userdata.rotation;
- if(monitor_callback_userdata.num_monitors == 1 && cap_kms->x11_rot != X11_ROT_0) {
- cap_kms->requires_rotation = true;
- } else {
- cap_kms->requires_rotation = false;
- }
-
- cap_kms->capture_pos = monitor.pos;
- cap_kms->capture_size = monitor.size;
-
- if(!gsr_egl_load(&cap_kms->egl, cap_kms->dpy)) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_start: failed to load opengl\n");
+ int res = gsr_capture_kms_start(&cap_kms->kms, cap_kms->params.display_to_capture, cap_kms->params.egl, video_codec_context, frame);
+ if(res != 0) {
gsr_capture_kms_vaapi_stop(cap, video_codec_context);
- return -1;
+ return res;
}
- /* Disable vsync */
- cap_kms->egl.eglSwapInterval(cap_kms->egl.egl_display, 0);
-
- video_codec_context->width = max_int(2, cap_kms->capture_size.x & ~1);
- video_codec_context->height = max_int(2, cap_kms->capture_size.y & ~1);
-
- if(!drm_create_codec_context(cap_kms, video_codec_context)) {
+ if(!drm_create_codec_context(cap_kms->params.egl->card_path, video_codec_context, video_codec_context->width, video_codec_context->height, cap_kms->params.hdr, &cap_kms->va_dpy)) {
gsr_capture_kms_vaapi_stop(cap, video_codec_context);
return -1;
}
- if(gsr_cursor_init(&cap_kms->cursor, &cap_kms->egl, cap_kms->dpy) != 0) {
+ if(!gsr_capture_base_setup_vaapi_textures(&cap_kms->kms.base, frame, cap_kms->va_dpy, &cap_kms->prime, cap_kms->params.color_range)) {
gsr_capture_kms_vaapi_stop(cap, video_codec_context);
return -1;
}
- gsr_cursor_change_window_target(&cap_kms->cursor, DefaultRootWindow(cap_kms->dpy));
- gsr_cursor_update(&cap_kms->cursor, &cap_kms->xev);
-
return 0;
}
-static uint32_t fourcc(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
- return (d << 24) | (c << 16) | (b << 8) | a;
-}
-
-#define FOURCC_NV12 842094158
-
-static void gsr_capture_kms_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
- gsr_capture_kms_vaapi *cap_kms = cap->priv;
-
- // TODO:
- cap_kms->egl.glClear(GL_COLOR_BUFFER_BIT);
-
- while(XPending(cap_kms->dpy)) {
- XNextEvent(cap_kms->dpy, &cap_kms->xev);
- gsr_cursor_update(&cap_kms->cursor, &cap_kms->xev);
- }
-
- if(!cap_kms->created_hw_frame) {
- cap_kms->created_hw_frame = true;
-
- av_frame_free(frame);
- *frame = av_frame_alloc();
- if(!frame) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to allocate frame\n");
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return;
- }
- (*frame)->format = video_codec_context->pix_fmt;
- (*frame)->width = video_codec_context->width;
- (*frame)->height = video_codec_context->height;
- (*frame)->color_range = video_codec_context->color_range;
- (*frame)->color_primaries = video_codec_context->color_primaries;
- (*frame)->color_trc = video_codec_context->color_trc;
- (*frame)->colorspace = video_codec_context->colorspace;
- (*frame)->chroma_location = video_codec_context->chroma_sample_location;
-
- int res = av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0);
- if(res < 0) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: av_hwframe_get_buffer failed: %d\n", res);
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return;
- }
-
- VASurfaceID target_surface_id = (uintptr_t)(*frame)->data[3];
-
- VAStatus va_status = vaExportSurfaceHandle(cap_kms->va_dpy, target_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_READ_WRITE | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &cap_kms->prime);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: vaExportSurfaceHandle failed, error: %d\n", va_status);
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return;
- }
- vaSyncSurface(cap_kms->va_dpy, target_surface_id);
-
- cap_kms->egl.glGenTextures(1, &cap_kms->input_texture);
- cap_kms->egl.glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- if(cap_kms->prime.fourcc == FOURCC_NV12) {
- cap_kms->egl.glGenTextures(2, cap_kms->target_textures);
- for(int i = 0; i < 2; ++i) {
- const uint32_t formats[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') };
- const int layer = i;
- const int plane = 0;
-
- const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
-
- const intptr_t img_attr[] = {
- EGL_LINUX_DRM_FOURCC_EXT, formats[i],
- EGL_WIDTH, cap_kms->prime.width / div[i],
- EGL_HEIGHT, cap_kms->prime.height / div[i],
- EGL_DMA_BUF_PLANE0_FD_EXT, cap_kms->prime.objects[cap_kms->prime.layers[layer].object_index[plane]].fd,
- EGL_DMA_BUF_PLANE0_OFFSET_EXT, cap_kms->prime.layers[layer].offset[plane],
- EGL_DMA_BUF_PLANE0_PITCH_EXT, cap_kms->prime.layers[layer].pitch[plane],
- EGL_NONE
- };
-
- while(cap_kms->egl.eglGetError() != EGL_SUCCESS){}
- EGLImage image = cap_kms->egl.eglCreateImage(cap_kms->egl.egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
- if(!image) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to create egl image from drm fd for output drm fd, error: %d\n", cap_kms->egl.eglGetError());
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return;
- }
-
- cap_kms->egl.glBindTexture(GL_TEXTURE_2D, cap_kms->target_textures[i]);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- cap_kms->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- while(cap_kms->egl.glGetError()) {}
- while(cap_kms->egl.eglGetError() != EGL_SUCCESS){}
- cap_kms->egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
- if(cap_kms->egl.glGetError() != 0 || cap_kms->egl.eglGetError() != EGL_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to bind egl image to gl texture, error: %d\n", cap_kms->egl.eglGetError());
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- cap_kms->egl.eglDestroyImage(cap_kms->egl.egl_display, image);
- cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
- return;
- }
-
- cap_kms->egl.eglDestroyImage(cap_kms->egl.egl_display, image);
- cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
- }
-
- gsr_color_conversion_params color_conversion_params = {0};
- color_conversion_params.egl = &cap_kms->egl;
- color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB;
- color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12;
-
- color_conversion_params.destination_textures[0] = cap_kms->target_textures[0];
- color_conversion_params.destination_textures[1] = cap_kms->target_textures[1];
- color_conversion_params.num_destination_textures = 2;
-
- if(gsr_color_conversion_init(&cap_kms->color_conversion, &color_conversion_params) != 0) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: failed to create color conversion\n");
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return;
- }
- } else {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_tick: unexpected fourcc %u for output drm fd, expected nv12\n", cap_kms->prime.fourcc);
- cap_kms->should_stop = true;
- cap_kms->stop_is_error = true;
- return;
- }
- }
-}
-
static bool gsr_capture_kms_vaapi_should_stop(gsr_capture *cap, bool *err) {
gsr_capture_kms_vaapi *cap_kms = cap->priv;
- if(cap_kms->should_stop) {
+ if(cap_kms->kms.should_stop) {
if(err)
- *err = cap_kms->stop_is_error;
+ *err = cap_kms->kms.stop_is_error;
return true;
}
@@ -404,180 +55,22 @@ static bool gsr_capture_kms_vaapi_should_stop(gsr_capture *cap, bool *err) {
return false;
}
-static gsr_kms_response_fd* find_drm_by_connector_id(gsr_kms_response *kms_response, uint32_t connector_id) {
- for(int i = 0; i < kms_response->num_fds; ++i) {
- if(kms_response->fds[i].connector_id == connector_id)
- return &kms_response->fds[i];
- }
- return NULL;
-}
-
-static gsr_kms_response_fd* find_first_combined_drm(gsr_kms_response *kms_response) {
- for(int i = 0; i < kms_response->num_fds; ++i) {
- if(kms_response->fds[i].is_combined_plane)
- return &kms_response->fds[i];
- }
- return NULL;
-}
-
-static gsr_kms_response_fd* find_largest_drm(gsr_kms_response *kms_response) {
- if(kms_response->num_fds == 0)
- return NULL;
-
- int64_t largest_size = 0;
- gsr_kms_response_fd *largest_drm = &kms_response->fds[0];
- for(int i = 0; i < kms_response->num_fds; ++i) {
- const int64_t size = (int64_t)kms_response->fds[i].width * (int64_t)kms_response->fds[i].height;
- if(size > largest_size) {
- largest_size = size;
- largest_drm = &kms_response->fds[i];
- }
- }
- return largest_drm;
+static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
+ gsr_capture_kms_vaapi *cap_kms = cap->priv;
+ gsr_capture_kms_capture(&cap_kms->kms, frame, cap_kms->params.hdr, false, false, cap_kms->params.record_cursor);
+ return 0;
}
-static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
+static void gsr_capture_kms_vaapi_capture_end(gsr_capture *cap, AVFrame *frame) {
(void)frame;
gsr_capture_kms_vaapi *cap_kms = cap->priv;
-
- for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) {
- if(cap_kms->kms_response.fds[i].fd > 0)
- close(cap_kms->kms_response.fds[i].fd);
- cap_kms->kms_response.fds[i].fd = 0;
- }
- cap_kms->kms_response.num_fds = 0;
-
- if(gsr_kms_client_get_kms(&cap_kms->kms_client, &cap_kms->kms_response) != 0) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_capture: failed to get kms, error: %d (%s)\n", cap_kms->kms_response.result, cap_kms->kms_response.err_msg);
- return -1;
- }
-
- if(cap_kms->kms_response.num_fds == 0) {
- static bool error_shown = false;
- if(!error_shown) {
- error_shown = true;
- fprintf(stderr, "gsr error: no drm found, capture will fail\n");
- }
- return -1;
- }
-
- bool requires_rotation = cap_kms->requires_rotation;
- bool capture_is_combined_plane = false;
-
- gsr_kms_response_fd *drm_fd = NULL;
- if(cap_kms->screen_capture) {
- drm_fd = find_first_combined_drm(&cap_kms->kms_response);
- if(!drm_fd)
- drm_fd = find_largest_drm(&cap_kms->kms_response);
- capture_is_combined_plane = drm_fd->is_combined_plane || ((int)drm_fd->width == cap_kms->screen_size.x && (int)drm_fd->height == cap_kms->screen_size.y);
- } else {
- for(int i = 0; i < cap_kms->monitor_id.num_connector_ids; ++i) {
- drm_fd = find_drm_by_connector_id(&cap_kms->kms_response, cap_kms->monitor_id.connector_ids[i]);
- if(drm_fd) {
- requires_rotation = cap_kms->x11_rot != X11_ROT_0;
- capture_is_combined_plane = drm_fd->is_combined_plane;
- break;
- }
- }
-
- if(!drm_fd) {
- drm_fd = find_first_combined_drm(&cap_kms->kms_response);
- if(!drm_fd)
- drm_fd = find_largest_drm(&cap_kms->kms_response);
- capture_is_combined_plane = drm_fd->is_combined_plane || ((int)drm_fd->width == cap_kms->screen_size.x && (int)drm_fd->height == cap_kms->screen_size.y);
- }
- }
-
- // TODO: This causes a crash sometimes on steam deck, why? is it a driver bug? a vaapi pure version doesn't cause a crash.
- // Even ffmpeg kmsgrab causes this crash. The error is:
- // amdgpu: Failed to allocate a buffer:
- // amdgpu: size : 28508160 bytes
- // amdgpu: alignment : 2097152 bytes
- // amdgpu: domains : 4
- // amdgpu: flags : 4
- // amdgpu: Failed to allocate a buffer:
- // amdgpu: size : 28508160 bytes
- // amdgpu: alignment : 2097152 bytes
- // amdgpu: domains : 4
- // amdgpu: flags : 4
- // EE ../jupiter-mesa/src/gallium/drivers/radeonsi/radeon_vcn_enc.c:516 radeon_create_encoder UVD - Can't create CPB buffer.
- // [hevc_vaapi @ 0x55ea72b09840] Failed to upload encode parameters: 2 (resource allocation failed).
- // [hevc_vaapi @ 0x55ea72b09840] Encode failed: -5.
- // Error: avcodec_send_frame failed, error: Input/output error
- // Assertion pic->display_order == pic->encode_order failed at libavcodec/vaapi_encode_h265.c:765
- // kms server info: kms client shutdown, shutting down the server
- const intptr_t img_attr[] = {
- EGL_LINUX_DRM_FOURCC_EXT, drm_fd->pixel_format,
- EGL_WIDTH, drm_fd->width,
- EGL_HEIGHT, drm_fd->height,
- EGL_DMA_BUF_PLANE0_FD_EXT, drm_fd->fd,
- EGL_DMA_BUF_PLANE0_OFFSET_EXT, drm_fd->offset,
- EGL_DMA_BUF_PLANE0_PITCH_EXT, drm_fd->pitch,
- EGL_NONE
- };
-
- EGLImage image = cap_kms->egl.eglCreateImage(cap_kms->egl.egl_display, 0, EGL_LINUX_DMA_BUF_EXT, NULL, img_attr);
- cap_kms->egl.glBindTexture(GL_TEXTURE_2D, cap_kms->input_texture);
- cap_kms->egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
- cap_kms->egl.eglDestroyImage(cap_kms->egl.egl_display, image);
- cap_kms->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- float texture_rotation = 0.0f;
- if(requires_rotation) {
- switch(cap_kms->x11_rot) {
- case X11_ROT_90:
- texture_rotation = M_PI*0.5f;
- break;
- case X11_ROT_180:
- texture_rotation = M_PI;
- break;
- case X11_ROT_270:
- texture_rotation = M_PI*1.5f;
- break;
- default:
- texture_rotation = 0.0f;
- break;
- }
- }
-
- gsr_cursor_tick(&cap_kms->cursor);
-
- vec2i capture_pos = cap_kms->capture_pos;
- vec2i capture_size = cap_kms->capture_size;
- vec2i cursor_capture_pos = (vec2i){cap_kms->cursor.position.x - cap_kms->cursor.hotspot.x - capture_pos.x, cap_kms->cursor.position.y - cap_kms->cursor.hotspot.y - capture_pos.y};
- if(!capture_is_combined_plane) {
- capture_pos = (vec2i){0, 0};
- //cursor_capture_pos = (vec2i){cap_kms->cursor.position.x - cap_kms->cursor.hotspot.x, cap_kms->cursor.position.y - cap_kms->cursor.hotspot.y};
- }
-
- gsr_color_conversion_draw(&cap_kms->color_conversion, cap_kms->input_texture,
- (vec2i){0, 0}, capture_size,
- capture_pos, capture_size,
- texture_rotation);
-
- gsr_color_conversion_draw(&cap_kms->color_conversion, cap_kms->cursor.texture_id,
- cursor_capture_pos, (vec2i){cap_kms->cursor.size.x, cap_kms->cursor.size.y},
- (vec2i){0, 0}, (vec2i){cap_kms->cursor.size.x, cap_kms->cursor.size.y},
- 0.0f);
-
- cap_kms->egl.eglSwapBuffers(cap_kms->egl.egl_display, cap_kms->egl.egl_surface);
-
- for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) {
- if(cap_kms->kms_response.fds[i].fd > 0)
- close(cap_kms->kms_response.fds[i].fd);
- cap_kms->kms_response.fds[i].fd = 0;
- }
- cap_kms->kms_response.num_fds = 0;
-
- return 0;
+ gsr_capture_kms_cleanup_kms_fds(&cap_kms->kms);
}
static void gsr_capture_kms_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context) {
+ (void)video_codec_context;
gsr_capture_kms_vaapi *cap_kms = cap->priv;
- gsr_cursor_deinit(&cap_kms->cursor);
- gsr_color_conversion_deinit(&cap_kms->color_conversion);
-
for(uint32_t i = 0; i < cap_kms->prime.num_objects; ++i) {
if(cap_kms->prime.objects[i].fd > 0) {
close(cap_kms->prime.objects[i].fd);
@@ -585,34 +78,7 @@ static void gsr_capture_kms_vaapi_stop(gsr_capture *cap, AVCodecContext *video_c
}
}
- if(cap_kms->input_texture) {
- cap_kms->egl.glDeleteTextures(1, &cap_kms->input_texture);
- cap_kms->input_texture = 0;
- }
-
- cap_kms->egl.glDeleteTextures(2, cap_kms->target_textures);
- cap_kms->target_textures[0] = 0;
- cap_kms->target_textures[1] = 0;
-
- for(int i = 0; i < cap_kms->kms_response.num_fds; ++i) {
- if(cap_kms->kms_response.fds[i].fd > 0)
- close(cap_kms->kms_response.fds[i].fd);
- cap_kms->kms_response.fds[i].fd = 0;
- }
- cap_kms->kms_response.num_fds = 0;
-
- if(video_codec_context->hw_device_ctx)
- av_buffer_unref(&video_codec_context->hw_device_ctx);
- if(video_codec_context->hw_frames_ctx)
- av_buffer_unref(&video_codec_context->hw_frames_ctx);
-
- gsr_egl_unload(&cap_kms->egl);
- gsr_kms_client_deinit(&cap_kms->kms_client);
- if(cap_kms->dpy) {
- // TODO: This causes a crash, why? maybe some other library dlclose xlib and that also happened to unload this???
- //XCloseDisplay(cap_kms->dpy);
- cap_kms->dpy = NULL;
- }
+ gsr_capture_kms_stop(&cap_kms->kms);
}
static void gsr_capture_kms_vaapi_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
@@ -644,14 +110,6 @@ gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *pa
return NULL;
}
- Display *display = XOpenDisplay(NULL);
- if(!display) {
- fprintf(stderr, "gsr error: gsr_capture_kms_vaapi_create failed: XOpenDisplay failed\n");
- free(cap);
- free(cap_kms);
- return NULL;
- }
-
const char *display_to_capture = strdup(params->display_to_capture);
if(!display_to_capture) {
/* TODO XCloseDisplay */
@@ -660,15 +118,15 @@ gsr_capture* gsr_capture_kms_vaapi_create(const gsr_capture_kms_vaapi_params *pa
return NULL;
}
- cap_kms->dpy = display;
cap_kms->params = *params;
cap_kms->params.display_to_capture = display_to_capture;
*cap = (gsr_capture) {
.start = gsr_capture_kms_vaapi_start,
- .tick = gsr_capture_kms_vaapi_tick,
+ .tick = NULL,
.should_stop = gsr_capture_kms_vaapi_should_stop,
.capture = gsr_capture_kms_vaapi_capture,
+ .capture_end = gsr_capture_kms_vaapi_capture_end,
.destroy = gsr_capture_kms_vaapi_destroy,
.priv = cap_kms
};
diff --git a/src/capture/nvfbc.c b/src/capture/nvfbc.c
index 32b83fc..9eabb18 100644
--- a/src/capture/nvfbc.c
+++ b/src/capture/nvfbc.c
@@ -1,10 +1,13 @@
#include "../../include/capture/nvfbc.h"
#include "../../external/NvFBC.h"
#include "../../include/cuda.h"
+#include "../../include/egl.h"
+#include "../../include/utils.h"
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
+#include <math.h>
#include <X11/Xlib.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_cuda.h>
@@ -13,6 +16,7 @@
#include <libavcodec/avcodec.h>
typedef struct {
+ gsr_capture_base base;
gsr_capture_nvfbc_params params;
void *library;
@@ -23,7 +27,20 @@ typedef struct {
bool capture_session_created;
gsr_cuda cuda;
- bool frame_initialized;
+ CUgraphicsResource cuda_graphics_resources[2];
+ CUarray mapped_arrays[2];
+ CUstream cuda_stream; // TODO: asdasdsa
+ NVFBC_TOGL_SETUP_PARAMS setup_params;
+
+ bool direct_capture;
+ bool supports_direct_cursor;
+ bool capture_region;
+ uint32_t x, y, width, height;
+ NVFBC_TRACKING_TYPE tracking_type;
+ uint32_t output_id;
+ uint32_t tracking_width, tracking_height;
+ bool nvfbc_needs_recreate;
+ double nvfbc_dead_start;
} gsr_capture_nvfbc;
#if defined(_WIN64) || defined(__LP64__)
@@ -129,114 +146,63 @@ static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) {
return true;
}
-#if LIBAVUTIL_VERSION_MAJOR < 57
-static AVBufferRef* dummy_hw_frame_init(int size) {
- return av_buffer_alloc(size);
-}
-#else
-static AVBufferRef* dummy_hw_frame_init(size_t size) {
- return av_buffer_alloc(size);
-}
-#endif
+/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */
+static void set_vertical_sync_enabled(gsr_egl *egl, int enabled) {
+ int result = 0;
-static bool ffmpeg_create_cuda_contexts(gsr_capture_nvfbc *cap_nvfbc, AVCodecContext *video_codec_context) {
- AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
- if(!device_ctx) {
- fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hardware device context\n");
- return false;
- }
-
- AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data;
- AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx;
- cuda_device_context->cuda_ctx = cap_nvfbc->cuda.cu_ctx;
- if(av_hwdevice_ctx_init(device_ctx) < 0) {
- fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hardware device context\n");
- av_buffer_unref(&device_ctx);
- return false;
+ if(egl->glXSwapIntervalEXT) {
+ egl->glXSwapIntervalEXT(egl->x11.dpy, egl->x11.window, enabled ? 1 : 0);
+ } else if(egl->glXSwapIntervalMESA) {
+ result = egl->glXSwapIntervalMESA(enabled ? 1 : 0);
+ } else if(egl->glXSwapIntervalSGI) {
+ result = egl->glXSwapIntervalSGI(enabled ? 1 : 0);
+ } else {
+ static int warned = 0;
+ if (!warned) {
+ warned = 1;
+ fprintf(stderr, "gsr warning: setting vertical sync not supported\n");
+ }
}
- AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
- if(!frame_context) {
- fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to create hwframe context\n");
- av_buffer_unref(&device_ctx);
- return false;
- }
+ if(result != 0)
+ fprintf(stderr, "gsr warning: setting vertical sync failed\n");
+}
- AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)frame_context->data;
- hw_frame_context->width = video_codec_context->width;
- hw_frame_context->height = video_codec_context->height;
- hw_frame_context->sw_format = AV_PIX_FMT_BGR0;
- hw_frame_context->format = video_codec_context->pix_fmt;
- hw_frame_context->device_ref = device_ctx;
- hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
-
- hw_frame_context->pool = av_buffer_pool_init(1, dummy_hw_frame_init);
- hw_frame_context->initial_pool_size = 1;
-
- if (av_hwframe_ctx_init(frame_context) < 0) {
- fprintf(stderr, "gsr error: cuda_create_codec_context failed: failed to initialize hardware frame context "
- "(note: ffmpeg version needs to be > 4.0)\n");
- av_buffer_unref(&device_ctx);
- //av_buffer_unref(&frame_context);
- return false;
+static void gsr_capture_nvfbc_destroy_session(gsr_capture_nvfbc *cap_nvfbc) {
+ if(cap_nvfbc->fbc_handle_created && cap_nvfbc->capture_session_created) {
+ NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
+ memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
+ destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
+ cap_nvfbc->nv_fbc_function_list.nvFBCDestroyCaptureSession(cap_nvfbc->nv_fbc_handle, &destroy_capture_params);
+ cap_nvfbc->capture_session_created = false;
}
-
- video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx);
- video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context);
- return true;
}
-static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
- gsr_capture_nvfbc *cap_nvfbc = cap->priv;
- if(!gsr_cuda_load(&cap_nvfbc->cuda, cap_nvfbc->params.dpy, cap_nvfbc->params.overclock))
- return -1;
-
- if(!gsr_capture_nvfbc_load_library(cap)) {
- gsr_cuda_unload(&cap_nvfbc->cuda);
- return -1;
+static void gsr_capture_nvfbc_destroy_handle(gsr_capture_nvfbc *cap_nvfbc) {
+ if(cap_nvfbc->fbc_handle_created) {
+ NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
+ memset(&destroy_params, 0, sizeof(destroy_params));
+ destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
+ cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
+ cap_nvfbc->fbc_handle_created = false;
+ cap_nvfbc->nv_fbc_handle = 0;
}
+}
- const uint32_t x = max_int(cap_nvfbc->params.pos.x, 0);
- const uint32_t y = max_int(cap_nvfbc->params.pos.y, 0);
- const uint32_t width = max_int(cap_nvfbc->params.size.x, 0);
- const uint32_t height = max_int(cap_nvfbc->params.size.y, 0);
-
- const bool capture_region = (x > 0 || y > 0 || width > 0 || height > 0);
-
- bool supports_direct_cursor = false;
- bool direct_capture = cap_nvfbc->params.direct_capture;
- int driver_major_version = 0;
- int driver_minor_version = 0;
- if(direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) {
- fprintf(stderr, "Info: detected nvidia version: %d.%d\n", driver_major_version, driver_minor_version);
-
- // TODO:
- if(version_at_least(driver_major_version, driver_minor_version, 515, 57) && version_less_than(driver_major_version, driver_minor_version, 520, 56)) {
- direct_capture = false;
- fprintf(stderr, "Warning: \"screen-direct\" has temporary been disabled as it causes stuttering with driver versions >= 515.57 and < 520.56. Please update your driver if possible. Capturing \"screen\" instead.\n");
- }
-
- // TODO:
- // Cursor capture disabled because moving the cursor doesn't update capture rate to monitor hz and instead captures at 10-30 hz
- /*
- if(direct_capture) {
- if(version_at_least(driver_major_version, driver_minor_version, 515, 57))
- supports_direct_cursor = true;
- else
- fprintf(stderr, "Info: capturing \"screen-direct\" but driver version appears to be less than 515.57. Disabling capture of cursor. Please update your driver if you want to capture your cursor or record \"screen\" instead.\n");
- }
- */
- }
+static void gsr_capture_nvfbc_destroy_session_and_handle(gsr_capture_nvfbc *cap_nvfbc) {
+ gsr_capture_nvfbc_destroy_session(cap_nvfbc);
+ gsr_capture_nvfbc_destroy_handle(cap_nvfbc);
+}
+static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *cap_nvfbc) {
NVFBCSTATUS status;
- NVFBC_TRACKING_TYPE tracking_type;
- uint32_t output_id = 0;
- cap_nvfbc->fbc_handle_created = false;
- cap_nvfbc->capture_session_created = false;
NVFBC_CREATE_HANDLE_PARAMS create_params;
memset(&create_params, 0, sizeof(create_params));
create_params.dwVersion = NVFBC_CREATE_HANDLE_PARAMS_VER;
+ create_params.bExternallyManagedContext = NVFBC_TRUE;
+ create_params.glxCtx = cap_nvfbc->params.egl->glx_context;
+ create_params.glxFBConfig = cap_nvfbc->params.egl->glx_fb_config;
status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
@@ -268,10 +234,10 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec
goto error_cleanup;
}
- uint32_t tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.dpy));
- uint32_t tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.dpy));
- tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT;
- if(tracking_type == NVFBC_TRACKING_OUTPUT) {
+ cap_nvfbc->tracking_width = XWidthOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.egl->x11.dpy));
+ cap_nvfbc->tracking_height = XHeightOfScreen(DefaultScreenOfDisplay(cap_nvfbc->params.egl->x11.dpy));
+ cap_nvfbc->tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT;
+ if(cap_nvfbc->tracking_type == NVFBC_TRACKING_OUTPUT) {
if(!status_params.bXRandRAvailable) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: the xrandr extension is not available\n");
goto error_cleanup;
@@ -282,169 +248,237 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec
goto error_cleanup;
}
- output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture, &tracking_width, &tracking_height);
- if(output_id == 0) {
+ cap_nvfbc->output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture, &cap_nvfbc->tracking_width, &cap_nvfbc->tracking_height);
+ if(cap_nvfbc->output_id == 0) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", cap_nvfbc->params.display_to_capture);
goto error_cleanup;
}
}
+ return 0;
+
+ error_cleanup:
+ gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc);
+ return -1;
+}
+
+static int gsr_capture_nvfbc_setup_session(gsr_capture_nvfbc *cap_nvfbc) {
NVFBC_CREATE_CAPTURE_SESSION_PARAMS create_capture_params;
memset(&create_capture_params, 0, sizeof(create_capture_params));
create_capture_params.dwVersion = NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER;
- create_capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
- create_capture_params.bWithCursor = (!direct_capture || supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE;
- if(capture_region)
- create_capture_params.captureBox = (NVFBC_BOX){ x, y, width, height };
- create_capture_params.eTrackingType = tracking_type;
- create_capture_params.dwSamplingRateMs = 1000u / ((uint32_t)cap_nvfbc->params.fps + 1);
- create_capture_params.bAllowDirectCapture = direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
- create_capture_params.bPushModel = direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
- //create_capture_params.bDisableAutoModesetRecovery = true; // TODO:
- if(tracking_type == NVFBC_TRACKING_OUTPUT)
- create_capture_params.dwOutputId = output_id;
-
- status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateCaptureSession(cap_nvfbc->nv_fbc_handle, &create_capture_params);
+ create_capture_params.eCaptureType = NVFBC_CAPTURE_TO_GL;
+ create_capture_params.bWithCursor = (!cap_nvfbc->direct_capture || cap_nvfbc->supports_direct_cursor) ? NVFBC_TRUE : NVFBC_FALSE;
+ if(!cap_nvfbc->params.record_cursor)
+ create_capture_params.bWithCursor = false;
+ if(cap_nvfbc->capture_region)
+ create_capture_params.captureBox = (NVFBC_BOX){ cap_nvfbc->x, cap_nvfbc->y, cap_nvfbc->width, cap_nvfbc->height };
+ create_capture_params.eTrackingType = cap_nvfbc->tracking_type;
+ create_capture_params.dwSamplingRateMs = (uint32_t)ceilf(1000.0f / (float)cap_nvfbc->params.fps);
+ create_capture_params.bAllowDirectCapture = cap_nvfbc->direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
+ create_capture_params.bPushModel = cap_nvfbc->direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
+ create_capture_params.bDisableAutoModesetRecovery = true;
+ if(cap_nvfbc->tracking_type == NVFBC_TRACKING_OUTPUT)
+ create_capture_params.dwOutputId = cap_nvfbc->output_id;
+
+ NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateCaptureSession(cap_nvfbc->nv_fbc_handle, &create_capture_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
- goto error_cleanup;
+ return -1;
}
cap_nvfbc->capture_session_created = true;
- NVFBC_TOCUDA_SETUP_PARAMS setup_params;
- memset(&setup_params, 0, sizeof(setup_params));
- setup_params.dwVersion = NVFBC_TOCUDA_SETUP_PARAMS_VER;
- setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA;
+ memset(&cap_nvfbc->setup_params, 0, sizeof(cap_nvfbc->setup_params));
+ cap_nvfbc->setup_params.dwVersion = NVFBC_TOGL_SETUP_PARAMS_VER;
+ cap_nvfbc->setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA;
- status = cap_nvfbc->nv_fbc_function_list.nvFBCToCudaSetUp(cap_nvfbc->nv_fbc_handle, &setup_params);
+ status = cap_nvfbc->nv_fbc_function_list.nvFBCToGLSetUp(cap_nvfbc->nv_fbc_handle, &cap_nvfbc->setup_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
- goto error_cleanup;
+ gsr_capture_nvfbc_destroy_session(cap_nvfbc);
+ return -1;
}
- if(capture_region) {
- video_codec_context->width = width & ~1;
- video_codec_context->height = height & ~1;
- } else {
- video_codec_context->width = tracking_width & ~1;
- video_codec_context->height = tracking_height & ~1;
- }
+ return 0;
+}
- if(!ffmpeg_create_cuda_contexts(cap_nvfbc, video_codec_context))
- goto error_cleanup;
+static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
+ gsr_capture_nvfbc *cap_nvfbc = cap->priv;
- return 0;
+ cap_nvfbc->base.video_codec_context = video_codec_context;
+ cap_nvfbc->base.egl = cap_nvfbc->params.egl;
- error_cleanup:
- if(cap_nvfbc->fbc_handle_created) {
- if(cap_nvfbc->capture_session_created) {
- NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
- memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
- destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
- cap_nvfbc->nv_fbc_function_list.nvFBCDestroyCaptureSession(cap_nvfbc->nv_fbc_handle, &destroy_capture_params);
- cap_nvfbc->capture_session_created = false;
- }
+ if(!gsr_cuda_load(&cap_nvfbc->cuda, cap_nvfbc->params.egl->x11.dpy, cap_nvfbc->params.overclock))
+ return -1;
- NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
- memset(&destroy_params, 0, sizeof(destroy_params));
- destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
- cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
- cap_nvfbc->fbc_handle_created = false;
+ if(!gsr_capture_nvfbc_load_library(cap)) {
+ gsr_cuda_unload(&cap_nvfbc->cuda);
+ return -1;
}
- if(video_codec_context->hw_device_ctx)
- av_buffer_unref(&video_codec_context->hw_device_ctx);
- if(video_codec_context->hw_frames_ctx)
- av_buffer_unref(&video_codec_context->hw_frames_ctx);
+ cap_nvfbc->x = max_int(cap_nvfbc->params.pos.x, 0);
+ cap_nvfbc->y = max_int(cap_nvfbc->params.pos.y, 0);
+ cap_nvfbc->width = max_int(cap_nvfbc->params.size.x, 0);
+ cap_nvfbc->height = max_int(cap_nvfbc->params.size.y, 0);
- gsr_cuda_unload(&cap_nvfbc->cuda);
- return -1;
-}
+ cap_nvfbc->capture_region = (cap_nvfbc->x > 0 || cap_nvfbc->y > 0 || cap_nvfbc->width > 0 || cap_nvfbc->height > 0);
-static void gsr_capture_nvfbc_destroy_session(gsr_capture *cap) {
- gsr_capture_nvfbc *cap_nvfbc = cap->priv;
+ cap_nvfbc->supports_direct_cursor = false;
+ bool direct_capture = cap_nvfbc->params.direct_capture;
+ int driver_major_version = 0;
+ int driver_minor_version = 0;
+ if(direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) {
+ fprintf(stderr, "Info: detected nvidia version: %d.%d\n", driver_major_version, driver_minor_version);
- if(cap_nvfbc->fbc_handle_created) {
- if(cap_nvfbc->capture_session_created) {
- NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
- memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
- destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
- cap_nvfbc->nv_fbc_function_list.nvFBCDestroyCaptureSession(cap_nvfbc->nv_fbc_handle, &destroy_capture_params);
- cap_nvfbc->capture_session_created = false;
+ // TODO:
+ if(version_at_least(driver_major_version, driver_minor_version, 515, 57) && version_less_than(driver_major_version, driver_minor_version, 520, 56)) {
+ direct_capture = false;
+ fprintf(stderr, "Warning: \"screen-direct\" has temporary been disabled as it causes stuttering with driver versions >= 515.57 and < 520.56. Please update your driver if possible. Capturing \"screen\" instead.\n");
}
- NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
- memset(&destroy_params, 0, sizeof(destroy_params));
- destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
- cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
- cap_nvfbc->fbc_handle_created = false;
+ // TODO:
+ // Cursor capture disabled because moving the cursor doesn't update capture rate to monitor hz and instead captures at 10-30 hz
+ /*
+ if(direct_capture) {
+ if(version_at_least(driver_major_version, driver_minor_version, 515, 57))
+ supports_direct_cursor = true;
+ else
+ fprintf(stderr, "Info: capturing \"screen-direct\" but driver version appears to be less than 515.57. Disabling capture of cursor. Please update your driver if you want to capture your cursor or record \"screen\" instead.\n");
+ }
+ */
}
- cap_nvfbc->nv_fbc_handle = 0;
-}
+ if(gsr_capture_nvfbc_setup_handle(cap_nvfbc) != 0) {
+ goto error_cleanup;
+ }
-static void gsr_capture_nvfbc_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
- gsr_capture_nvfbc *cap_nvfbc = cap->priv;
- if(!cap_nvfbc->frame_initialized && video_codec_context->hw_frames_ctx) {
- cap_nvfbc->frame_initialized = true;
- (*frame)->hw_frames_ctx = video_codec_context->hw_frames_ctx;
- (*frame)->buf[0] = av_buffer_pool_get(((AVHWFramesContext*)video_codec_context->hw_frames_ctx->data)->pool);
- (*frame)->extended_data = (*frame)->data;
- (*frame)->color_range = video_codec_context->color_range;
- (*frame)->color_primaries = video_codec_context->color_primaries;
- (*frame)->color_trc = video_codec_context->color_trc;
- (*frame)->colorspace = video_codec_context->colorspace;
- (*frame)->chroma_location = video_codec_context->chroma_sample_location;
+ if(gsr_capture_nvfbc_setup_session(cap_nvfbc) != 0) {
+ goto error_cleanup;
}
+
+ if(cap_nvfbc->capture_region) {
+ video_codec_context->width = cap_nvfbc->width & ~1;
+ video_codec_context->height = cap_nvfbc->height & ~1;
+ } else {
+ video_codec_context->width = cap_nvfbc->tracking_width & ~1;
+ video_codec_context->height = cap_nvfbc->tracking_height & ~1;
+ }
+
+ frame->width = video_codec_context->width;
+ frame->height = video_codec_context->height;
+
+ if(!cuda_create_codec_context(cap_nvfbc->cuda.cu_ctx, video_codec_context, video_codec_context->width, video_codec_context->height, false, &cap_nvfbc->cuda_stream))
+ goto error_cleanup;
+
+ gsr_cuda_context cuda_context = {
+ .cuda = &cap_nvfbc->cuda,
+ .cuda_graphics_resources = cap_nvfbc->cuda_graphics_resources,
+ .mapped_arrays = cap_nvfbc->mapped_arrays
+ };
+
+ // TODO: Remove this, it creates shit we dont need
+ if(!gsr_capture_base_setup_cuda_textures(&cap_nvfbc->base, frame, &cuda_context, cap_nvfbc->params.color_range, GSR_SOURCE_COLOR_BGR, cap_nvfbc->params.hdr)) {
+ goto error_cleanup;
+ }
+ /* Disable vsync */
+ set_vertical_sync_enabled(cap_nvfbc->params.egl, 0);
+
+ return 0;
+
+ error_cleanup:
+ gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc);
+ gsr_capture_base_stop(&cap_nvfbc->base);
+ gsr_cuda_unload(&cap_nvfbc->cuda);
+ return -1;
}
static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
- CUdeviceptr cu_device_ptr = 0;
+ const double nvfbc_recreate_retry_time_seconds = 1.0;
+ if(cap_nvfbc->nvfbc_needs_recreate) {
+ const double now = clock_get_monotonic_seconds();
+ if(now - cap_nvfbc->nvfbc_dead_start >= nvfbc_recreate_retry_time_seconds) {
+ cap_nvfbc->nvfbc_dead_start = now;
+ gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc);
+
+ if(gsr_capture_nvfbc_setup_handle(cap_nvfbc) != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed to recreate nvfbc handle, trying again in %f second(s)\n", nvfbc_recreate_retry_time_seconds);
+ return -1;
+ }
+
+ if(gsr_capture_nvfbc_setup_session(cap_nvfbc) != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed to recreate nvfbc session, trying again in %f second(s)\n", nvfbc_recreate_retry_time_seconds);
+ return -1;
+ }
+
+ cap_nvfbc->nvfbc_needs_recreate = false;
+ } else {
+ return 0;
+ }
+ }
NVFBC_FRAME_GRAB_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));
- NVFBC_TOCUDA_GRAB_FRAME_PARAMS grab_params;
+ NVFBC_TOGL_GRAB_FRAME_PARAMS grab_params;
memset(&grab_params, 0, sizeof(grab_params));
- grab_params.dwVersion = NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER;
- grab_params.dwFlags = NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT;/* | NVFBC_TOCUDA_GRAB_FLAGS_FORCE_REFRESH;*/
+ grab_params.dwVersion = NVFBC_TOGL_GRAB_FRAME_PARAMS_VER;
+ grab_params.dwFlags = NVFBC_TOGL_GRAB_FLAGS_NOWAIT | NVFBC_TOGL_GRAB_FLAGS_FORCE_REFRESH; // TODO: Remove NVFBC_TOGL_GRAB_FLAGS_FORCE_REFRESH
grab_params.pFrameGrabInfo = &frame_info;
- grab_params.pCUDADeviceBuffer = &cu_device_ptr;
grab_params.dwTimeoutMs = 0;
- NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCToCudaGrabFrame(cap_nvfbc->nv_fbc_handle, &grab_params);
+ NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCToGLGrabFrame(cap_nvfbc->nv_fbc_handle, &grab_params);
if(status != NVFBC_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
- return -1;
+ fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed: %s (%d), recreating session after %f second(s)\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle), status, nvfbc_recreate_retry_time_seconds);
+ cap_nvfbc->nvfbc_needs_recreate = true;
+ cap_nvfbc->nvfbc_dead_start = clock_get_monotonic_seconds();
+ return 0;
}
- /*
- *byte_size = frame_info.dwByteSize;
+ //cap_nvfbc->params.egl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ cap_nvfbc->params.egl->glClear(0);
+
+ gsr_color_conversion_draw(&cap_nvfbc->base.color_conversion, cap_nvfbc->setup_params.dwTextures[grab_params.dwTextureIndex],
+ (vec2i){0, 0}, (vec2i){frame->width, frame->height},
+ (vec2i){0, 0}, (vec2i){frame->width, frame->height},
+ 0.0f, false);
+
+ cap_nvfbc->params.egl->glXSwapBuffers(cap_nvfbc->params.egl->x11.dpy, cap_nvfbc->params.egl->x11.window);
+
+ // TODO: HDR is broken
+ const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
+ for(int i = 0; i < 2; ++i) {
+ CUDA_MEMCPY2D memcpy_struct;
+ memcpy_struct.srcXInBytes = 0;
+ memcpy_struct.srcY = 0;
+ memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY;
+
+ memcpy_struct.dstXInBytes = 0;
+ memcpy_struct.dstY = 0;
+ memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE;
+
+ memcpy_struct.srcArray = cap_nvfbc->mapped_arrays[i];
+ memcpy_struct.srcPitch = frame->width / div[i];
+ memcpy_struct.dstDevice = (CUdeviceptr)frame->data[i];
+ memcpy_struct.dstPitch = frame->linesize[i];
+ memcpy_struct.WidthInBytes = frame->width * (cap_nvfbc->params.hdr ? 2 : 1);
+ memcpy_struct.Height = frame->height / div[i];
+ // TODO: Remove this copy if possible
+ cap_nvfbc->cuda.cuMemcpy2DAsync_v2(&memcpy_struct, cap_nvfbc->cuda_stream);
+ }
- TODO: Check bIsNewFrame
- TODO: Check dwWidth and dwHeight and update size in video output in ffmpeg. This can happen when xrandr is used to change monitor resolution
- */
+ // TODO: needed?
+ cap_nvfbc->cuda.cuStreamSynchronize(cap_nvfbc->cuda_stream);
- frame->data[0] = (uint8_t*)cu_device_ptr;
- //frame->data[1] = (uint8_t*)cu_device_ptr;
- //frame->data[2] = (uint8_t*)cu_device_ptr;
- frame->linesize[0] = frame->width * 4;
- // TODO: Use these when outputting yuv444 by changing nvfbc color to YUV444P and sw_format to YUV444P
- //frame->linesize[1] = frame->width * 1;
- //frame->linesize[2] = frame->width * 1;
return 0;
}
static void gsr_capture_nvfbc_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
+ (void)video_codec_context;
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
- gsr_capture_nvfbc_destroy_session(cap);
- if(video_codec_context->hw_device_ctx)
- av_buffer_unref(&video_codec_context->hw_device_ctx);
- if(video_codec_context->hw_frames_ctx)
- av_buffer_unref(&video_codec_context->hw_frames_ctx);
+ gsr_capture_nvfbc_destroy_session_and_handle(cap_nvfbc);
if(cap_nvfbc) {
+ gsr_capture_base_stop(&cap_nvfbc->base);
gsr_cuda_unload(&cap_nvfbc->cuda);
dlclose(cap_nvfbc->library);
free((void*)cap_nvfbc->params.display_to_capture);
@@ -489,9 +523,10 @@ gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
*cap = (gsr_capture) {
.start = gsr_capture_nvfbc_start,
- .tick = gsr_capture_nvfbc_tick,
+ .tick = NULL,
.should_stop = NULL,
.capture = gsr_capture_nvfbc_capture,
+ .capture_end = NULL,
.destroy = gsr_capture_nvfbc_destroy,
.priv = cap_nvfbc
};
diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c
new file mode 100644
index 0000000..29b42d5
--- /dev/null
+++ b/src/capture/xcomposite.c
@@ -0,0 +1,299 @@
+#include "../../include/capture/xcomposite.h"
+#include "../../include/window_texture.h"
+#include "../../include/utils.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <X11/Xlib.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/frame.h>
+#include <libavcodec/avcodec.h>
+#include <va/va.h>
+#include <va/va_drmcommon.h>
+
+static int max_int(int a, int b) {
+ return a > b ? a : b;
+}
+
+static int min_int(int a, int b) {
+ return a < b ? a : b;
+}
+
+void gsr_capture_xcomposite_init(gsr_capture_xcomposite *self, const gsr_capture_xcomposite_params *params) {
+ memset(self, 0, sizeof(*self));
+ self->params = *params;
+}
+
+static Window get_focused_window(Display *display, Atom net_active_window_atom) {
+ Atom type;
+ int format = 0;
+ unsigned long num_items = 0;
+ unsigned long bytes_after = 0;
+ unsigned char *properties = NULL;
+ if(XGetWindowProperty(display, DefaultRootWindow(display), net_active_window_atom, 0, 1024, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &properties) == Success && properties) {
+ Window focused_window = *(unsigned long*)properties;
+ XFree(properties);
+ return focused_window;
+ }
+ return None;
+}
+
+int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context, AVFrame *frame) {
+ self->base.video_codec_context = video_codec_context;
+ self->base.egl = self->params.egl;
+
+ if(self->params.follow_focused) {
+ self->net_active_window_atom = XInternAtom(self->params.egl->x11.dpy, "_NET_ACTIVE_WINDOW", False);
+ if(!self->net_active_window_atom) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_start failed: failed to get _NET_ACTIVE_WINDOW atom\n");
+ return -1;
+ }
+ self->window = get_focused_window(self->params.egl->x11.dpy, self->net_active_window_atom);
+ } else {
+ self->window = self->params.window;
+ }
+
+ /* TODO: Do these in tick, and allow error if follow_focused */
+
+ XWindowAttributes attr;
+ if(!XGetWindowAttributes(self->params.egl->x11.dpy, self->window, &attr) && !self->params.follow_focused) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_start failed: invalid window id: %lu\n", self->window);
+ return -1;
+ }
+
+ self->window_size.x = max_int(attr.width, 0);
+ self->window_size.y = max_int(attr.height, 0);
+
+ if(self->params.follow_focused)
+ XSelectInput(self->params.egl->x11.dpy, DefaultRootWindow(self->params.egl->x11.dpy), PropertyChangeMask);
+
+ // TODO: Get select and add these on top of it and then restore at the end. Also do the same in other xcomposite
+ XSelectInput(self->params.egl->x11.dpy, self->window, StructureNotifyMask | ExposureMask);
+
+ if(!self->params.egl->eglExportDMABUFImageQueryMESA) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: could not find eglExportDMABUFImageQueryMESA\n");
+ return -1;
+ }
+
+ if(!self->params.egl->eglExportDMABUFImageMESA) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: could not find eglExportDMABUFImageMESA\n");
+ return -1;
+ }
+
+ /* Disable vsync */
+ self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0);
+ if(window_texture_init(&self->window_texture, self->params.egl->x11.dpy, self->window, self->params.egl) != 0 && !self->params.follow_focused) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: failed to get window texture for window %ld\n", self->window);
+ return -1;
+ }
+
+ if(gsr_cursor_init(&self->cursor, self->params.egl, self->params.egl->x11.dpy) != 0) {
+ gsr_capture_xcomposite_stop(self);
+ return -1;
+ }
+
+ self->texture_size.x = 0;
+ self->texture_size.y = 0;
+
+ self->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&self->window_texture));
+ self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->texture_size.x);
+ self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y);
+ self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ self->texture_size.x = max_int(2, even_number_ceil(self->texture_size.x));
+ self->texture_size.y = max_int(2, even_number_ceil(self->texture_size.y));
+
+ video_codec_context->width = self->texture_size.x;
+ video_codec_context->height = self->texture_size.y;
+
+ if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
+ video_codec_context->width = max_int(2, even_number_ceil(self->params.region_size.x));
+ video_codec_context->height = max_int(2, even_number_ceil(self->params.region_size.y));
+ }
+
+ frame->width = video_codec_context->width;
+ frame->height = video_codec_context->height;
+
+ self->window_resize_timer = clock_get_monotonic_seconds();
+ self->clear_next_frame = true;
+ return 0;
+}
+
+void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) {
+ window_texture_deinit(&self->window_texture);
+ gsr_cursor_deinit(&self->cursor);
+ gsr_capture_base_stop(&self->base);
+}
+
+void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context) {
+ //self->params.egl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ self->params.egl->glClear(0);
+
+ bool init_new_window = false;
+ while(XPending(self->params.egl->x11.dpy)) {
+ XNextEvent(self->params.egl->x11.dpy, &self->xev);
+
+ switch(self->xev.type) {
+ case DestroyNotify: {
+ /* Window died (when not following focused window), so we stop recording */
+ if(!self->params.follow_focused && self->xev.xdestroywindow.window == self->window) {
+ self->should_stop = true;
+ self->stop_is_error = false;
+ }
+ break;
+ }
+ case Expose: {
+ /* Requires window texture recreate */
+ if(self->xev.xexpose.count == 0 && self->xev.xexpose.window == self->window) {
+ self->window_resize_timer = clock_get_monotonic_seconds();
+ self->window_resized = true;
+ }
+ break;
+ }
+ case ConfigureNotify: {
+ /* Window resized */
+ if(self->xev.xconfigure.window == self->window && (self->xev.xconfigure.width != self->window_size.x || self->xev.xconfigure.height != self->window_size.y)) {
+ self->window_size.x = max_int(self->xev.xconfigure.width, 0);
+ self->window_size.y = max_int(self->xev.xconfigure.height, 0);
+ self->window_resize_timer = clock_get_monotonic_seconds();
+ self->window_resized = true;
+ }
+ break;
+ }
+ case PropertyNotify: {
+ /* Focused window changed */
+ if(self->params.follow_focused && self->xev.xproperty.atom == self->net_active_window_atom) {
+ init_new_window = true;
+ }
+ break;
+ }
+ }
+
+ gsr_cursor_update(&self->cursor, &self->xev);
+ }
+
+ if(self->params.follow_focused && !self->follow_focused_initialized) {
+ init_new_window = true;
+ }
+
+ if(init_new_window) {
+ Window focused_window = get_focused_window(self->params.egl->x11.dpy, self->net_active_window_atom);
+ if(focused_window != self->window || !self->follow_focused_initialized) {
+ self->follow_focused_initialized = true;
+ XSelectInput(self->params.egl->x11.dpy, self->window, 0);
+ self->window = focused_window;
+ XSelectInput(self->params.egl->x11.dpy, self->window, StructureNotifyMask | ExposureMask);
+
+ XWindowAttributes attr;
+ attr.width = 0;
+ attr.height = 0;
+ if(!XGetWindowAttributes(self->params.egl->x11.dpy, self->window, &attr))
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick failed: invalid window id: %lu\n", self->window);
+
+ self->window_size.x = max_int(attr.width, 0);
+ self->window_size.y = max_int(attr.height, 0);
+ self->window_resized = true;
+
+ window_texture_deinit(&self->window_texture);
+ window_texture_init(&self->window_texture, self->params.egl->x11.dpy, self->window, self->params.egl); // TODO: Do not do the below window_texture_on_resize after this
+ }
+ }
+
+ const double window_resize_timeout = 1.0; // 1 second
+ if(self->window_resized && clock_get_monotonic_seconds() - self->window_resize_timer >= window_resize_timeout) {
+ self->window_resized = false;
+
+ if(window_texture_on_resize(&self->window_texture) != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_xcomposite_tick: window_texture_on_resize failed\n");
+ //self->should_stop = true;
+ //self->stop_is_error = true;
+ return;
+ }
+
+ self->texture_size.x = 0;
+ self->texture_size.y = 0;
+
+ self->params.egl->glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&self->window_texture));
+ self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->texture_size.x);
+ self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y);
+ self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ self->texture_size.x = min_int(video_codec_context->width, max_int(2, even_number_ceil(self->texture_size.x)));
+ self->texture_size.y = min_int(video_codec_context->height, max_int(2, even_number_ceil(self->texture_size.y)));
+
+ gsr_color_conversion_clear(&self->base.color_conversion);
+ }
+}
+
+bool gsr_capture_xcomposite_should_stop(gsr_capture_xcomposite *self, bool *err) {
+ if(self->should_stop) {
+ if(err)
+ *err = self->stop_is_error;
+ return true;
+ }
+
+ if(err)
+ *err = false;
+ return false;
+}
+
+int gsr_capture_xcomposite_capture(gsr_capture_xcomposite *self, AVFrame *frame) {
+ (void)frame;
+
+ const int target_x = max_int(0, frame->width / 2 - self->texture_size.x / 2);
+ const int target_y = max_int(0, frame->height / 2 - self->texture_size.y / 2);
+
+ // TODO: Can we do this a better way than to call it every capture?
+ if(self->params.record_cursor)
+ gsr_cursor_tick(&self->cursor, self->window);
+
+ const vec2i cursor_pos = {
+ target_x + self->cursor.position.x - self->cursor.hotspot.x,
+ target_y + self->cursor.position.y - self->cursor.hotspot.y
+ };
+
+ const bool cursor_completely_inside_window =
+ cursor_pos.x >= target_x &&
+ cursor_pos.x + self->cursor.size.x <= target_x + self->texture_size.x &&
+ cursor_pos.y >= target_y &&
+ cursor_pos.y + self->cursor.size.y <= target_y + self->texture_size.y;
+
+ const bool cursor_inside_window =
+ cursor_pos.x + self->cursor.size.x >= target_x &&
+ cursor_pos.x <= target_x + self->texture_size.x &&
+ cursor_pos.y + self->cursor.size.y >= target_y &&
+ cursor_pos.y <= target_y + self->texture_size.y;
+
+ if(self->clear_next_frame) {
+ self->clear_next_frame = false;
+ gsr_color_conversion_clear(&self->base.color_conversion);
+ }
+
+ /*
+ We dont draw the cursor if it's outside the window but if it's partially inside the window then the cursor area that is outside the window
+ will not get overdrawn the next frame causing a cursor trail to be visible since we dont clear the background.
+ To fix this we detect if the cursor is partially inside the window and clear the background only in that case.
+ */
+ if(!cursor_completely_inside_window && cursor_inside_window && self->params.record_cursor)
+ self->clear_next_frame = true;
+
+ gsr_color_conversion_draw(&self->base.color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
+ (vec2i){target_x, target_y}, self->texture_size,
+ (vec2i){0, 0}, self->texture_size,
+ 0.0f, false);
+
+ if(cursor_inside_window && self->params.record_cursor) {
+ gsr_color_conversion_draw(&self->base.color_conversion, self->cursor.texture_id,
+ cursor_pos, self->cursor.size,
+ (vec2i){0, 0}, self->cursor.size,
+ 0.0f, false);
+ }
+
+ self->params.egl->eglSwapBuffers(self->params.egl->egl_display, self->params.egl->egl_surface);
+ //self->params.egl->glFlush();
+ //self->params.egl->glFinish();
+
+ return 0;
+}
diff --git a/src/capture/xcomposite_cuda.c b/src/capture/xcomposite_cuda.c
index d6fc10b..6e13d2a 100644
--- a/src/capture/xcomposite_cuda.c
+++ b/src/capture/xcomposite_cuda.c
@@ -1,477 +1,114 @@
#include "../../include/capture/xcomposite_cuda.h"
-#include "../../include/egl.h"
#include "../../include/cuda.h"
-#include "../../include/window_texture.h"
-#include "../../include/utils.h"
-#include <libavutil/hwcontext.h>
-#include <libavutil/hwcontext_cuda.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <libavutil/frame.h>
#include <libavcodec/avcodec.h>
typedef struct {
- gsr_capture_xcomposite_cuda_params params;
- Display *dpy;
- XEvent xev;
+ gsr_capture_xcomposite xcomposite;
+ bool overclock;
- bool should_stop;
- bool stop_is_error;
- bool window_resized;
- bool created_hw_frame;
- bool follow_focused_initialized;
- double window_resize_timer;
-
- vec2i window_size;
-
- unsigned int target_texture_id;
- vec2i texture_size;
- Window window;
- WindowTexture window_texture;
- Atom net_active_window_atom;
-
- CUgraphicsResource cuda_graphics_resource;
- CUarray mapped_array;
-
- gsr_egl egl;
gsr_cuda cuda;
+ CUgraphicsResource cuda_graphics_resources[2];
+ CUarray mapped_arrays[2];
+ CUstream cuda_stream;
} gsr_capture_xcomposite_cuda;
-static int max_int(int a, int b) {
- return a > b ? a : b;
-}
-
-static int min_int(int a, int b) {
- return a < b ? a : b;
-}
-
-static Window get_focused_window(Display *display, Atom net_active_window_atom) {
- Atom type;
- int format = 0;
- unsigned long num_items = 0;
- unsigned long bytes_after = 0;
- unsigned char *properties = NULL;
- if(XGetWindowProperty(display, DefaultRootWindow(display), net_active_window_atom, 0, 1024, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &properties) == Success && properties) {
- Window focused_window = *(unsigned long*)properties;
- XFree(properties);
- return focused_window;
- }
- return None;
-}
-
static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context);
-static bool cuda_register_opengl_texture(gsr_capture_xcomposite_cuda *cap_xcomp) {
- CUresult res;
- CUcontext old_ctx;
- res = cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx);
- res = cap_xcomp->cuda.cuGraphicsGLRegisterImage(
- &cap_xcomp->cuda_graphics_resource, cap_xcomp->target_texture_id, GL_TEXTURE_2D,
- CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY);
- if (res != CUDA_SUCCESS) {
- const char *err_str = "unknown";
- cap_xcomp->cuda.cuGetErrorString(res, &err_str);
- fprintf(stderr,
- "Error: cuGraphicsGLRegisterImage failed, error %s, texture "
- "id: %u\n",
- err_str, cap_xcomp->target_texture_id);
- res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
- return false;
- }
-
- res = cap_xcomp->cuda.cuGraphicsResourceSetMapFlags(cap_xcomp->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
- res = cap_xcomp->cuda.cuGraphicsMapResources(1, &cap_xcomp->cuda_graphics_resource, 0);
-
- res = cap_xcomp->cuda.cuGraphicsSubResourceGetMappedArray(&cap_xcomp->mapped_array, cap_xcomp->cuda_graphics_resource, 0, 0);
- res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
- return true;
-}
-
-static bool cuda_create_codec_context(gsr_capture_xcomposite_cuda *cap_xcomp, AVCodecContext *video_codec_context) {
- CUcontext old_ctx;
- cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx);
-
- AVBufferRef *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
- if(!device_ctx) {
- fprintf(stderr, "Error: Failed to create hardware device context\n");
- cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
- return false;
- }
-
- AVHWDeviceContext *hw_device_context = (AVHWDeviceContext*)device_ctx->data;
- AVCUDADeviceContext *cuda_device_context = (AVCUDADeviceContext*)hw_device_context->hwctx;
- cuda_device_context->cuda_ctx = cap_xcomp->cuda.cu_ctx;
- if(av_hwdevice_ctx_init(device_ctx) < 0) {
- fprintf(stderr, "Error: Failed to create hardware device context\n");
- av_buffer_unref(&device_ctx);
- cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
- return false;
- }
-
- AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
- if(!frame_context) {
- fprintf(stderr, "Error: Failed to create hwframe context\n");
- av_buffer_unref(&device_ctx);
- cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
- return false;
- }
-
- AVHWFramesContext *hw_frame_context =
- (AVHWFramesContext *)frame_context->data;
- hw_frame_context->width = video_codec_context->width;
- hw_frame_context->height = video_codec_context->height;
- hw_frame_context->sw_format = AV_PIX_FMT_BGR0;
- hw_frame_context->format = video_codec_context->pix_fmt;
- hw_frame_context->device_ref = device_ctx;
- hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
-
- if (av_hwframe_ctx_init(frame_context) < 0) {
- fprintf(stderr, "Error: Failed to initialize hardware frame context "
- "(note: ffmpeg version needs to be > 4.0)\n");
- av_buffer_unref(&device_ctx);
- //av_buffer_unref(&frame_context);
- cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
- return false;
- }
-
- video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx);
- video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context);
- return true;
-}
-
-static unsigned int gl_create_texture(gsr_capture_xcomposite_cuda *cap_xcomp, int width, int height) {
- unsigned int texture_id = 0;
- cap_xcomp->egl.glGenTextures(1, &texture_id);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, texture_id);
- cap_xcomp->egl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
-
- cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
- return texture_id;
-}
-
-static int gsr_capture_xcomposite_cuda_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
+static int gsr_capture_xcomposite_cuda_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
- if(cap_xcomp->params.follow_focused) {
- cap_xcomp->net_active_window_atom = XInternAtom(cap_xcomp->dpy, "_NET_ACTIVE_WINDOW", False);
- if(!cap_xcomp->net_active_window_atom) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: failed to get _NET_ACTIVE_WINDOW atom\n");
- return -1;
- }
- cap_xcomp->window = get_focused_window(cap_xcomp->dpy, cap_xcomp->net_active_window_atom);
- } else {
- cap_xcomp->window = cap_xcomp->params.window;
- }
-
- /* TODO: Do these in tick, and allow error if follow_focused */
-
- XWindowAttributes attr;
- attr.width = 0;
- attr.height = 0;
- if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->window, &attr) && !cap_xcomp->params.follow_focused) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: invalid window id: %lu\n", cap_xcomp->window);
- return -1;
- }
-
- cap_xcomp->window_size.x = max_int(attr.width, 0);
- cap_xcomp->window_size.y = max_int(attr.height, 0);
-
- if(cap_xcomp->params.follow_focused)
- XSelectInput(cap_xcomp->dpy, DefaultRootWindow(cap_xcomp->dpy), PropertyChangeMask);
-
- XSelectInput(cap_xcomp->dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask);
-
- if(!gsr_egl_load(&cap_xcomp->egl, cap_xcomp->dpy)) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to load opengl\n");
- return -1;
- }
-
- cap_xcomp->egl.eglSwapInterval(cap_xcomp->egl.egl_display, 0);
- if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->window, &cap_xcomp->egl) != 0 && !cap_xcomp->params.follow_focused) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed get window texture for window %ld\n", cap_xcomp->window);
- gsr_egl_unload(&cap_xcomp->egl);
- return -1;
- }
-
- cap_xcomp->texture_size.x = 0;
- cap_xcomp->texture_size.y = 0;
-
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1);
- cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1);
-
- video_codec_context->width = cap_xcomp->texture_size.x;
- video_codec_context->height = cap_xcomp->texture_size.y;
-
- if(cap_xcomp->params.region_size.x > 0 && cap_xcomp->params.region_size.y > 0) {
- video_codec_context->width = max_int(2, cap_xcomp->params.region_size.x & ~1);
- video_codec_context->height = max_int(2, cap_xcomp->params.region_size.y & ~1);
- }
-
- cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, video_codec_context->width, video_codec_context->height);
- if(cap_xcomp->target_texture_id == 0) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to create opengl texture\n");
+ const int res = gsr_capture_xcomposite_start(&cap_xcomp->xcomposite, video_codec_context, frame);
+ if(res != 0) {
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
- return -1;
+ return res;
}
- if(!gsr_cuda_load(&cap_xcomp->cuda, cap_xcomp->dpy, cap_xcomp->params.overclock)) {
+ if(!gsr_cuda_load(&cap_xcomp->cuda, cap_xcomp->xcomposite.params.egl->x11.dpy, cap_xcomp->overclock)) {
+ fprintf(stderr, "gsr error: gsr_capture_kms_cuda_start: failed to load cuda\n");
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
return -1;
}
- if(!cuda_create_codec_context(cap_xcomp, video_codec_context)) {
+ if(!cuda_create_codec_context(cap_xcomp->cuda.cu_ctx, video_codec_context, video_codec_context->width, video_codec_context->height, false, &cap_xcomp->cuda_stream)) {
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
return -1;
}
- if(!cuda_register_opengl_texture(cap_xcomp)) {
+ gsr_cuda_context cuda_context = {
+ .cuda = &cap_xcomp->cuda,
+ .cuda_graphics_resources = cap_xcomp->cuda_graphics_resources,
+ .mapped_arrays = cap_xcomp->mapped_arrays
+ };
+
+ if(!gsr_capture_base_setup_cuda_textures(&cap_xcomp->xcomposite.base, frame, &cuda_context, cap_xcomp->xcomposite.params.color_range, GSR_SOURCE_COLOR_RGB, false)) {
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
return -1;
}
- cap_xcomp->window_resize_timer = clock_get_monotonic_seconds();
return 0;
}
-static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context) {
- gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
-
- window_texture_deinit(&cap_xcomp->window_texture);
-
- if(cap_xcomp->target_texture_id) {
- cap_xcomp->egl.glDeleteTextures(1, &cap_xcomp->target_texture_id);
- cap_xcomp->target_texture_id = 0;
- }
-
- if(video_codec_context->hw_device_ctx)
- av_buffer_unref(&video_codec_context->hw_device_ctx);
- if(video_codec_context->hw_frames_ctx)
- av_buffer_unref(&video_codec_context->hw_frames_ctx);
-
+static void gsr_capture_xcomposite_unload_cuda_graphics(gsr_capture_xcomposite_cuda *cap_xcomp) {
if(cap_xcomp->cuda.cu_ctx) {
- CUcontext old_ctx;
- cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx);
-
- cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resource, 0);
- cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resource);
- cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
- }
- gsr_cuda_unload(&cap_xcomp->cuda);
-
- gsr_egl_unload(&cap_xcomp->egl);
- if(cap_xcomp->dpy) {
- // TODO: This causes a crash, why? maybe some other library dlclose xlib and that also happened to unload this???
- //XCloseDisplay(cap_xcomp->dpy);
- cap_xcomp->dpy = NULL;
- }
-}
-
-static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
- gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
-
- cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT);
-
- bool init_new_window = false;
- while(XPending(cap_xcomp->dpy)) {
- XNextEvent(cap_xcomp->dpy, &cap_xcomp->xev);
-
- switch(cap_xcomp->xev.type) {
- case DestroyNotify: {
- /* Window died (when not following focused window), so we stop recording */
- if(!cap_xcomp->params.follow_focused && cap_xcomp->xev.xdestroywindow.window == cap_xcomp->window) {
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = false;
- }
- break;
- }
- case Expose: {
- /* Requires window texture recreate */
- if(cap_xcomp->xev.xexpose.count == 0 && cap_xcomp->xev.xexpose.window == cap_xcomp->window) {
- cap_xcomp->window_resize_timer = clock_get_monotonic_seconds();
- cap_xcomp->window_resized = true;
- }
- break;
- }
- case ConfigureNotify: {
- /* Window resized */
- if(cap_xcomp->xev.xconfigure.window == cap_xcomp->window && (cap_xcomp->xev.xconfigure.width != cap_xcomp->window_size.x || cap_xcomp->xev.xconfigure.height != cap_xcomp->window_size.y)) {
- cap_xcomp->window_size.x = max_int(cap_xcomp->xev.xconfigure.width, 0);
- cap_xcomp->window_size.y = max_int(cap_xcomp->xev.xconfigure.height, 0);
- cap_xcomp->window_resize_timer = clock_get_monotonic_seconds();
- cap_xcomp->window_resized = true;
- }
- break;
+ for(int i = 0; i < 2; ++i) {
+ if(cap_xcomp->cuda_graphics_resources[i]) {
+ cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resources[i], 0);
+ cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resources[i]);
+ cap_xcomp->cuda_graphics_resources[i] = 0;
}
- case PropertyNotify: {
- /* Focused window changed */
- if(cap_xcomp->params.follow_focused && cap_xcomp->xev.xproperty.atom == cap_xcomp->net_active_window_atom) {
- init_new_window = true;
- }
- break;
- }
- }
- }
-
- if(cap_xcomp->params.follow_focused && !cap_xcomp->follow_focused_initialized) {
- init_new_window = true;
- }
-
- if(init_new_window) {
- Window focused_window = get_focused_window(cap_xcomp->dpy, cap_xcomp->net_active_window_atom);
- if(focused_window != cap_xcomp->window || !cap_xcomp->follow_focused_initialized) {
- cap_xcomp->follow_focused_initialized = true;
- XSelectInput(cap_xcomp->dpy, cap_xcomp->window, 0);
- cap_xcomp->window = focused_window;
- XSelectInput(cap_xcomp->dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask);
-
- XWindowAttributes attr;
- attr.width = 0;
- attr.height = 0;
- if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->window, &attr))
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick failed: invalid window id: %lu\n", cap_xcomp->window);
-
- cap_xcomp->window_size.x = max_int(attr.width, 0);
- cap_xcomp->window_size.y = max_int(attr.height, 0);
- cap_xcomp->window_resized = true;
-
- window_texture_deinit(&cap_xcomp->window_texture);
- window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->window, &cap_xcomp->egl); // TODO: Do not do the below window_texture_on_resize after this
-
- cap_xcomp->texture_size.x = 0;
- cap_xcomp->texture_size.y = 0;
-
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1));
- cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1));
}
}
+}
- const double window_resize_timeout = 1.0; // 1 second
- if(!cap_xcomp->created_hw_frame || (cap_xcomp->window_resized && clock_get_monotonic_seconds() - cap_xcomp->window_resize_timer >= window_resize_timeout)) {
- cap_xcomp->window_resized = false;
- if(window_texture_on_resize(&cap_xcomp->window_texture) != 0) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: window_texture_on_resize failed\n");
- //cap_xcomp->should_stop = true;
- //cap_xcomp->stop_is_error = true;
- return;
- }
-
- cap_xcomp->texture_size.x = 0;
- cap_xcomp->texture_size.y = 0;
-
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1));
- cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1));
-
- if(!cap_xcomp->created_hw_frame) {
- cap_xcomp->created_hw_frame = true;
- av_frame_free(frame);
- *frame = av_frame_alloc();
- if(!frame) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: failed to allocate frame\n");
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
- (*frame)->format = video_codec_context->pix_fmt;
- (*frame)->width = video_codec_context->width;
- (*frame)->height = video_codec_context->height;
- (*frame)->color_range = video_codec_context->color_range;
- (*frame)->color_primaries = video_codec_context->color_primaries;
- (*frame)->color_trc = video_codec_context->color_trc;
- (*frame)->colorspace = video_codec_context->colorspace;
- (*frame)->chroma_location = video_codec_context->chroma_sample_location;
-
- if(av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0) < 0) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: av_hwframe_get_buffer failed\n");
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
- }
+static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context) {
+ (void)video_codec_context;
+ gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
+ gsr_capture_xcomposite_stop(&cap_xcomp->xcomposite);
+ gsr_capture_xcomposite_unload_cuda_graphics(cap_xcomp);
+ gsr_cuda_unload(&cap_xcomp->cuda);
+}
- // Clear texture with black background because the source texture (window_texture_get_opengl_texture_id(&cap_xcomp->window_texture))
- // might be smaller than cap_xcomp->target_texture_id
- cap_xcomp->egl.glClearTexImage(cap_xcomp->target_texture_id, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
- }
+static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *video_codec_context) {
+ gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
+ gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context);
}
static bool gsr_capture_xcomposite_cuda_should_stop(gsr_capture *cap, bool *err) {
gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
- if(cap_xcomp->should_stop) {
- if(err)
- *err = cap_xcomp->stop_is_error;
- return true;
- }
-
- if(err)
- *err = false;
- return false;
+ return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err);
}
static int gsr_capture_xcomposite_cuda_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
- vec2i source_pos = { 0, 0 };
- vec2i source_size = cap_xcomp->texture_size;
-
- if(cap_xcomp->window_texture.texture_id != 0) {
- while(cap_xcomp->egl.glGetError()) {}
- /* TODO: Remove this copy, which is only possible by using nvenc directly and encoding window_pixmap.target_texture_id */
- cap_xcomp->egl.glCopyImageSubData(
- window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), GL_TEXTURE_2D, 0, source_pos.x, source_pos.y, 0,
- cap_xcomp->target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0,
- source_size.x, source_size.y, 1);
- unsigned int err = cap_xcomp->egl.glGetError();
- if(err != 0) {
- static bool error_shown = false;
- if(!error_shown) {
- error_shown = true;
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_capture: glCopyImageSubData failed, gl error: %d\n", err);
- }
- }
- }
- cap_xcomp->egl.eglSwapBuffers(cap_xcomp->egl.egl_display, cap_xcomp->egl.egl_surface);
-
- frame->linesize[0] = frame->width * 4;
- //frame->linesize[0] = frame->width * 1;
- //frame->linesize[1] = frame->width * 1;
- //frame->linesize[2] = frame->width * 1;
+ gsr_capture_xcomposite_capture(&cap_xcomp->xcomposite, frame);
- CUDA_MEMCPY2D memcpy_struct;
- memcpy_struct.srcXInBytes = 0;
- memcpy_struct.srcY = 0;
- memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY;
+ const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
+ for(int i = 0; i < 2; ++i) {
+ CUDA_MEMCPY2D memcpy_struct;
+ memcpy_struct.srcXInBytes = 0;
+ memcpy_struct.srcY = 0;
+ memcpy_struct.srcMemoryType = CU_MEMORYTYPE_ARRAY;
- memcpy_struct.dstXInBytes = 0;
- memcpy_struct.dstY = 0;
- memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE;
+ memcpy_struct.dstXInBytes = 0;
+ memcpy_struct.dstY = 0;
+ memcpy_struct.dstMemoryType = CU_MEMORYTYPE_DEVICE;
- memcpy_struct.srcArray = cap_xcomp->mapped_array;
- memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0];
- memcpy_struct.dstPitch = frame->linesize[0];
- memcpy_struct.WidthInBytes = frame->width * 4;//frame->width * 1;
- memcpy_struct.Height = frame->height;
- cap_xcomp->cuda.cuMemcpy2D_v2(&memcpy_struct);
+ memcpy_struct.srcArray = cap_xcomp->mapped_arrays[i];
+ memcpy_struct.srcPitch = frame->width / div[i];
+ memcpy_struct.dstDevice = (CUdeviceptr)frame->data[i];
+ memcpy_struct.dstPitch = frame->linesize[i];
+ memcpy_struct.WidthInBytes = frame->width;
+ memcpy_struct.Height = frame->height / div[i];
+ // TODO: Remove this copy if possible
+ cap_xcomp->cuda.cuMemcpy2DAsync_v2(&memcpy_struct, cap_xcomp->cuda_stream);
+ }
- //frame->data[1] = frame->data[0];
- //frame->data[2] = frame->data[0];
+ // TODO: needed?
+ cap_xcomp->cuda.cuStreamSynchronize(cap_xcomp->cuda_stream);
return 0;
}
@@ -501,22 +138,15 @@ gsr_capture* gsr_capture_xcomposite_cuda_create(const gsr_capture_xcomposite_cud
return NULL;
}
- Display *display = XOpenDisplay(NULL);
- if(!display) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_create failed: XOpenDisplay failed\n");
- free(cap);
- free(cap_xcomp);
- return NULL;
- }
-
- cap_xcomp->dpy = display;
- cap_xcomp->params = *params;
+ gsr_capture_xcomposite_init(&cap_xcomp->xcomposite, &params->base);
+ cap_xcomp->overclock = params->overclock;
*cap = (gsr_capture) {
.start = gsr_capture_xcomposite_cuda_start,
.tick = gsr_capture_xcomposite_cuda_tick,
.should_stop = gsr_capture_xcomposite_cuda_should_stop,
.capture = gsr_capture_xcomposite_cuda_capture,
+ .capture_end = NULL,
.destroy = gsr_capture_xcomposite_cuda_destroy,
.priv = cap_xcomp
};
diff --git a/src/capture/xcomposite_vaapi.c b/src/capture/xcomposite_vaapi.c
index bb92f7f..8c9c56c 100644
--- a/src/capture/xcomposite_vaapi.c
+++ b/src/capture/xcomposite_vaapi.c
@@ -1,607 +1,70 @@
#include "../../include/capture/xcomposite_vaapi.h"
-#include "../../include/egl.h"
-#include "../../include/window_texture.h"
-#include "../../include/utils.h"
+#include "../../include/capture/xcomposite.h"
+#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
-#include <unistd.h>
-#include <assert.h>
-#include <X11/Xlib.h>
-#include <libavutil/hwcontext.h>
-#include <libavutil/hwcontext_vaapi.h>
-#include <libavutil/frame.h>
+#include <va/va.h>
+#include <va/va_drmcommon.h>
#include <libavcodec/avcodec.h>
typedef struct {
- gsr_capture_xcomposite_vaapi_params params;
- Display *dpy;
- XEvent xev;
-
- bool should_stop;
- bool stop_is_error;
- bool window_resized;
- bool created_hw_frame;
- bool follow_focused_initialized;
-
- Window window;
- vec2i window_size;
- vec2i texture_size;
- double window_resize_timer;
-
- WindowTexture window_texture;
-
- gsr_egl egl;
-
- int fourcc;
- int num_planes;
- uint64_t modifiers;
- int dmabuf_fd;
- int32_t pitch;
- int32_t offset;
+ gsr_capture_xcomposite xcomposite;
VADisplay va_dpy;
- VAConfigID config_id;
- VAContextID context_id;
- VASurfaceID input_surface;
- VABufferID buffer_id;
- VARectangle output_region;
-
- Atom net_active_window_atom;
+ VADRMPRIMESurfaceDescriptor prime;
} gsr_capture_xcomposite_vaapi;
-static int max_int(int a, int b) {
- return a > b ? a : b;
-}
-
-static int min_int(int a, int b) {
- return a < b ? a : b;
-}
-
static void gsr_capture_xcomposite_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context);
-static Window get_focused_window(Display *display, Atom net_active_window_atom) {
- Atom type;
- int format = 0;
- unsigned long num_items = 0;
- unsigned long bytes_after = 0;
- unsigned char *properties = NULL;
- if(XGetWindowProperty(display, DefaultRootWindow(display), net_active_window_atom, 0, 1024, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &properties) == Success && properties) {
- Window focused_window = *(unsigned long*)properties;
- XFree(properties);
- return focused_window;
- }
- return None;
-}
-
-static bool drm_create_codec_context(gsr_capture_xcomposite_vaapi *cap_xcomp, AVCodecContext *video_codec_context) {
- AVBufferRef *device_ctx;
- if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, cap_xcomp->params.card_path, NULL, 0) < 0) {
- fprintf(stderr, "Error: Failed to create hardware device context\n");
- return false;
- }
-
- AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
- if(!frame_context) {
- fprintf(stderr, "Error: Failed to create hwframe context\n");
- av_buffer_unref(&device_ctx);
- return false;
- }
-
- AVHWFramesContext *hw_frame_context =
- (AVHWFramesContext *)frame_context->data;
- hw_frame_context->width = video_codec_context->width;
- hw_frame_context->height = video_codec_context->height;
- hw_frame_context->sw_format = AV_PIX_FMT_NV12;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_YUV420P;//AV_PIX_FMT_0RGB32;//AV_PIX_FMT_NV12;
- hw_frame_context->format = video_codec_context->pix_fmt;
- hw_frame_context->device_ref = device_ctx;
- hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
-
- hw_frame_context->initial_pool_size = 1;
-
- AVVAAPIDeviceContext *vactx =((AVHWDeviceContext*)device_ctx->data)->hwctx;
- cap_xcomp->va_dpy = vactx->display;
-
- if (av_hwframe_ctx_init(frame_context) < 0) {
- fprintf(stderr, "Error: Failed to initialize hardware frame context "
- "(note: ffmpeg version needs to be > 4.0)\n");
- av_buffer_unref(&device_ctx);
- //av_buffer_unref(&frame_context);
- return false;
- }
-
- video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx);
- video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context);
- return true;
-}
-
-#define DRM_FORMAT_MOD_INVALID 72057594037927935
-
-static int gsr_capture_xcomposite_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
+static int gsr_capture_xcomposite_vaapi_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
- if(cap_xcomp->params.follow_focused) {
- cap_xcomp->net_active_window_atom = XInternAtom(cap_xcomp->dpy, "_NET_ACTIVE_WINDOW", False);
- if(!cap_xcomp->net_active_window_atom) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start failed: failed to get _NET_ACTIVE_WINDOW atom\n");
- return -1;
- }
- cap_xcomp->window = get_focused_window(cap_xcomp->dpy, cap_xcomp->net_active_window_atom);
- } else {
- cap_xcomp->window = cap_xcomp->params.window;
- }
-
- /* TODO: Do these in tick, and allow error if follow_focused */
-
- XWindowAttributes attr;
- if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->params.window, &attr) && !cap_xcomp->params.follow_focused) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start failed: invalid window id: %lu\n", cap_xcomp->params.window);
- return -1;
- }
-
- cap_xcomp->window_size.x = max_int(attr.width, 0);
- cap_xcomp->window_size.y = max_int(attr.height, 0);
-
- if(cap_xcomp->params.follow_focused)
- XSelectInput(cap_xcomp->dpy, DefaultRootWindow(cap_xcomp->dpy), PropertyChangeMask);
-
- // TODO: Get select and add these on top of it and then restore at the end. Also do the same in other xcomposite
- XSelectInput(cap_xcomp->dpy, cap_xcomp->params.window, StructureNotifyMask | ExposureMask);
-
- if(!gsr_egl_load(&cap_xcomp->egl, cap_xcomp->dpy)) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start: failed to load opengl\n");
- return -1;
- }
-
- if(!cap_xcomp->egl.eglExportDMABUFImageQueryMESA) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start: could not find eglExportDMABUFImageQueryMESA\n");
- gsr_egl_unload(&cap_xcomp->egl);
- return -1;
- }
-
- if(!cap_xcomp->egl.eglExportDMABUFImageMESA) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start: could not find eglExportDMABUFImageMESA\n");
- gsr_egl_unload(&cap_xcomp->egl);
- return -1;
+ const int res = gsr_capture_xcomposite_start(&cap_xcomp->xcomposite, video_codec_context, frame);
+ if(res != 0) {
+ gsr_capture_xcomposite_vaapi_stop(cap, video_codec_context);
+ return res;
}
- /* Disable vsync */
- cap_xcomp->egl.eglSwapInterval(cap_xcomp->egl.egl_display, 0);
- if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->params.window, &cap_xcomp->egl) != 0 && !cap_xcomp->params.follow_focused) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_start: failed get window texture for window %ld\n", cap_xcomp->params.window);
- gsr_egl_unload(&cap_xcomp->egl);
+ if(!drm_create_codec_context(cap_xcomp->xcomposite.params.egl->card_path, video_codec_context, video_codec_context->width, video_codec_context->height, false, &cap_xcomp->va_dpy)) {
+ gsr_capture_xcomposite_vaapi_stop(cap, video_codec_context);
return -1;
}
- cap_xcomp->texture_size.x = 0;
- cap_xcomp->texture_size.y = 0;
-
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
- cap_xcomp->texture_size.x = 0;
- cap_xcomp->texture_size.y = 0;
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1);
- cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1);
-
- video_codec_context->width = cap_xcomp->texture_size.x;
- video_codec_context->height = cap_xcomp->texture_size.y;
-
- if(cap_xcomp->params.region_size.x > 0 && cap_xcomp->params.region_size.y > 0) {
- video_codec_context->width = max_int(2, cap_xcomp->params.region_size.x & ~1);
- video_codec_context->height = max_int(2, cap_xcomp->params.region_size.y & ~1);
- }
-
- if(!drm_create_codec_context(cap_xcomp, video_codec_context)) {
+ if(!gsr_capture_base_setup_vaapi_textures(&cap_xcomp->xcomposite.base, frame, cap_xcomp->va_dpy, &cap_xcomp->prime, cap_xcomp->xcomposite.params.color_range)) {
gsr_capture_xcomposite_vaapi_stop(cap, video_codec_context);
return -1;
}
- cap_xcomp->window_resize_timer = clock_get_monotonic_seconds();
return 0;
}
-static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
+static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
-
- // TODO:
- //cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT);
-
- bool init_new_window = false;
- while(XPending(cap_xcomp->dpy)) {
- XNextEvent(cap_xcomp->dpy, &cap_xcomp->xev);
-
- switch(cap_xcomp->xev.type) {
- case DestroyNotify: {
- /* Window died (when not following focused window), so we stop recording */
- if(!cap_xcomp->params.follow_focused && cap_xcomp->xev.xdestroywindow.window == cap_xcomp->window) {
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = false;
- }
- break;
- }
- case Expose: {
- /* Requires window texture recreate */
- if(cap_xcomp->xev.xexpose.count == 0 && cap_xcomp->xev.xexpose.window == cap_xcomp->window) {
- cap_xcomp->window_resize_timer = clock_get_monotonic_seconds();
- cap_xcomp->window_resized = true;
- }
- break;
- }
- case ConfigureNotify: {
- /* Window resized */
- if(cap_xcomp->xev.xconfigure.window == cap_xcomp->window && (cap_xcomp->xev.xconfigure.width != cap_xcomp->window_size.x || cap_xcomp->xev.xconfigure.height != cap_xcomp->window_size.y)) {
- cap_xcomp->window_size.x = max_int(cap_xcomp->xev.xconfigure.width, 0);
- cap_xcomp->window_size.y = max_int(cap_xcomp->xev.xconfigure.height, 0);
- cap_xcomp->window_resize_timer = clock_get_monotonic_seconds();
- cap_xcomp->window_resized = true;
- }
- break;
- }
- case PropertyNotify: {
- /* Focused window changed */
- if(cap_xcomp->params.follow_focused && cap_xcomp->xev.xproperty.atom == cap_xcomp->net_active_window_atom) {
- init_new_window = true;
- }
- break;
- }
- }
- }
-
- if(cap_xcomp->params.follow_focused && !cap_xcomp->follow_focused_initialized) {
- init_new_window = true;
- }
-
- if(init_new_window) {
- Window focused_window = get_focused_window(cap_xcomp->dpy, cap_xcomp->net_active_window_atom);
- if(focused_window != cap_xcomp->window || !cap_xcomp->follow_focused_initialized) {
- cap_xcomp->follow_focused_initialized = true;
- XSelectInput(cap_xcomp->dpy, cap_xcomp->window, 0);
- cap_xcomp->window = focused_window;
- XSelectInput(cap_xcomp->dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask);
-
- XWindowAttributes attr;
- attr.width = 0;
- attr.height = 0;
- if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->window, &attr))
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick failed: invalid window id: %lu\n", cap_xcomp->window);
-
- cap_xcomp->window_size.x = max_int(attr.width, 0);
- cap_xcomp->window_size.y = max_int(attr.height, 0);
- cap_xcomp->window_resized = true;
-
- window_texture_deinit(&cap_xcomp->window_texture);
- window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->window, &cap_xcomp->egl); // TODO: Do not do the below window_texture_on_resize after this
-
- cap_xcomp->texture_size.x = 0;
- cap_xcomp->texture_size.y = 0;
-
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1));
- cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1));
- }
- }
-
- const double window_resize_timeout = 1.0; // 1 second
- if(!cap_xcomp->created_hw_frame || (cap_xcomp->window_resized && clock_get_monotonic_seconds() - cap_xcomp->window_resize_timer >= window_resize_timeout)) {
- cap_xcomp->window_resized = false;
-
- if(window_texture_on_resize(&cap_xcomp->window_texture) != 0) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: window_texture_on_resize failed\n");
- //cap_xcomp->should_stop = true;
- //cap_xcomp->stop_is_error = true;
- return;
- }
-
- cap_xcomp->texture_size.x = 0;
- cap_xcomp->texture_size.y = 0;
-
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1));
- cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1));
-
- if(cap_xcomp->buffer_id) {
- vaDestroyBuffer(cap_xcomp->va_dpy, cap_xcomp->buffer_id);
- cap_xcomp->buffer_id = 0;
- }
-
- if(cap_xcomp->context_id) {
- vaDestroyContext(cap_xcomp->va_dpy, cap_xcomp->context_id);
- cap_xcomp->context_id = 0;
- }
-
- if(cap_xcomp->config_id) {
- vaDestroyConfig(cap_xcomp->va_dpy, cap_xcomp->config_id);
- cap_xcomp->config_id = 0;
- }
-
- if(cap_xcomp->input_surface) {
- vaDestroySurfaces(cap_xcomp->va_dpy, &cap_xcomp->input_surface, 1);
- cap_xcomp->input_surface = 0;
- }
-
- if(cap_xcomp->dmabuf_fd) {
- close(cap_xcomp->dmabuf_fd);
- cap_xcomp->dmabuf_fd = 0;
- }
-
- if(!cap_xcomp->created_hw_frame) {
- cap_xcomp->created_hw_frame = true;
- av_frame_free(frame);
- *frame = av_frame_alloc();
- if(!frame) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: failed to allocate frame\n");
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
- (*frame)->format = video_codec_context->pix_fmt;
- (*frame)->width = video_codec_context->width;
- (*frame)->height = video_codec_context->height;
- (*frame)->color_range = video_codec_context->color_range;
- (*frame)->color_primaries = video_codec_context->color_primaries;
- (*frame)->color_trc = video_codec_context->color_trc;
- (*frame)->colorspace = video_codec_context->colorspace;
- (*frame)->chroma_location = video_codec_context->chroma_sample_location;
-
- int res = av_hwframe_get_buffer(video_codec_context->hw_frames_ctx, *frame, 0);
- if(res < 0) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: av_hwframe_get_buffer failed: %d\n", res);
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
- }
-
- int xx = 0, yy = 0;
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &xx);
- cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &yy);
- cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
-
- const intptr_t pixmap_attrs[] = {
- EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
- EGL_NONE,
- };
-
- EGLImage img = cap_xcomp->egl.eglCreateImage(cap_xcomp->egl.egl_display, cap_xcomp->egl.egl_context, EGL_GL_TEXTURE_2D, (EGLClientBuffer)(uint64_t)window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), pixmap_attrs);
- if(!img) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: eglCreateImage failed\n");
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
-
- if(!cap_xcomp->egl.eglExportDMABUFImageQueryMESA(cap_xcomp->egl.egl_display, img, &cap_xcomp->fourcc, &cap_xcomp->num_planes, &cap_xcomp->modifiers)) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: eglExportDMABUFImageQueryMESA failed\n");
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- cap_xcomp->egl.eglDestroyImage(cap_xcomp->egl.egl_display, img);
- return;
- }
-
- if(cap_xcomp->num_planes != 1) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: expected 1 plane for drm buf, got %d planes\n", cap_xcomp->num_planes);
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- cap_xcomp->egl.eglDestroyImage(cap_xcomp->egl.egl_display, img);
- return;
- }
-
- if(!cap_xcomp->egl.eglExportDMABUFImageMESA(cap_xcomp->egl.egl_display, img, &cap_xcomp->dmabuf_fd, &cap_xcomp->pitch, &cap_xcomp->offset)) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: eglExportDMABUFImageMESA failed\n");
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- cap_xcomp->egl.eglDestroyImage(cap_xcomp->egl.egl_display, img);
- return;
- }
-
- cap_xcomp->egl.eglDestroyImage(cap_xcomp->egl.egl_display, img);
-
- uintptr_t dmabuf = cap_xcomp->dmabuf_fd;
-
- VASurfaceAttribExternalBuffers buf = {0};
- buf.pixel_format = VA_FOURCC_BGRX;
- buf.width = xx;
- buf.height = yy;
- buf.data_size = yy * cap_xcomp->pitch;
- buf.num_planes = 1;
- buf.pitches[0] = cap_xcomp->pitch;
- buf.offsets[0] = cap_xcomp->offset;
- buf.buffers = &dmabuf;
- buf.num_buffers = 1;
- buf.flags = 0;
- buf.private_data = 0;
-
- #define VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME 0x20000000
-
- VASurfaceAttrib attribs[2] = {0};
- attribs[0].type = VASurfaceAttribMemoryType;
- attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
- attribs[0].value.type = VAGenericValueTypeInteger;
- attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME;
- attribs[1].type = VASurfaceAttribExternalBufferDescriptor;
- attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
- attribs[1].value.type = VAGenericValueTypePointer;
- attribs[1].value.value.p = &buf;
-
- VAStatus va_status = vaCreateSurfaces(cap_xcomp->va_dpy, VA_RT_FORMAT_RGB32, xx, yy, &cap_xcomp->input_surface, 1, attribs, 2);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaCreateSurfaces failed: %d\n", va_status);
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
-
- va_status = vaCreateConfig(cap_xcomp->va_dpy, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &cap_xcomp->config_id);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaCreateConfig failed: %d\n", va_status);
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
-
- VASurfaceID target_surface_id = (uintptr_t)(*frame)->data[3];
- va_status = vaCreateContext(cap_xcomp->va_dpy, cap_xcomp->config_id, xx, yy, VA_PROGRESSIVE, &target_surface_id, 1, &cap_xcomp->context_id);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaCreateContext failed: %d\n", va_status);
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
-
- cap_xcomp->output_region = (VARectangle){
- .x = 0,
- .y = 0,
- .width = xx,
- .height = yy
- };
-
- // Copying a surface to another surface will automatically perform the color conversion. Thanks vaapi!
- VAProcPipelineParameterBuffer params = {0};
- params.surface = cap_xcomp->input_surface;
- params.surface_region = NULL;
- params.output_region = &cap_xcomp->output_region;
- params.output_background_color = 0;
- params.filter_flags = VA_FRAME_PICTURE;
-
- params.input_color_properties.colour_primaries = 1;
- params.input_color_properties.transfer_characteristics = 1;
- params.input_color_properties.matrix_coefficients = 1;
- params.surface_color_standard = VAProcColorStandardBT709;
- params.input_color_properties.color_range = (*frame)->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
-
- params.output_color_properties.colour_primaries = 1;
- params.output_color_properties.transfer_characteristics = 1;
- params.output_color_properties.matrix_coefficients = 1;
- params.output_color_standard = VAProcColorStandardBT709;
- params.output_color_properties.color_range = (*frame)->color_range == AVCOL_RANGE_JPEG ? VA_SOURCE_RANGE_FULL : VA_SOURCE_RANGE_REDUCED;
-
- params.processing_mode = VAProcPerformanceMode;
-
- va_status = vaCreateBuffer(cap_xcomp->va_dpy, cap_xcomp->context_id, VAProcPipelineParameterBufferType, sizeof(params), 1, &params, &cap_xcomp->buffer_id);
- if(va_status != VA_STATUS_SUCCESS) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaCreateBuffer failed: %d\n", va_status);
- cap_xcomp->should_stop = true;
- cap_xcomp->stop_is_error = true;
- return;
- }
-
- // Clear texture with black background because the source texture (window_texture_get_opengl_texture_id(&cap_xcomp->window_texture))
- // might be smaller than cap_xcomp->target_texture_id
- // TODO:
- //cap_xcomp->egl.glClearTexImage(cap_xcomp->target_texture_id, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
- }
+ gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context);
}
static bool gsr_capture_xcomposite_vaapi_should_stop(gsr_capture *cap, bool *err) {
gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
- if(cap_xcomp->should_stop) {
- if(err)
- *err = cap_xcomp->stop_is_error;
- return true;
- }
-
- if(err)
- *err = false;
- return false;
+ return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err);
}
static int gsr_capture_xcomposite_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
-
- VASurfaceID target_surface_id = (uintptr_t)frame->data[3];
-
- VAStatus va_status = vaBeginPicture(cap_xcomp->va_dpy, cap_xcomp->context_id, target_surface_id);
- if(va_status != VA_STATUS_SUCCESS) {
- static bool error_printed = false;
- if(!error_printed) {
- error_printed = true;
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaBeginPicture failed: %d\n", va_status);
- }
- return -1;
- }
-
- va_status = vaRenderPicture(cap_xcomp->va_dpy, cap_xcomp->context_id, &cap_xcomp->buffer_id, 1);
- if(va_status != VA_STATUS_SUCCESS) {
- vaEndPicture(cap_xcomp->va_dpy, cap_xcomp->context_id);
- static bool error_printed = false;
- if(!error_printed) {
- error_printed = true;
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaRenderPicture failed: %d\n", va_status);
- }
- return -1;
- }
-
- va_status = vaEndPicture(cap_xcomp->va_dpy, cap_xcomp->context_id);
- if(va_status != VA_STATUS_SUCCESS) {
- static bool error_printed = false;
- if(!error_printed) {
- error_printed = true;
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_tick: vaEndPicture failed: %d\n", va_status);
- }
- return -1;
- }
-
- // TODO: Needed?
- //vaSyncSurface(cap_xcomp->va_dpy, target_surface_id);
-
- // TODO: Remove
- //cap_xcomp->egl.eglSwapBuffers(cap_xcomp->egl.egl_display, cap_xcomp->egl.egl_surface);
-
- return 0;
+ return gsr_capture_xcomposite_capture(&cap_xcomp->xcomposite, frame);
}
static void gsr_capture_xcomposite_vaapi_stop(gsr_capture *cap, AVCodecContext *video_codec_context) {
+ (void)video_codec_context;
gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
- if(cap_xcomp->buffer_id) {
- vaDestroyBuffer(cap_xcomp->va_dpy, cap_xcomp->buffer_id);
- cap_xcomp->buffer_id = 0;
- }
-
- if(cap_xcomp->context_id) {
- vaDestroyContext(cap_xcomp->va_dpy, cap_xcomp->context_id);
- cap_xcomp->context_id = 0;
- }
-
- if(cap_xcomp->config_id) {
- vaDestroyConfig(cap_xcomp->va_dpy, cap_xcomp->config_id);
- cap_xcomp->config_id = 0;
- }
-
- if(cap_xcomp->input_surface) {
- vaDestroySurfaces(cap_xcomp->va_dpy, &cap_xcomp->input_surface, 1);
- cap_xcomp->input_surface = 0;
- }
-
- if(cap_xcomp->dmabuf_fd) {
- close(cap_xcomp->dmabuf_fd);
- cap_xcomp->dmabuf_fd = 0;
+ for(uint32_t i = 0; i < cap_xcomp->prime.num_objects; ++i) {
+ if(cap_xcomp->prime.objects[i].fd > 0) {
+ close(cap_xcomp->prime.objects[i].fd);
+ cap_xcomp->prime.objects[i].fd = 0;
+ }
}
- window_texture_deinit(&cap_xcomp->window_texture);
-
- if(video_codec_context->hw_device_ctx)
- av_buffer_unref(&video_codec_context->hw_device_ctx);
- if(video_codec_context->hw_frames_ctx)
- av_buffer_unref(&video_codec_context->hw_frames_ctx);
-
- gsr_egl_unload(&cap_xcomp->egl);
- if(cap_xcomp->dpy) {
- // TODO: This causes a crash, why? maybe some other library dlclose xlib and that also happened to unload this???
- //XCloseDisplay(cap_xcomp->dpy);
- cap_xcomp->dpy = NULL;
- }
+ gsr_capture_xcomposite_stop(&cap_xcomp->xcomposite);
}
static void gsr_capture_xcomposite_vaapi_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
@@ -630,22 +93,14 @@ gsr_capture* gsr_capture_xcomposite_vaapi_create(const gsr_capture_xcomposite_va
return NULL;
}
- Display *display = XOpenDisplay(NULL);
- if(!display) {
- fprintf(stderr, "gsr error: gsr_capture_xcomposite_vaapi_create failed: XOpenDisplay failed\n");
- free(cap);
- free(cap_xcomp);
- return NULL;
- }
-
- cap_xcomp->dpy = display;
- cap_xcomp->params = *params;
+ gsr_capture_xcomposite_init(&cap_xcomp->xcomposite, &params->base);
*cap = (gsr_capture) {
.start = gsr_capture_xcomposite_vaapi_start,
.tick = gsr_capture_xcomposite_vaapi_tick,
.should_stop = gsr_capture_xcomposite_vaapi_should_stop,
.capture = gsr_capture_xcomposite_vaapi_capture,
+ .capture_end = NULL,
.destroy = gsr_capture_xcomposite_vaapi_destroy,
.priv = cap_xcomp
};
diff --git a/src/color_conversion.c b/src/color_conversion.c
index 84500fd..cd0397e 100644
--- a/src/color_conversion.c
+++ b/src/color_conversion.c
@@ -1,10 +1,13 @@
#include "../include/color_conversion.h"
+#include "../include/egl.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <assert.h>
-#define MAX_SHADERS 2
+/* TODO: highp instead of mediump? */
+
+#define MAX_SHADERS 4
#define MAX_FRAMEBUFFERS 2
static float abs_f(float v) {
@@ -18,88 +21,184 @@ static float abs_f(float v) {
" 0.0, 0.0, 0.0, 1.0);\n" \
"}\n"
-#define RGB_TO_YUV "const mat4 RGBtoYUV = mat4(0.257, 0.439, -0.148, 0.0,\n" \
- " 0.504, -0.368, -0.291, 0.0,\n" \
- " 0.098, -0.071, 0.439, 0.0,\n" \
- " 0.0625, 0.500, 0.500, 1.0);"
+/* https://en.wikipedia.org/wiki/YCbCr, see study/color_space_transform_matrix.png */
+
+/* ITU-R BT2020, full */
+/* https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf */
+#define RGB_TO_P010_FULL "const mat4 RGBtoYUV = mat4(0.262700, -0.139630, 0.500000, 0.000000,\n" \
+ " 0.678000, -0.360370, -0.459786, 0.000000,\n" \
+ " 0.059300, 0.500000, -0.040214, 0.000000,\n" \
+ " 0.000000, 0.500000, 0.500000, 1.000000);"
+
+/* ITU-R BT2020, limited (full multiplied by (235-16)/255, adding 16/255 to luma) */
+#define RGB_TO_P010_LIMITED "const mat4 RGBtoYUV = mat4(0.225613, -0.119918, 0.429412, 0.000000,\n" \
+ " 0.582282, -0.309494, -0.394875, 0.000000,\n" \
+ " 0.050928, 0.429412, -0.034537, 0.000000,\n" \
+ " 0.062745, 0.500000, 0.500000, 1.000000);"
+
+/* ITU-R BT709, full, custom values: 0.2110 0.7110 0.0710 */
+/* https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf */
+#define RGB_TO_NV12_FULL "const mat4 RGBtoYUV = mat4(0.211000, -0.113563, 0.500000, 0.000000,\n" \
+ " 0.711000, -0.382670, -0.450570, 0.000000,\n" \
+ " 0.071000, 0.500000, -0.044994, 0.000000,\n" \
+ " 0.000000, 0.500000, 0.500000, 1.000000);"
+
+/* ITU-R BT709, limited, custom values: 0.2100 0.7100 0.0700 (full multiplied by (235-16)/255, adding 16/255 to luma) */
+#define RGB_TO_NV12_LIMITED "const mat4 RGBtoYUV = mat4(0.180353, -0.096964, 0.429412, 0.000000,\n" \
+ " 0.609765, -0.327830, -0.385927, 0.000000,\n" \
+ " 0.060118, 0.429412, -0.038049, 0.000000,\n" \
+ " 0.062745, 0.500000, 0.500000, 1.000000);"
+
+static const char* color_format_range_get_transform_matrix(gsr_destination_color color_format, gsr_color_range color_range) {
+ switch(color_format) {
+ case GSR_DESTINATION_COLOR_NV12: {
+ switch(color_range) {
+ case GSR_COLOR_RANGE_LIMITED:
+ return RGB_TO_NV12_LIMITED;
+ case GSR_COLOR_RANGE_FULL:
+ return RGB_TO_NV12_FULL;
+ }
+ break;
+ }
+ case GSR_DESTINATION_COLOR_P010: {
+ switch(color_range) {
+ case GSR_COLOR_RANGE_LIMITED:
+ return RGB_TO_P010_LIMITED;
+ case GSR_COLOR_RANGE_FULL:
+ return RGB_TO_P010_FULL;
+ }
+ break;
+ }
+ default:
+ return NULL;
+ }
+ return NULL;
+}
+
+static int load_shader_y(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *uniforms, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture) {
+ const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
-static int load_shader_y(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform) {
char vertex_shader[2048];
snprintf(vertex_shader, sizeof(vertex_shader),
"#version 300 es \n"
"in vec2 pos; \n"
"in vec2 texcoords; \n"
"out vec2 texcoords_out; \n"
+ "uniform vec2 offset; \n"
"uniform float rotation; \n"
ROTATE_Z
"void main() \n"
"{ \n"
- " texcoords_out = texcoords; \n"
- " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0) * rotate_z(rotation); \n"
+ " texcoords_out = (vec4(texcoords.x - 0.5, texcoords.y - 0.5, 0.0, 0.0) * rotate_z(rotation)).xy + vec2(0.5, 0.5); \n"
+ " gl_Position = vec4(offset.x, offset.y, 0.0, 0.0) + vec4(pos.x, pos.y, 0.0, 1.0); \n"
"} \n");
- char fragment_shader[] =
- "#version 300 es \n"
- "precision mediump float; \n"
- "in vec2 texcoords_out; \n"
- "uniform sampler2D tex1; \n"
- "out vec4 FragColor; \n"
- RGB_TO_YUV
- "void main() \n"
- "{ \n"
- " vec4 pixel = texture(tex1, texcoords_out); \n"
- " FragColor.x = (RGBtoYUV * vec4(pixel.rgb, 1.0)).x; \n"
- " FragColor.w = pixel.a; \n"
- "} \n";
+ char fragment_shader[2048];
+ if(external_texture) {
+ snprintf(fragment_shader, sizeof(fragment_shader),
+ "#version 300 es \n"
+ "#extension GL_OES_EGL_image_external : enable \n"
+ "#extension GL_OES_EGL_image_external_essl3 : require \n"
+ "precision mediump float; \n"
+ "in vec2 texcoords_out; \n"
+ "uniform samplerExternalOES tex1; \n"
+ "out vec4 FragColor; \n"
+ "%s"
+ "void main() \n"
+ "{ \n"
+ " vec4 pixel = texture(tex1, texcoords_out); \n"
+ " FragColor.x = (RGBtoYUV * vec4(pixel.rgb, 1.0)).x; \n"
+ " FragColor.w = pixel.a; \n"
+ "} \n", color_transform_matrix);
+ } else {
+ snprintf(fragment_shader, sizeof(fragment_shader),
+ "#version 300 es \n"
+ "precision mediump float; \n"
+ "in vec2 texcoords_out; \n"
+ "uniform sampler2D tex1; \n"
+ "out vec4 FragColor; \n"
+ "%s"
+ "void main() \n"
+ "{ \n"
+ " vec4 pixel = texture(tex1, texcoords_out); \n"
+ " FragColor.x = (RGBtoYUV * vec4(pixel.rgb, 1.0)).x; \n"
+ " FragColor.w = pixel.a; \n"
+ "} \n", color_transform_matrix);
+ }
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
return -1;
gsr_shader_bind_attribute_location(shader, "pos", 0);
gsr_shader_bind_attribute_location(shader, "texcoords", 1);
- *rotation_uniform = egl->glGetUniformLocation(shader->program_id, "rotation");
+ uniforms->offset = egl->glGetUniformLocation(shader->program_id, "offset");
+ uniforms->rotation = egl->glGetUniformLocation(shader->program_id, "rotation");
return 0;
}
-static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, int *rotation_uniform) {
+static unsigned int load_shader_uv(gsr_shader *shader, gsr_egl *egl, gsr_color_uniforms *uniforms, gsr_destination_color color_format, gsr_color_range color_range, bool external_texture) {
+ const char *color_transform_matrix = color_format_range_get_transform_matrix(color_format, color_range);
+
char vertex_shader[2048];
snprintf(vertex_shader, sizeof(vertex_shader),
"#version 300 es \n"
"in vec2 pos; \n"
"in vec2 texcoords; \n"
"out vec2 texcoords_out; \n"
+ "uniform vec2 offset; \n"
"uniform float rotation; \n"
ROTATE_Z
"void main() \n"
"{ \n"
- " texcoords_out = texcoords; \n"
- " gl_Position = vec4(pos.x, pos.y, 0.0, 1.0) * rotate_z(rotation) * vec4(0.5, 0.5, 1.0, 1.0) - vec4(0.5, 0.5, 0.0, 0.0); \n"
+ " texcoords_out = (vec4(texcoords.x - 0.5, texcoords.y - 0.5, 0.0, 0.0) * rotate_z(rotation)).xy + vec2(0.5, 0.5); \n"
+ " gl_Position = (vec4(offset.x, offset.y, 0.0, 0.0) + vec4(pos.x, pos.y, 0.0, 1.0)) * vec4(0.5, 0.5, 1.0, 1.0) - vec4(0.5, 0.5, 0.0, 0.0); \n"
"} \n");
- char fragment_shader[] =
- "#version 300 es \n"
- "precision mediump float; \n"
- "in vec2 texcoords_out; \n"
- "uniform sampler2D tex1; \n"
- "out vec4 FragColor; \n"
- RGB_TO_YUV
- "void main() \n"
- "{ \n"
- " vec4 pixel = texture(tex1, texcoords_out); \n"
- " FragColor.xy = (RGBtoYUV * vec4(pixel.rgb, 1.0)).zy; \n"
- " FragColor.w = pixel.a; \n"
- "} \n";
+ char fragment_shader[2048];
+ if(external_texture) {
+ snprintf(fragment_shader, sizeof(fragment_shader),
+ "#version 300 es \n"
+ "#extension GL_OES_EGL_image_external : enable \n"
+ "#extension GL_OES_EGL_image_external_essl3 : require \n"
+ "precision mediump float; \n"
+ "in vec2 texcoords_out; \n"
+ "uniform samplerExternalOES tex1; \n"
+ "out vec4 FragColor; \n"
+ "%s"
+ "void main() \n"
+ "{ \n"
+ " vec4 pixel = texture(tex1, texcoords_out); \n"
+ " FragColor.xy = (RGBtoYUV * vec4(pixel.rgb, 1.0)).yz; \n"
+ " FragColor.w = pixel.a; \n"
+ "} \n", color_transform_matrix);
+ } else {
+ snprintf(fragment_shader, sizeof(fragment_shader),
+ "#version 300 es \n"
+ "precision mediump float; \n"
+ "in vec2 texcoords_out; \n"
+ "uniform sampler2D tex1; \n"
+ "out vec4 FragColor; \n"
+ "%s"
+ "void main() \n"
+ "{ \n"
+ " vec4 pixel = texture(tex1, texcoords_out); \n"
+ " FragColor.xy = (RGBtoYUV * vec4(pixel.rgb, 1.0)).yz; \n"
+ " FragColor.w = pixel.a; \n"
+ "} \n", color_transform_matrix);
+ }
if(gsr_shader_init(shader, egl, vertex_shader, fragment_shader) != 0)
return -1;
gsr_shader_bind_attribute_location(shader, "pos", 0);
gsr_shader_bind_attribute_location(shader, "texcoords", 1);
- *rotation_uniform = egl->glGetUniformLocation(shader->program_id, "rotation");
+ uniforms->offset = egl->glGetUniformLocation(shader->program_id, "offset");
+ uniforms->rotation = egl->glGetUniformLocation(shader->program_id, "rotation");
return 0;
}
-static int loader_framebuffers(gsr_color_conversion *self) {
+static int load_framebuffers(gsr_color_conversion *self) {
+ /* TODO: Only generate the necessary amount of framebuffers (self->params.num_destination_textures) */
const unsigned int draw_buffer = GL_COLOR_ATTACHMENT0;
self->params.egl->glGenFramebuffers(MAX_FRAMEBUFFERS, self->framebuffers);
@@ -111,12 +210,14 @@ static int loader_framebuffers(gsr_color_conversion *self) {
goto err;
}
- self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
- self->params.egl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->params.destination_textures[1], 0);
- self->params.egl->glDrawBuffers(1, &draw_buffer);
- if(self->params.egl->glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
- fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to create framebuffer for UV\n");
- goto err;
+ if(self->params.num_destination_textures > 1) {
+ self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
+ self->params.egl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->params.destination_textures[1], 0);
+ self->params.egl->glDrawBuffers(1, &draw_buffer);
+ if(self->params.egl->glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to create framebuffer for UV\n");
+ goto err;
+ }
}
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -152,22 +253,40 @@ int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conver
self->params.egl = params->egl;
self->params = *params;
- if(self->params.num_destination_textures != 2) {
- fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 2 destination textures for destination color NV12, got %d destination texture(s)\n", self->params.num_destination_textures);
- return -1;
+ switch(params->destination_color) {
+ case GSR_DESTINATION_COLOR_NV12:
+ case GSR_DESTINATION_COLOR_P010: {
+ if(self->params.num_destination_textures != 2) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: expected 2 destination textures for destination color NV12/P010, got %d destination texture(s)\n", self->params.num_destination_textures);
+ return -1;
+ }
+
+ if(load_shader_y(&self->shaders[0], self->params.egl, &self->uniforms[0], params->destination_color, params->color_range, false) != 0) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
+ goto err;
+ }
+
+ if(load_shader_uv(&self->shaders[1], self->params.egl, &self->uniforms[1], params->destination_color, params->color_range, false) != 0) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV shader\n");
+ goto err;
+ }
+
+ if(self->params.load_external_image_shader) {
+ if(load_shader_y(&self->shaders[2], self->params.egl, &self->uniforms[2], params->destination_color, params->color_range, true) != 0) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
+ goto err;
+ }
+
+ if(load_shader_uv(&self->shaders[3], self->params.egl, &self->uniforms[3], params->destination_color, params->color_range, true) != 0) {
+ fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV shader\n");
+ goto err;
+ }
+ }
+ break;
+ }
}
- if(load_shader_y(&self->shaders[0], self->params.egl, &self->rotation_uniforms[0]) != 0) {
- fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load Y shader\n");
- goto err;
- }
-
- if(load_shader_uv(&self->shaders[1], self->params.egl, &self->rotation_uniforms[1]) != 0) {
- fprintf(stderr, "gsr error: gsr_color_conversion_init: failed to load UV shader\n");
- goto err;
- }
-
- if(loader_framebuffers(self) != 0)
+ if(load_framebuffers(self) != 0)
goto err;
if(create_vertices(self) != 0)
@@ -176,7 +295,6 @@ int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conver
return 0;
err:
- self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
gsr_color_conversion_deinit(self);
return -1;
}
@@ -207,20 +325,46 @@ void gsr_color_conversion_deinit(gsr_color_conversion *self) {
self->params.egl = NULL;
}
+static void gsr_color_conversion_swizzle_texture_source(gsr_color_conversion *self) {
+ if(self->params.source_color == GSR_SOURCE_COLOR_BGR) {
+ const int swizzle_mask[] = { GL_BLUE, GL_GREEN, GL_RED, 1 };
+ self->params.egl->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle_mask);
+ }
+}
+
+static void gsr_color_conversion_swizzle_reset(gsr_color_conversion *self) {
+ if(self->params.source_color == GSR_SOURCE_COLOR_BGR) {
+ const int swizzle_mask[] = { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA };
+ self->params.egl->glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle_mask);
+ }
+}
+
/* |source_pos| is in pixel coordinates and |source_size| */
-int gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i source_pos, vec2i source_size, vec2i texture_pos, vec2i texture_size, float rotation) {
+void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i source_pos, vec2i source_size, vec2i texture_pos, vec2i texture_size, float rotation, bool external_texture) {
+ // TODO: Remove this crap
+ rotation = M_PI*2.0f - rotation;
+
/* TODO: Do not call this every frame? */
vec2i dest_texture_size = {0, 0};
self->params.egl->glBindTexture(GL_TEXTURE_2D, self->params.destination_textures[0]);
self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &dest_texture_size.x);
self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &dest_texture_size.y);
+ self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ const int texture_target = external_texture ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
+
+ self->params.egl->glBindTexture(texture_target, texture_id);
- /* TODO: Do not call this every frame? */
vec2i source_texture_size = {0, 0};
- self->params.egl->glBindTexture(GL_TEXTURE_2D, texture_id);
- self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &source_texture_size.x);
- self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &source_texture_size.y);
+ if(external_texture) {
+ source_texture_size = source_size;
+ } else {
+ /* TODO: Do not call this every frame? */
+ self->params.egl->glGetTexLevelParameteriv(texture_target, 0, GL_TEXTURE_WIDTH, &source_texture_size.x);
+ self->params.egl->glGetTexLevelParameteriv(texture_target, 0, GL_TEXTURE_HEIGHT, &source_texture_size.y);
+ }
+ // TODO: Remove this crap
if(abs_f(M_PI * 0.5f - rotation) <= 0.001f || abs_f(M_PI * 1.5f - rotation) <= 0.001f) {
float tmp = source_texture_size.x;
source_texture_size.x = source_texture_size.y;
@@ -248,18 +392,19 @@ int gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_i
};
const float vertices[] = {
- -1.0f + pos_norm.x, -1.0f + pos_norm.y + size_norm.y, texture_pos_norm.x, texture_pos_norm.y + texture_size_norm.y,
- -1.0f + pos_norm.x, -1.0f + pos_norm.y, texture_pos_norm.x, texture_pos_norm.y,
- -1.0f + pos_norm.x + size_norm.x, -1.0f + pos_norm.y, texture_pos_norm.x + texture_size_norm.x, texture_pos_norm.y,
+ -1.0f + 0.0f, -1.0f + 0.0f + size_norm.y, texture_pos_norm.x, texture_pos_norm.y + texture_size_norm.y,
+ -1.0f + 0.0f, -1.0f + 0.0f, texture_pos_norm.x, texture_pos_norm.y,
+ -1.0f + 0.0f + size_norm.x, -1.0f + 0.0f, texture_pos_norm.x + texture_size_norm.x, texture_pos_norm.y,
- -1.0f + pos_norm.x, -1.0f + pos_norm.y + size_norm.y, texture_pos_norm.x, texture_pos_norm.y + texture_size_norm.y,
- -1.0f + pos_norm.x + size_norm.x, -1.0f + pos_norm.y, texture_pos_norm.x + texture_size_norm.x, texture_pos_norm.y,
- -1.0f + pos_norm.x + size_norm.x, -1.0f + pos_norm.y + size_norm.y, texture_pos_norm.x + texture_size_norm.x, texture_pos_norm.y + texture_size_norm.y
+ -1.0f + 0.0f, -1.0f + 0.0f + size_norm.y, texture_pos_norm.x, texture_pos_norm.y + texture_size_norm.y,
+ -1.0f + 0.0f + size_norm.x, -1.0f + 0.0f, texture_pos_norm.x + texture_size_norm.x, texture_pos_norm.y,
+ -1.0f + 0.0f + size_norm.x, -1.0f + 0.0f + size_norm.y, texture_pos_norm.x + texture_size_norm.x, texture_pos_norm.y + texture_size_norm.y
};
+ gsr_color_conversion_swizzle_texture_source(self);
+
self->params.egl->glBindVertexArray(self->vertex_array_object_id);
self->params.egl->glViewport(0, 0, dest_texture_size.x, dest_texture_size.y);
- self->params.egl->glBindTexture(GL_TEXTURE_2D, texture_id);
/* TODO: this, also cleanup */
//self->params.egl->glBindBuffer(GL_ARRAY_BUFFER, self->vertex_buffer_object_id);
@@ -267,25 +412,58 @@ int gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_i
{
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
- //cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT); // TODO: Do this in a separate clear_ function. We want to do that when using multiple drm to create the final image (multiple monitors for example)
+ //cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT); // TODO: Do this in a separate clear_ function. We want to do that when using multiple drm to create the final image (multiple monitors for example)
- gsr_shader_use(&self->shaders[0]);
- self->params.egl->glUniform1f(self->rotation_uniforms[0], rotation);
+ const int shader_index = external_texture ? 2 : 0;
+ gsr_shader_use(&self->shaders[shader_index]);
+ self->params.egl->glUniform1f(self->uniforms[shader_index].rotation, rotation);
+ self->params.egl->glUniform2f(self->uniforms[shader_index].offset, pos_norm.x, pos_norm.y);
self->params.egl->glDrawArrays(GL_TRIANGLES, 0, 6);
}
- {
+ if(self->params.num_destination_textures > 1) {
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
- //cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT);
+ //cap_xcomp->params.egl->glClear(GL_COLOR_BUFFER_BIT);
- gsr_shader_use(&self->shaders[1]);
- self->params.egl->glUniform1f(self->rotation_uniforms[1], rotation);
+ const int shader_index = external_texture ? 3 : 1;
+ gsr_shader_use(&self->shaders[shader_index]);
+ self->params.egl->glUniform1f(self->uniforms[shader_index].rotation, rotation);
+ self->params.egl->glUniform2f(self->uniforms[shader_index].offset, pos_norm.x, pos_norm.y);
self->params.egl->glDrawArrays(GL_TRIANGLES, 0, 6);
}
self->params.egl->glBindVertexArray(0);
gsr_shader_use_none(&self->shaders[0]);
- self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
+ self->params.egl->glBindTexture(texture_target, 0);
+ self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ gsr_color_conversion_swizzle_reset(self);
+}
+
+void gsr_color_conversion_clear(gsr_color_conversion *self) {
+ float color1[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+ float color2[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+
+ switch(self->params.destination_color) {
+ case GSR_DESTINATION_COLOR_NV12:
+ case GSR_DESTINATION_COLOR_P010: {
+ color2[0] = 0.5f;
+ color2[1] = 0.5f;
+ color2[2] = 0.0f;
+ color2[3] = 1.0f;
+ break;
+ }
+ }
+
+ self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[0]);
+ self->params.egl->glClearColor(color1[0], color1[1], color1[2], color1[3]);
+ self->params.egl->glClear(GL_COLOR_BUFFER_BIT);
+
+ if(self->params.num_destination_textures > 1) {
+ self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, self->framebuffers[1]);
+ self->params.egl->glClearColor(color2[0], color2[1], color2[2], color2[3]);
+ self->params.egl->glClear(GL_COLOR_BUFFER_BIT);
+ }
+
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
- return 0;
}
diff --git a/src/cuda.c b/src/cuda.c
index 32b447e..6d685b5 100644
--- a/src/cuda.c
+++ b/src/cuda.c
@@ -3,6 +3,7 @@
#include <string.h>
#include <stdio.h>
#include <dlfcn.h>
+#include <assert.h>
bool gsr_cuda_load(gsr_cuda *self, Display *display, bool do_overclock) {
memset(self, 0, sizeof(gsr_cuda));
@@ -27,10 +28,12 @@ bool gsr_cuda_load(gsr_cuda *self, Display *display, bool do_overclock) {
{ (void**)&self->cuCtxPushCurrent_v2, "cuCtxPushCurrent_v2" },
{ (void**)&self->cuCtxPopCurrent_v2, "cuCtxPopCurrent_v2" },
{ (void**)&self->cuGetErrorString, "cuGetErrorString" },
- { (void**)&self->cuMemsetD8_v2, "cuMemsetD8_v2" },
{ (void**)&self->cuMemcpy2D_v2, "cuMemcpy2D_v2" },
+ { (void**)&self->cuMemcpy2DAsync_v2, "cuMemcpy2DAsync_v2" },
+ { (void**)&self->cuStreamSynchronize, "cuStreamSynchronize" },
{ (void**)&self->cuGraphicsGLRegisterImage, "cuGraphicsGLRegisterImage" },
+ { (void**)&self->cuGraphicsEGLRegisterImage, "cuGraphicsEGLRegisterImage" },
{ (void**)&self->cuGraphicsResourceSetMapFlags, "cuGraphicsResourceSetMapFlags" },
{ (void**)&self->cuGraphicsMapResources, "cuGraphicsMapResources" },
{ (void**)&self->cuGraphicsUnmapResources, "cuGraphicsUnmapResources" },
@@ -80,6 +83,7 @@ bool gsr_cuda_load(gsr_cuda *self, Display *display, bool do_overclock) {
}
if(self->do_overclock) {
+ assert(display);
if(gsr_overclock_load(&self->overclock, display))
gsr_overclock_start(&self->overclock);
else
diff --git a/src/cursor.c b/src/cursor.c
index 9212b55..737c33b 100644
--- a/src/cursor.c
+++ b/src/cursor.c
@@ -80,6 +80,11 @@ int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) {
}
self->egl->glGenTextures(1, &self->texture_id);
+
+ XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), XFixesDisplayCursorNotifyMask);
+ gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
+ self->cursor_image_set = true;
+
return 0;
}
@@ -92,44 +97,31 @@ void gsr_cursor_deinit(gsr_cursor *self) {
self->texture_id = 0;
}
- if(self->window) {
- XFixesSelectCursorInput(self->display, self->window, 0);
- self->window = None;
- }
+ XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), 0);
self->display = NULL;
self->egl = NULL;
}
-int gsr_cursor_change_window_target(gsr_cursor *self, Window window) {
- if(self->window)
- XFixesSelectCursorInput(self->display, self->window, 0);
-
- XFixesSelectCursorInput(self->display, window, XFixesDisplayCursorNotifyMask);
- self->window = window;
- self->cursor_image_set = false;
- return 0;
-}
-
void gsr_cursor_update(gsr_cursor *self, XEvent *xev) {
if(xev->type == self->x_fixes_event_base + XFixesCursorNotify) {
XFixesCursorNotifyEvent *cursor_notify_event = (XFixesCursorNotifyEvent*)xev;
- if(cursor_notify_event->subtype == XFixesDisplayCursorNotify && cursor_notify_event->window == self->window) {
+ if(cursor_notify_event->subtype == XFixesDisplayCursorNotify && cursor_notify_event->window == DefaultRootWindow(self->display)) {
self->cursor_image_set = true;
gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
}
}
- if(!self->cursor_image_set && self->window) {
+ if(!self->cursor_image_set) {
self->cursor_image_set = true;
gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
}
}
-void gsr_cursor_tick(gsr_cursor *self) {
+void gsr_cursor_tick(gsr_cursor *self, Window relative_to) {
/* TODO: Use XInput2 instead. However that doesn't work when the pointer is grabbed. Maybe check for focused window change and XSelectInput PointerMask */
Window dummy_window;
int dummy_i;
unsigned int dummy_u;
- XQueryPointer(self->display, DefaultRootWindow(self->display), &dummy_window, &dummy_window, &dummy_i, &dummy_i, &self->position.x, &self->position.y, &dummy_u);
+ XQueryPointer(self->display, relative_to, &dummy_window, &dummy_window, &dummy_i, &dummy_i, &self->position.x, &self->position.y, &dummy_u);
}
diff --git a/src/egl.c b/src/egl.c
index a0fcc23..2ad4f85 100644
--- a/src/egl.c
+++ b/src/egl.c
@@ -1,84 +1,329 @@
#include "../include/egl.h"
#include "../include/library_loader.h"
+#include "../include/utils.h"
#include <string.h>
#include <stdio.h>
+#include <stdlib.h>
#include <dlfcn.h>
+#include <assert.h>
+
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <unistd.h>
+#include <sys/capability.h>
+
+// TODO: rename gsr_egl to something else since this includes both egl and eglx and in the future maybe vulkan too
+
+// TODO: Move this shit to a separate wayland file, and have a separate file for x11.
+
+static void output_handle_geometry(void *data, struct wl_output *wl_output,
+ int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
+ int32_t subpixel, const char *make, const char *model,
+ int32_t transform) {
+ (void)wl_output;
+ (void)phys_width;
+ (void)phys_height;
+ (void)subpixel;
+ (void)make;
+ (void)model;
+ gsr_wayland_output *gsr_output = data;
+ gsr_output->pos.x = x;
+ gsr_output->pos.y = y;
+ gsr_output->transform = transform;
+}
+
+static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
+ (void)wl_output;
+ (void)flags;
+ (void)refresh;
+ gsr_wayland_output *gsr_output = data;
+ gsr_output->size.x = width;
+ gsr_output->size.y = height;
+}
+
+static void output_handle_done(void *data, struct wl_output *wl_output) {
+ (void)data;
+ (void)wl_output;
+}
+
+static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) {
+ (void)data;
+ (void)wl_output;
+ (void)factor;
+}
+
+static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) {
+ (void)wl_output;
+ gsr_wayland_output *gsr_output = data;
+ if(gsr_output->name) {
+ free(gsr_output->name);
+ gsr_output->name = NULL;
+ }
+ gsr_output->name = strdup(name);
+}
+
+static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) {
+ (void)data;
+ (void)wl_output;
+ (void)description;
+}
+
+static const struct wl_output_listener output_listener = {
+ .geometry = output_handle_geometry,
+ .mode = output_handle_mode,
+ .done = output_handle_done,
+ .scale = output_handle_scale,
+ .name = output_handle_name,
+ .description = output_handle_description,
+};
+
+static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
+ (void)version;
+ gsr_egl *egl = data;
+ if (strcmp(interface, "wl_compositor") == 0) {
+ if(egl->wayland.compositor) {
+ wl_compositor_destroy(egl->wayland.compositor);
+ egl->wayland.compositor = NULL;
+ }
+ egl->wayland.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
+ } else if(strcmp(interface, wl_output_interface.name) == 0) {
+ if(version < 4) {
+ fprintf(stderr, "gsr warning: wl output interface version is < 4, expected >= 4 to capture a monitor. Using KMS capture instead\n");
+ return;
+ }
+
+ if(egl->wayland.num_outputs == GSR_MAX_OUTPUTS) {
+ fprintf(stderr, "gsr warning: reached maximum outputs (32), ignoring output %u\n", name);
+ return;
+ }
+
+ gsr_wayland_output *gsr_output = &egl->wayland.outputs[egl->wayland.num_outputs];
+ egl->wayland.num_outputs++;
+ *gsr_output = (gsr_wayland_output) {
+ .wl_name = name,
+ .output = wl_registry_bind(registry, name, &wl_output_interface, 4),
+ .pos = { .x = 0, .y = 0 },
+ .size = { .x = 0, .y = 0 },
+ .transform = 0,
+ .name = NULL,
+ };
+ wl_output_add_listener(gsr_output->output, &output_listener, gsr_output);
+ }
+}
-static bool gsr_egl_create_window(gsr_egl *self) {
+static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
+ (void)data;
+ (void)registry;
+ (void)name;
+}
+
+static struct wl_registry_listener registry_listener = {
+ .global = registry_add_object,
+ .global_remove = registry_remove_object,
+};
+
+static void reset_cap_nice(void) {
+ cap_t caps = cap_get_proc();
+ if(!caps)
+ return;
+
+ const cap_value_t cap_to_remove = CAP_SYS_NICE;
+ cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap_to_remove, CAP_CLEAR);
+ cap_set_flag(caps, CAP_PERMITTED, 1, &cap_to_remove, CAP_CLEAR);
+ cap_set_proc(caps);
+ cap_free(caps);
+}
+
+#define GLX_DRAWABLE_TYPE 0x8010
+#define GLX_RENDER_TYPE 0x8011
+#define GLX_RGBA_BIT 0x00000001
+#define GLX_WINDOW_BIT 0x00000001
+#define GLX_PIXMAP_BIT 0x00000002
+#define GLX_BIND_TO_TEXTURE_RGBA_EXT 0x20D1
+#define GLX_BIND_TO_TEXTURE_TARGETS_EXT 0x20D3
+#define GLX_TEXTURE_2D_BIT_EXT 0x00000002
+#define GLX_DOUBLEBUFFER 5
+#define GLX_RED_SIZE 8
+#define GLX_GREEN_SIZE 9
+#define GLX_BLUE_SIZE 10
+#define GLX_ALPHA_SIZE 11
+#define GLX_DEPTH_SIZE 12
+#define GLX_RGBA_TYPE 0x8014
+
+#define GLX_CONTEXT_PRIORITY_LEVEL_EXT 0x3100
+#define GLX_CONTEXT_PRIORITY_HIGH_EXT 0x3101
+#define GLX_CONTEXT_PRIORITY_MEDIUM_EXT 0x3102
+#define GLX_CONTEXT_PRIORITY_LOW_EXT 0x3103
+
+static GLXFBConfig glx_fb_config_choose(gsr_egl *self) {
+ const int glx_visual_attribs[] = {
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
+ // TODO:
+ //GLX_BIND_TO_TEXTURE_RGBA_EXT, 1,
+ //GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
+ GLX_DOUBLEBUFFER, True,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, 0,
+ GLX_DEPTH_SIZE, 0,
+ None, None
+ };
+
+ // TODO: Cleanup
+ int c = 0;
+ GLXFBConfig *fb_configs = self->glXChooseFBConfig(self->x11.dpy, DefaultScreen(self->x11.dpy), glx_visual_attribs, &c);
+ if(c == 0 || !fb_configs)
+ return NULL;
+
+ return fb_configs[0];
+}
+
+// TODO: Create egl context without surface (in other words, x11/wayland agnostic, doesn't require x11/wayland dependency)
+static bool gsr_egl_create_window(gsr_egl *self, bool wayland) {
EGLConfig ecfg;
int32_t num_config = 0;
- EGLDisplay egl_display = NULL;
- EGLSurface egl_surface = NULL;
- EGLContext egl_context = NULL;
- Window window = None;
-
- int32_t attr[] = {
+
+ const int32_t attr[] = {
EGL_BUFFER_SIZE, 24,
- EGL_RENDERABLE_TYPE,
- EGL_OPENGL_ES2_BIT,
- EGL_NONE
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+ EGL_NONE, EGL_NONE
};
- int32_t ctxattr[] = {
+ const int32_t ctxattr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE
+ EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, /* requires cap_sys_nice, ignored otherwise */
+ EGL_NONE, EGL_NONE
};
- window = XCreateWindow(self->dpy, DefaultRootWindow(self->dpy), 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
-
- if(!window) {
- fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n");
- goto fail;
+ if(wayland) {
+ self->wayland.dpy = wl_display_connect(NULL);
+ if(!self->wayland.dpy) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: wl_display_connect failed\n");
+ goto fail;
+ }
+
+ self->wayland.registry = wl_display_get_registry(self->wayland.dpy); // TODO: Error checking
+ wl_registry_add_listener(self->wayland.registry, &registry_listener, self); // TODO: Error checking
+
+ // Fetch globals
+ wl_display_roundtrip(self->wayland.dpy);
+
+ // Fetch wl_output
+ wl_display_roundtrip(self->wayland.dpy);
+
+ if(!self->wayland.compositor) {
+ fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to find compositor\n");
+ goto fail;
+ }
+ } else {
+ self->x11.window = XCreateWindow(self->x11.dpy, DefaultRootWindow(self->x11.dpy), 0, 0, 16, 16, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
+
+ if(!self->x11.window) {
+ fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n");
+ goto fail;
+ }
}
- egl_display = self->eglGetDisplay(self->dpy);
- if(!egl_display) {
+ self->eglBindAPI(EGL_OPENGL_API);
+
+ self->egl_display = self->eglGetDisplay(self->wayland.dpy ? (EGLNativeDisplayType)self->wayland.dpy : (EGLNativeDisplayType)self->x11.dpy);
+ if(!self->egl_display) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglGetDisplay failed\n");
goto fail;
}
- if(!self->eglInitialize(egl_display, NULL, NULL)) {
+ if(!self->eglInitialize(self->egl_display, NULL, NULL)) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglInitialize failed\n");
goto fail;
}
- if(!self->eglChooseConfig(egl_display, attr, &ecfg, 1, &num_config) || num_config != 1) {
+ if(!self->eglChooseConfig(self->egl_display, attr, &ecfg, 1, &num_config) || num_config != 1) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to find a matching config\n");
goto fail;
}
- egl_surface = self->eglCreateWindowSurface(egl_display, ecfg, (EGLNativeWindowType)window, NULL);
- if(!egl_surface) {
+ self->egl_context = self->eglCreateContext(self->egl_display, ecfg, NULL, ctxattr);
+ if(!self->egl_context) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create egl context\n");
+ goto fail;
+ }
+
+ if(wayland) {
+ self->wayland.surface = wl_compositor_create_surface(self->wayland.compositor);
+ self->wayland.window = wl_egl_window_create(self->wayland.surface, 16, 16);
+ self->egl_surface = self->eglCreateWindowSurface(self->egl_display, ecfg, (EGLNativeWindowType)self->wayland.window, NULL);
+ } else {
+ self->egl_surface = self->eglCreateWindowSurface(self->egl_display, ecfg, (EGLNativeWindowType)self->x11.window, NULL);
+ }
+
+ if(!self->egl_surface) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create window surface\n");
goto fail;
}
-
- egl_context = self->eglCreateContext(egl_display, ecfg, NULL, ctxattr);
- if(!egl_context) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create egl context\n");
+
+ if(!self->eglMakeCurrent(self->egl_display, self->egl_surface, self->egl_surface, self->egl_context)) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to make egl context current\n");
goto fail;
}
- if(!self->eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context)) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to make context current\n");
+ reset_cap_nice();
+ return true;
+
+ fail:
+ reset_cap_nice();
+ gsr_egl_unload(self);
+ return false;
+}
+
+static bool gsr_egl_switch_to_glx_context(gsr_egl *self) {
+ // TODO: Cleanup
+
+ if(self->egl_context) {
+ self->eglMakeCurrent(self->egl_display, NULL, NULL, NULL);
+ self->eglDestroyContext(self->egl_display, self->egl_context);
+ self->egl_context = NULL;
+ }
+
+ if(self->egl_surface) {
+ self->eglDestroySurface(self->egl_display, self->egl_surface);
+ self->egl_surface = NULL;
+ }
+
+ if(self->egl_display) {
+ self->eglTerminate(self->egl_display);
+ self->egl_display = NULL;
+ }
+
+ self->glx_fb_config = glx_fb_config_choose(self);
+ if(!self->glx_fb_config) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to find a suitable fb config\n");
+ goto fail;
+ }
+
+ // TODO:
+ //self->glx_context = self->glXCreateContextAttribsARB(self->x11.dpy, self->glx_fb_config, NULL, True, context_attrib_list);
+ self->glx_context = self->glXCreateNewContext(self->x11.dpy, self->glx_fb_config, GLX_RGBA_TYPE, NULL, True);
+ if(!self->glx_context) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create glx context\n");
+ goto fail;
+ }
+
+ if(!self->glXMakeContextCurrent(self->x11.dpy, self->x11.window, self->x11.window, self->glx_context)) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to make glx context current\n");
goto fail;
}
- self->egl_display = egl_display;
- self->egl_surface = egl_surface;
- self->egl_context = egl_context;
- self->window = window;
return true;
fail:
- if(egl_context)
- self->eglDestroyContext(egl_display, egl_context);
- if(egl_surface)
- self->eglDestroySurface(egl_display, egl_surface);
- if(egl_display)
- self->eglTerminate(egl_display);
- if(window)
- XDestroyWindow(self->dpy, window);
+ if(self->glx_context) {
+ self->glXMakeContextCurrent(self->x11.dpy, None, None, NULL);
+ self->glXDestroyContext(self->x11.dpy, self->glx_context);
+ self->glx_context = NULL;
+ self->glx_fb_config = NULL;
+ }
return false;
}
@@ -98,6 +343,7 @@ static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
{ (void**)&self->eglDestroyImage, "eglDestroyImage" },
{ (void**)&self->eglSwapInterval, "eglSwapInterval" },
{ (void**)&self->eglSwapBuffers, "eglSwapBuffers" },
+ { (void**)&self->eglBindAPI, "eglBindAPI" },
{ (void**)&self->eglGetProcAddress, "eglGetProcAddress" },
{ NULL, NULL }
@@ -115,6 +361,8 @@ static bool gsr_egl_proc_load_egl(gsr_egl *self) {
self->eglExportDMABUFImageQueryMESA = (FUNC_eglExportDMABUFImageQueryMESA)self->eglGetProcAddress("eglExportDMABUFImageQueryMESA");
self->eglExportDMABUFImageMESA = (FUNC_eglExportDMABUFImageMESA)self->eglGetProcAddress("eglExportDMABUFImageMESA");
self->glEGLImageTargetTexture2DOES = (FUNC_glEGLImageTargetTexture2DOES)self->eglGetProcAddress("glEGLImageTargetTexture2DOES");
+ self->eglQueryDisplayAttribEXT = (FUNC_eglQueryDisplayAttribEXT)self->eglGetProcAddress("eglQueryDisplayAttribEXT");
+ self->eglQueryDeviceStringEXT = (FUNC_eglQueryDeviceStringEXT)self->eglGetProcAddress("eglQueryDeviceStringEXT");
if(!self->glEGLImageTargetTexture2DOES) {
fprintf(stderr, "gsr error: gsr_egl_load failed: could not find glEGLImageTargetTexture2DOES\n");
@@ -124,16 +372,49 @@ static bool gsr_egl_proc_load_egl(gsr_egl *self) {
return true;
}
+static bool gsr_egl_load_glx(gsr_egl *self, void *library) {
+ dlsym_assign required_dlsym[] = {
+ { (void**)&self->glXGetProcAddress, "glXGetProcAddress" },
+ { (void**)&self->glXChooseFBConfig, "glXChooseFBConfig" },
+ { (void**)&self->glXMakeContextCurrent, "glXMakeContextCurrent" },
+ { (void**)&self->glXCreateNewContext, "glXCreateNewContext" },
+ { (void**)&self->glXDestroyContext, "glXDestroyContext" },
+ { (void**)&self->glXSwapBuffers, "glXSwapBuffers" },
+
+ { NULL, NULL }
+ };
+
+ if(!dlsym_load_list(library, required_dlsym)) {
+ fprintf(stderr, "gsr error: gsr_egl_load failed: missing required symbols in libGLX.so.0\n");
+ return false;
+ }
+
+ self->glXCreateContextAttribsARB = (FUNC_glXCreateContextAttribsARB)self->glXGetProcAddress((const unsigned char*)"glXCreateContextAttribsARB");
+ if(!self->glXCreateContextAttribsARB) {
+ fprintf(stderr, "gsr error: gsr_egl_load_glx failed: could not find glXCreateContextAttribsARB\n");
+ return false;
+ }
+
+ self->glXSwapIntervalEXT = (FUNC_glXSwapIntervalEXT)self->glXGetProcAddress((const unsigned char*)"glXSwapIntervalEXT");
+ self->glXSwapIntervalMESA = (FUNC_glXSwapIntervalMESA)self->glXGetProcAddress((const unsigned char*)"glXSwapIntervalMESA");
+ self->glXSwapIntervalSGI = (FUNC_glXSwapIntervalSGI)self->glXGetProcAddress((const unsigned char*)"glXSwapIntervalSGI");
+
+ return true;
+}
+
static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
dlsym_assign required_dlsym[] = {
{ (void**)&self->glGetError, "glGetError" },
{ (void**)&self->glGetString, "glGetString" },
+ { (void**)&self->glFlush, "glFlush" },
+ { (void**)&self->glFinish, "glFinish" },
{ (void**)&self->glClear, "glClear" },
{ (void**)&self->glClearColor, "glClearColor" },
{ (void**)&self->glGenTextures, "glGenTextures" },
{ (void**)&self->glDeleteTextures, "glDeleteTextures" },
{ (void**)&self->glBindTexture, "glBindTexture" },
{ (void**)&self->glTexParameteri, "glTexParameteri" },
+ { (void**)&self->glTexParameteriv, "glTexParameteriv" },
{ (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" },
{ (void**)&self->glTexImage2D, "glTexImage2D" },
{ (void**)&self->glCopyImageSubData, "glCopyImageSubData" },
@@ -174,6 +455,8 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glBlendFunc, "glBlendFunc" },
{ (void**)&self->glGetUniformLocation, "glGetUniformLocation" },
{ (void**)&self->glUniform1f, "glUniform1f" },
+ { (void**)&self->glUniform2f, "glUniform2f" },
+ { (void**)&self->glDebugMessageCallback, "glDebugMessageCallback" },
{ NULL, NULL }
};
@@ -186,56 +469,97 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
return true;
}
-bool gsr_egl_load(gsr_egl *self, Display *dpy) {
+// #define GL_DEBUG_TYPE_ERROR 0x824C
+// static void debug_callback( unsigned int source,
+// unsigned int type,
+// unsigned int id,
+// unsigned int severity,
+// int length,
+// const char* message,
+// const void* userParam )
+// {
+// (void)source;
+// (void)id;
+// (void)length;
+// (void)userParam;
+// fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+// ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ),
+// type, severity, message );
+// }
+
+bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland, bool is_monitor_capture) {
+ (void)is_monitor_capture;
memset(self, 0, sizeof(gsr_egl));
- self->dpy = dpy;
-
- void *egl_lib = NULL;
- void *gl_lib = NULL;
+ self->x11.dpy = dpy;
+ self->context_type = GSR_GL_CONTEXT_TYPE_EGL;
dlerror(); /* clear */
- egl_lib = dlopen("libEGL.so.1", RTLD_LAZY);
- if(!egl_lib) {
+ self->egl_library = dlopen("libEGL.so.1", RTLD_LAZY);
+ if(!self->egl_library) {
fprintf(stderr, "gsr error: gsr_egl_load: failed to load libEGL.so.1, error: %s\n", dlerror());
goto fail;
}
- gl_lib = dlopen("libGL.so.1", RTLD_LAZY);
- if(!egl_lib) {
+ self->glx_library = dlopen("libGLX.so.0", RTLD_LAZY);
+ if(!self->glx_library) {
+ fprintf(stderr, "gsr error: gsr_egl_load: failed to load libGLX.so.0, error: %s\n", dlerror());
+ goto fail;
+ }
+
+ self->gl_library = dlopen("libGL.so.1", RTLD_LAZY);
+ if(!self->egl_library) {
fprintf(stderr, "gsr error: gsr_egl_load: failed to load libGL.so.1, error: %s\n", dlerror());
goto fail;
}
- if(!gsr_egl_load_egl(self, egl_lib))
+ if(!gsr_egl_load_egl(self, self->egl_library))
+ goto fail;
+
+ if(!gsr_egl_load_glx(self, self->glx_library))
goto fail;
- if(!gsr_egl_load_gl(self, gl_lib))
+ if(!gsr_egl_load_gl(self, self->gl_library))
goto fail;
if(!gsr_egl_proc_load_egl(self))
goto fail;
- if(!gsr_egl_create_window(self))
+ if(!gsr_egl_create_window(self, wayland))
+ goto fail;
+
+ if(!gl_get_gpu_info(self, &self->gpu_info))
goto fail;
+ if(self->eglQueryDisplayAttribEXT && self->eglQueryDeviceStringEXT) {
+ intptr_t device = 0;
+ if(self->eglQueryDisplayAttribEXT(self->egl_display, EGL_DEVICE_EXT, &device) && device)
+ self->dri_card_path = self->eglQueryDeviceStringEXT((void*)device, EGL_DRM_DEVICE_FILE_EXT);
+ }
+
+ /* Nvfbc requires glx */
+ if(!wayland && is_monitor_capture && self->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA) {
+ self->context_type = GSR_GL_CONTEXT_TYPE_GLX;
+ self->dri_card_path = NULL;
+ if(!gsr_egl_switch_to_glx_context(self))
+ goto fail;
+ }
+
self->glEnable(GL_BLEND);
self->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- self->egl_library = egl_lib;
- self->gl_library = gl_lib;
+ //self->glEnable(GL_DEBUG_OUTPUT);
+ //self->glDebugMessageCallback(debug_callback, NULL);
+
return true;
fail:
- if(egl_lib)
- dlclose(egl_lib);
- if(gl_lib)
- dlclose(gl_lib);
- memset(self, 0, sizeof(gsr_egl));
+ gsr_egl_unload(self);
return false;
}
void gsr_egl_unload(gsr_egl *self) {
if(self->egl_context) {
+ self->eglMakeCurrent(self->egl_display, NULL, NULL, NULL);
self->eglDestroyContext(self->egl_display, self->egl_context);
self->egl_context = NULL;
}
@@ -250,9 +574,54 @@ void gsr_egl_unload(gsr_egl *self) {
self->egl_display = NULL;
}
- if(self->window) {
- XDestroyWindow(self->dpy, self->window);
- self->window = None;
+ if(self->glx_context) {
+ self->glXMakeContextCurrent(self->x11.dpy, None, None, NULL);
+ self->glXDestroyContext(self->x11.dpy, self->glx_context);
+ self->glx_context = NULL;
+ self->glx_fb_config = NULL;
+ }
+
+ if(self->x11.window) {
+ XDestroyWindow(self->x11.dpy, self->x11.window);
+ self->x11.window = None;
+ }
+
+ if(self->wayland.window) {
+ wl_egl_window_destroy(self->wayland.window);
+ self->wayland.window = NULL;
+ }
+
+ if(self->wayland.surface) {
+ wl_surface_destroy(self->wayland.surface);
+ self->wayland.surface = NULL;
+ }
+
+ for(int i = 0; i < self->wayland.num_outputs; ++i) {
+ if(self->wayland.outputs[i].output) {
+ wl_output_destroy(self->wayland.outputs[i].output);
+ self->wayland.outputs[i].output = NULL;
+ }
+
+ if(self->wayland.outputs[i].name) {
+ free(self->wayland.outputs[i].name);
+ self->wayland.outputs[i].name = NULL;
+ }
+ }
+ self->wayland.num_outputs = 0;
+
+ if(self->wayland.compositor) {
+ wl_compositor_destroy(self->wayland.compositor);
+ self->wayland.compositor = NULL;
+ }
+
+ if(self->wayland.registry) {
+ wl_registry_destroy(self->wayland.registry);
+ self->wayland.registry = NULL;
+ }
+
+ if(self->wayland.dpy) {
+ wl_display_disconnect(self->wayland.dpy);
+ self->wayland.dpy = NULL;
}
if(self->egl_library) {
@@ -260,6 +629,11 @@ void gsr_egl_unload(gsr_egl *self) {
self->egl_library = NULL;
}
+ if(self->glx_library) {
+ dlclose(self->glx_library);
+ self->glx_library = NULL;
+ }
+
if(self->gl_library) {
dlclose(self->gl_library);
self->gl_library = NULL;
@@ -267,3 +641,11 @@ void gsr_egl_unload(gsr_egl *self) {
memset(self, 0, sizeof(gsr_egl));
}
+
+void gsr_egl_update(gsr_egl *self) {
+ if(!self->wayland.dpy)
+ return;
+
+ // TODO: pselect on wl_display_get_fd before doing dispatch
+ wl_display_dispatch(self->wayland.dpy);
+}
diff --git a/src/main.cpp b/src/main.cpp
index 48a4891..d00a9be 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3,8 +3,10 @@ extern "C" {
#include "../include/capture/xcomposite_cuda.h"
#include "../include/capture/xcomposite_vaapi.h"
#include "../include/capture/kms_vaapi.h"
+#include "../include/capture/kms_cuda.h"
#include "../include/egl.h"
#include "../include/utils.h"
+#include "../include/color_conversion.h"
}
#include <assert.h>
@@ -18,6 +20,9 @@ extern "C" {
#include <map>
#include <signal.h>
#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <libgen.h>
#include "../include/sound.hpp"
@@ -45,10 +50,19 @@ static const int VIDEO_STREAM_INDEX = 0;
static thread_local char av_error_buffer[AV_ERROR_MAX_STRING_SIZE];
-static void monitor_output_callback_print(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata) {
- (void)mode_info;
+static void monitor_output_callback_print(const gsr_monitor *monitor, void *userdata) {
(void)userdata;
- fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", output_info->nameLen, output_info->name, (int)crt_info->width, (int)crt_info->height, crt_info->x, crt_info->y);
+ fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor->size.x, monitor->size.y, monitor->pos.x, monitor->pos.y);
+}
+
+typedef struct {
+ const char *output_name;
+} FirstOutputCallback;
+
+static void get_first_output(const gsr_monitor *monitor, void *userdata) {
+ FirstOutputCallback *first_output = (FirstOutputCallback*)userdata;
+ if(!first_output->output_name)
+ first_output->output_name = strndup(monitor->name, monitor->name_len + 1);
}
static char* av_error_to_string(int err) {
@@ -66,7 +80,10 @@ enum class VideoQuality {
enum class VideoCodec {
H264,
- H265
+ HEVC,
+ HEVC_HDR,
+ AV1,
+ AV1_HDR
};
enum class AudioCodec {
@@ -93,16 +110,38 @@ static int x11_io_error_handler(Display*) {
return 0;
}
+static bool video_codec_is_hdr(VideoCodec video_codec) {
+ switch(video_codec) {
+ case VideoCodec::HEVC_HDR:
+ case VideoCodec::AV1_HDR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+struct PacketData {
+ PacketData() {}
+ PacketData(const PacketData&) = delete;
+ PacketData& operator=(const PacketData&) = delete;
+
+ ~PacketData() {
+ av_free(data.data);
+ }
+
+ AVPacket data;
+};
+
// |stream| is only required for non-replay mode
static void receive_frames(AVCodecContext *av_codec_context, int stream_index, AVStream *stream, int64_t pts,
AVFormatContext *av_format_context,
double replay_start_time,
- std::deque<AVPacket> &frame_data_queue,
+ std::deque<std::shared_ptr<PacketData>> &frame_data_queue,
int replay_buffer_size_secs,
bool &frames_erased,
- std::mutex &write_output_mutex) {
+ std::mutex &write_output_mutex,
+ double paused_time_offset) {
for (;;) {
- // TODO: Use av_packet_alloc instead because sizeof(av_packet) might not be future proof(?)
AVPacket *av_packet = av_packet_alloc();
if(!av_packet)
break;
@@ -117,14 +156,20 @@ static void receive_frames(AVCodecContext *av_codec_context, int stream_index, A
std::lock_guard<std::mutex> lock(write_output_mutex);
if(replay_buffer_size_secs != -1) {
- double time_now = clock_get_monotonic_seconds();
+ // TODO: Preallocate all frames data and use those instead.
+ // Why are we doing this you ask? there is a new ffmpeg bug that causes cpu usage to increase over time when you have
+ // packets that are not being free'd until later. So we copy the packet data, free the packet and then reconstruct
+ // the packet later on when we need it, to keep packets alive only for a short period.
+ auto new_packet = std::make_shared<PacketData>();
+ new_packet->data = *av_packet;
+ new_packet->data.data = (uint8_t*)av_malloc(av_packet->size);
+ memcpy(new_packet->data.data, av_packet->data, av_packet->size);
+
+ double time_now = clock_get_monotonic_seconds() - paused_time_offset;
double replay_time_elapsed = time_now - replay_start_time;
- AVPacket new_pack;
- av_packet_move_ref(&new_pack, av_packet);
- frame_data_queue.push_back(std::move(new_pack));
+ frame_data_queue.push_back(std::move(new_packet));
if(replay_time_elapsed >= replay_buffer_size_secs) {
- av_packet_unref(&frame_data_queue.front());
frame_data_queue.pop_front();
frames_erased = true;
}
@@ -132,22 +177,25 @@ static void receive_frames(AVCodecContext *av_codec_context, int stream_index, A
av_packet_rescale_ts(av_packet, av_codec_context->time_base, stream->time_base);
av_packet->stream_index = stream->index;
// TODO: Is av_interleaved_write_frame needed?
- int ret = av_interleaved_write_frame(av_format_context, av_packet);
+ int ret = av_write_frame(av_format_context, av_packet);
if(ret < 0) {
fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", av_packet->stream_index, av_error_to_string(ret), ret);
}
}
+ av_packet_free(&av_packet);
} else if (res == AVERROR(EAGAIN)) { // we have no packet
// fprintf(stderr, "No packet!\n");
+ av_packet_free(&av_packet);
break;
} else if (res == AVERROR_EOF) { // this is the end of the stream
+ av_packet_free(&av_packet);
fprintf(stderr, "End of stream!\n");
break;
} else {
+ av_packet_free(&av_packet);
fprintf(stderr, "Unexpected error: %d\n", res);
break;
}
- av_packet_free(&av_packet);
}
}
@@ -171,7 +219,7 @@ static AVCodecID audio_codec_get_id(AudioCodec audio_codec) {
return AV_CODEC_ID_AAC;
}
-static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, const AVCodec *codec) {
+static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, const AVCodec *codec, bool mix_audio) {
switch(audio_codec) {
case AudioCodec::AAC: {
return AV_SAMPLE_FMT_FLTP;
@@ -188,8 +236,14 @@ static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, cons
}
}
+ // Amix only works with float audio
+ if(mix_audio)
+ supports_s16 = false;
+
if(!supports_s16 && !supports_flt) {
- fprintf(stderr, "Warning: opus audio codec is chosen but your ffmpeg version does not support s16/flt sample format and performance might be slightly worse. You can either rebuild ffmpeg with libopus instead of the built-in opus, use the flatpak version of gpu screen recorder or record with flac audio codec instead (-ac flac). Falling back to fltp audio sample format instead.\n");
+ fprintf(stderr, "Warning: opus audio codec is chosen but your ffmpeg version does not support s16/flt sample format and performance might be slightly worse.\n");
+ fprintf(stderr, " You can either rebuild ffmpeg with libopus instead of the built-in opus, use the flatpak version of gpu screen recorder or record with aac audio codec instead (-ac aac).\n");
+ fprintf(stderr, " Falling back to fltp audio sample format instead.\n");
}
if(supports_s16)
@@ -209,12 +263,12 @@ static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, cons
static int64_t audio_codec_get_get_bitrate(AudioCodec audio_codec) {
switch(audio_codec) {
- case AudioCodec::AAC: return 128000;
- case AudioCodec::OPUS: return 96000;
- case AudioCodec::FLAC: return 96000;
+ case AudioCodec::AAC: return 160000;
+ case AudioCodec::OPUS: return 128000;
+ case AudioCodec::FLAC: return 128000;
}
assert(false);
- return 96000;
+ return 128000;
}
static AudioFormat audio_codec_context_get_audio_format(const AVCodecContext *audio_codec_context) {
@@ -237,7 +291,8 @@ static AVSampleFormat audio_format_to_sample_format(const AudioFormat audio_form
return AV_SAMPLE_FMT_S16;
}
-static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_codec) {
+static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_codec, bool mix_audio, int audio_bitrate) {
+ (void)fps;
const AVCodec *codec = avcodec_find_encoder(audio_codec_get_id(audio_codec));
if (!codec) {
fprintf(stderr, "Error: Could not find %s audio encoder\n", audio_codec_get_name(audio_codec));
@@ -248,8 +303,8 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
assert(codec->type == AVMEDIA_TYPE_AUDIO);
codec_context->codec_id = codec->id;
- codec_context->sample_fmt = audio_codec_get_sample_format(audio_codec, codec);
- codec_context->bit_rate = audio_codec_get_get_bitrate(audio_codec);
+ codec_context->sample_fmt = audio_codec_get_sample_format(audio_codec, codec, mix_audio);
+ codec_context->bit_rate = audio_bitrate == 0 ? audio_codec_get_get_bitrate(audio_codec) : audio_bitrate;
codec_context->sample_rate = 48000;
if(audio_codec == AudioCodec::AAC)
codec_context->profile = FF_PROFILE_AAC_LOW;
@@ -262,8 +317,7 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
codec_context->time_base.num = 1;
codec_context->time_base.den = AV_TIME_BASE;
- codec_context->framerate.num = fps;
- codec_context->framerate.den = 1;
+ codec_context->thread_count = 1;
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
return codec_context;
@@ -271,7 +325,8 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
VideoQuality video_quality,
- int fps, const AVCodec *codec, bool is_livestream, gsr_gpu_vendor vendor, FramerateMode framerate_mode) {
+ int fps, const AVCodec *codec, bool is_livestream, gsr_gpu_vendor vendor, FramerateMode framerate_mode,
+ bool hdr, gsr_color_range color_range) {
AVCodecContext *codec_context = avcodec_alloc_context3(codec);
@@ -289,7 +344,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->framerate.den = 1;
codec_context->sample_aspect_ratio.num = 0;
codec_context->sample_aspect_ratio.den = 0;
- // High values reeduce file size but increases time it takes to seek
+ // High values reduce file size but increases time it takes to seek
if(is_livestream) {
codec_context->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
codec_context->flags2 |= AV_CODEC_FLAG2_FAST;
@@ -301,10 +356,16 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
}
codec_context->max_b_frames = 0;
codec_context->pix_fmt = pix_fmt;
- //codec_context->color_range = AVCOL_RANGE_JPEG; // TODO: Amd/nvidia?
- //codec_context->color_primaries = AVCOL_PRI_BT709;
- //codec_context->color_trc = AVCOL_TRC_BT709;
- //codec_context->colorspace = AVCOL_SPC_BT709;
+ codec_context->color_range = color_range == GSR_COLOR_RANGE_LIMITED ? AVCOL_RANGE_MPEG : AVCOL_RANGE_JPEG;
+ if(hdr) {
+ codec_context->color_primaries = AVCOL_PRI_BT2020;
+ codec_context->color_trc = AVCOL_TRC_SMPTE2084;
+ codec_context->colorspace = AVCOL_SPC_BT2020_NCL;
+ } else {
+ codec_context->color_primaries = AVCOL_PRI_BT709;
+ codec_context->color_trc = AVCOL_TRC_BT709;
+ codec_context->colorspace = AVCOL_SPC_BT709;
+ }
//codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER;
if(codec->id == AV_CODEC_ID_HEVC)
codec_context->codec_tag = MKTAG('h', 'v', 'c', '1');
@@ -353,6 +414,23 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->bit_rate = 0;
#endif
+ if(vendor != GSR_GPU_VENDOR_NVIDIA) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ codec_context->global_quality = 180;
+ break;
+ case VideoQuality::HIGH:
+ codec_context->global_quality = 140;
+ break;
+ case VideoQuality::VERY_HIGH:
+ codec_context->global_quality = 120;
+ break;
+ case VideoQuality::ULTRA:
+ codec_context->global_quality = 100;
+ break;
+ }
+ }
+
av_opt_set_int(codec_context->priv_data, "b_ref_mode", 0, 0);
//av_opt_set_int(codec_context->priv_data, "cbr", true, 0);
@@ -364,9 +442,13 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
//codec_context->compression_level = 2;
}
+ //av_opt_set(codec_context->priv_data, "bsf", "hevc_metadata=colour_primaries=9:transfer_characteristics=16:matrix_coefficients=9", 0);
+
//codec_context->rc_max_rate = codec_context->bit_rate;
//codec_context->rc_min_rate = codec_context->bit_rate;
//codec_context->rc_buffer_size = codec_context->bit_rate / 10;
+ // TODO: Do this when not using cqp
+ //codec_context->rc_initial_buffer_occupancy = codec_context->bit_rate * 1000;
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
@@ -374,8 +456,14 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
}
static bool vaapi_create_codec_context(AVCodecContext *video_codec_context, const char *card_path) {
+ char render_path[128];
+ if(!gsr_card_path_get_render_path(card_path, render_path)) {
+ fprintf(stderr, "gsr error: failed to get /dev/dri/renderDXXX file from %s\n", card_path);
+ return false;
+ }
+
AVBufferRef *device_ctx;
- if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, card_path, NULL, 0) < 0) {
+ if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, render_path, NULL, 0) < 0) {
fprintf(stderr, "Error: Failed to create hardware device context\n");
return false;
}
@@ -396,7 +484,7 @@ static bool vaapi_create_codec_context(AVCodecContext *video_codec_context, cons
hw_frame_context->device_ref = device_ctx;
hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
- hw_frame_context->initial_pool_size = 1;
+ //hw_frame_context->initial_pool_size = 1;
if (av_hwframe_ctx_init(frame_context) < 0) {
fprintf(stderr, "Error: Failed to initialize hardware frame context "
@@ -413,7 +501,7 @@ static bool vaapi_create_codec_context(AVCodecContext *video_codec_context, cons
static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gsr_gpu_vendor vendor, const char *card_path) {
// Do not use AV_PIX_FMT_CUDA because we dont want to do full check with hardware context
- AVCodecContext *codec_context = create_video_codec_context(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT);
+ AVCodecContext *codec_context = create_video_codec_context(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT, false, GSR_COLOR_RANGE_LIMITED);
if(!codec_context)
return false;
@@ -473,7 +561,31 @@ static const AVCodec* find_h265_encoder(gsr_gpu_vendor vendor, const char *card_
return checked_success ? codec : nullptr;
}
-static AVFrame* open_audio(AVCodecContext *audio_codec_context) {
+static const AVCodec* find_av1_encoder(gsr_gpu_vendor vendor, const char *card_path) {
+ // Workaround bug with av1 nvidia in older ffmpeg versions that causes the whole application to crash
+ // when avcodec_open2 is opened with av1_nvenc
+ if(vendor == GSR_GPU_VENDOR_NVIDIA && LIBAVCODEC_BUILD < AV_VERSION_INT(60, 30, 100)) {
+ return nullptr;
+ }
+
+ const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "av1_nvenc" : "av1_vaapi");
+ if(!codec)
+ codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "nvenc_av1" : "vaapi_av1");
+
+ if(!codec)
+ return nullptr;
+
+ static bool checked = false;
+ static bool checked_success = true;
+ if(!checked) {
+ checked = true;
+ if(!check_if_codec_valid_for_hardware(codec, vendor, card_path))
+ checked_success = false;
+ }
+ return checked_success ? codec : nullptr;
+}
+
+static void open_audio(AVCodecContext *audio_codec_context) {
AVDictionary *options = nullptr;
av_dict_set(&options, "strict", "experimental", 0);
@@ -483,7 +595,9 @@ static AVFrame* open_audio(AVCodecContext *audio_codec_context) {
fprintf(stderr, "failed to open codec, reason: %s\n", av_error_to_string(ret));
_exit(1);
}
+}
+static AVFrame* create_audio_frame(AVCodecContext *audio_codec_context) {
AVFrame *frame = av_frame_alloc();
if(!frame) {
fprintf(stderr, "failed to allocate audio frame\n");
@@ -500,7 +614,7 @@ static AVFrame* open_audio(AVCodecContext *audio_codec_context) {
av_channel_layout_copy(&frame->ch_layout, &audio_codec_context->ch_layout);
#endif
- ret = av_frame_get_buffer(frame, 0);
+ int ret = av_frame_get_buffer(frame, 0);
if(ret < 0) {
fprintf(stderr, "failed to allocate audio data buffers, reason: %s\n", av_error_to_string(ret));
_exit(1);
@@ -509,23 +623,41 @@ static AVFrame* open_audio(AVCodecContext *audio_codec_context) {
return frame;
}
-static void open_video(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format) {
+static void open_video(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format, bool hdr) {
AVDictionary *options = nullptr;
if(vendor == GSR_GPU_VENDOR_NVIDIA) {
+ // Disable setting preset since some nvidia gpus cant handle it nicely and greatly reduce encoding performance (from more than 60 fps to less than 45 fps) (such as Nvidia RTX A2000)
+ #if 0
bool supports_p4 = false;
- bool supports_p6 = false;
+ bool supports_p5 = false;
const AVOption *opt = nullptr;
while((opt = av_opt_next(codec_context->priv_data, opt))) {
if(opt->type == AV_OPT_TYPE_CONST) {
if(strcmp(opt->name, "p4") == 0)
supports_p4 = true;
- else if(strcmp(opt->name, "p6") == 0)
- supports_p6 = true;
+ else if(strcmp(opt->name, "p5") == 0)
+ supports_p5 = true;
}
}
+ #endif
- if(very_old_gpu) {
+ if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ av_dict_set_int(&options, "qp", 37, 0);
+ break;
+ case VideoQuality::HIGH:
+ av_dict_set_int(&options, "qp", 32, 0);
+ break;
+ case VideoQuality::VERY_HIGH:
+ av_dict_set_int(&options, "qp", 28, 0);
+ break;
+ case VideoQuality::ULTRA:
+ av_dict_set_int(&options, "qp", 24, 0);
+ break;
+ }
+ } else if(very_old_gpu || codec_context->codec_id == AV_CODEC_ID_H264) {
switch(video_quality) {
case VideoQuality::MEDIUM:
av_dict_set_int(&options, "qp", 37, 0);
@@ -543,13 +675,13 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
} else {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 40, 0);
+ av_dict_set_int(&options, "qp", 37, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 35, 0);
+ av_dict_set_int(&options, "qp", 32, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 30, 0);
+ av_dict_set_int(&options, "qp", 28, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(&options, "qp", 24, 0);
@@ -557,7 +689,8 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
}
}
- if(!supports_p4 && !supports_p6)
+ #if 0
+ if(!supports_p4 && !supports_p5)
fprintf(stderr, "Info: your ffmpeg version is outdated. It's recommended that you use the flatpak version of gpu-screen-recorder version instead, which you can find at https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder\n");
//if(is_livestream) {
@@ -571,10 +704,15 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
// older gpus p5-p7 slow the gpu down to a crawl...
// "hq" is now just an alias for p7 in ffmpeg :(
// TODO: Temporary disable because of stuttering?
+
+ // TODO: Preset is set to p5 for now but it should ideally be p6 or p7.
+ // This change is needed because for certain sizes of a window (or monitor?) such as 971x780 causes encoding to freeze
+ // when using h264 codec. This is a new(?) nvidia driver bug.
if(very_old_gpu)
av_dict_set(&options, "preset", supports_p4 ? "p4" : "medium", 0);
else
- av_dict_set(&options, "preset", supports_p6 ? "p6" : "slow", 0);
+ av_dict_set(&options, "preset", supports_p5 ? "p5" : "slow", 0);
+ #endif
av_dict_set(&options, "tune", "hq", 0);
av_dict_set(&options, "rc", "constqp", 0);
@@ -588,24 +726,57 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
av_dict_set(&options, "profile", "high444p", 0);
break;
}
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ switch(pixel_format) {
+ case PixelFormat::YUV420:
+ av_dict_set(&options, "rgb_mode", "yuv420", 0);
+ break;
+ case PixelFormat::YUV444:
+ av_dict_set(&options, "rgb_mode", "yuv444", 0);
+ break;
+ }
} else {
//av_dict_set(&options, "profile", "main10", 0);
//av_dict_set(&options, "pix_fmt", "yuv420p16le", 0);
+ if(hdr) {
+ av_dict_set(&options, "profile", "main10", 0);
+ } else {
+ av_dict_set(&options, "profile", "main", 0);
+ }
}
} else {
- switch(video_quality) {
- case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 32, 0);
- break;
- case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 28, 0);
- break;
- case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 24, 0);
- break;
- case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 18, 0);
- break;
+ if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ // Using global_quality option
+ } else if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ av_dict_set_int(&options, "qp", 34, 0);
+ break;
+ case VideoQuality::HIGH:
+ av_dict_set_int(&options, "qp", 30, 0);
+ break;
+ case VideoQuality::VERY_HIGH:
+ av_dict_set_int(&options, "qp", 26, 0);
+ break;
+ case VideoQuality::ULTRA:
+ av_dict_set_int(&options, "qp", 22, 0);
+ break;
+ }
+ } else {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ av_dict_set_int(&options, "qp", 37, 0);
+ break;
+ case VideoQuality::HIGH:
+ av_dict_set_int(&options, "qp", 32, 0);
+ break;
+ case VideoQuality::VERY_HIGH:
+ av_dict_set_int(&options, "qp", 28, 0);
+ break;
+ case VideoQuality::ULTRA:
+ av_dict_set_int(&options, "qp", 24, 0);
+ break;
+ }
}
// TODO: More quality options
@@ -614,9 +785,17 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
if(codec_context->codec_id == AV_CODEC_ID_H264) {
av_dict_set(&options, "profile", "high", 0);
- av_dict_set_int(&options, "quality", 7, 0);
+ //av_dict_set_int(&options, "quality", 5, 0); // quality preset
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ av_dict_set(&options, "profile", "main", 0); // TODO: use professional instead?
+ av_dict_set(&options, "tier", "main", 0);
} else {
- av_dict_set(&options, "profile", "main", 0);
+ if(hdr) {
+ av_dict_set(&options, "profile", "main10", 0);
+ av_dict_set(&options, "sei", "hdr", 0);
+ } else {
+ av_dict_set(&options, "profile", "main", 0);
+ }
}
}
@@ -634,23 +813,26 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
}
static void usage_header() {
- fprintf(stderr, "usage: gpu-screen-recorder -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|h265] [-ac aac|opus|flac] [-oc yes|no] [-fm cfr|vfr] [-v yes|no] [-h|--help] [-o <output_file>]\n");
+ const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
+ const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
+ fprintf(stderr, "usage: %s -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr] [-cr limited|full] [-v yes|no] [-h|--help] [-o <output_file>] [-mf yes|no] [-sc <script_path>] [-cursor yes|no]\n", program_name);
}
static void usage_full() {
+ const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
+ const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
usage_header();
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS:\n");
- fprintf(stderr, " -w Window to record, a display, \"screen\", \"screen-direct\", \"screen-direct-force\" or \"focused\".\n");
- fprintf(stderr, " The display is the display (monitor) name in xrandr and if \"screen\", \"screen-direct\" or \"screen-direct-force\" is selected then all displays are recorded.\n");
- fprintf(stderr, " If this is \"focused\" then the currently focused window is recorded. When recording the focused window then the -s option has to be used as well.\n");
- fprintf(stderr, " \"screen-direct\"/\"screen-direct-force\" skips one texture copy for fullscreen applications so it may lead to better performance and it works with VRR monitors\n");
- fprintf(stderr, " when recording fullscreen application but may break some applications, such as mpv in fullscreen mode. Direct mode doesn't capture cursor either.\n");
- fprintf(stderr, " \"screen-direct-force\" is not recommended unless you use a VRR monitor because there might be driver issues that cause the video to stutter or record a black screen.\n");
- fprintf(stderr, " On AMD/Intel, capturing a monitor might have better performance than recording a single window.\n");
+ fprintf(stderr, " -w Window id to record, a display (monitor name), \"screen\", \"screen-direct-force\" or \"focused\".\n");
+ fprintf(stderr, " If this is \"screen\" or \"screen-direct-force\" then all monitors are recorded.\n");
+ fprintf(stderr, " \"screen-direct-force\" is not recommended unless you use a VRR (G-SYNC) monitor on Nvidia X11 and you are aware that using this option can cause games to freeze/crash or other issues because of Nvidia driver issues.\n");
+ fprintf(stderr, " \"screen-direct-force\" option is only available on Nvidia X11. VRR works without this option on other systems.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -c Container format for output file, for example mp4, or flv. Only required if no output file is specified or if recording in replay buffer mode.\n");
fprintf(stderr, " If an output file is specified and -c is not used then the container format is determined from the output filename extension.\n");
+ fprintf(stderr, " Only containers that support h264, hevc or av1 are supported, which means that only mp4, mkv, flv (and some others) are supported.\n");
+ fprintf(stderr, " WebM is not supported yet (most hardware doesn't support WebM video encoding).\n");
fprintf(stderr, "\n");
fprintf(stderr, " -s The size (area) to record at in the format WxH, for example 1920x1080. This option is only supported (and required) when -w is \"focused\".\n");
fprintf(stderr, "\n");
@@ -668,33 +850,58 @@ static void usage_full() {
fprintf(stderr, " and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature.\n");
fprintf(stderr, " This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264' or 'h265'. Defaults to 'auto' which defaults to 'h265' unless recording at fps higher than 60. Defaults to 'h264' on intel.\n");
- fprintf(stderr, " Forcefully set to 'h264' if -c is 'flv'.\n");
+ fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'hevc_hdr' or 'av1_hdr'. Defaults to 'auto' which defaults to 'hevc' on AMD/Nvidia and 'h264' on intel.\n");
+ fprintf(stderr, " Forcefully set to 'h264' if the file container type is 'flv'.\n");
+ fprintf(stderr, " Forcefully set to 'hevc' on AMD/intel if video codec is 'h264' and if the file container type is 'mkv'.\n");
+ fprintf(stderr, " 'hevc_hdr' and 'av1_hdr' option is not available on X11.\n");
+ fprintf(stderr, " Note: hdr metadata is not included in the video when recording with 'hevc_hdr'/'av1_hdr' because of bugs in AMD, Intel and NVIDIA drivers (amazin', they are all bugged).\n");
fprintf(stderr, "\n");
fprintf(stderr, " -ac Audio codec to use. Should be either 'aac', 'opus' or 'flac'. Defaults to 'opus' for .mp4/.mkv files, otherwise defaults to 'aac'.\n");
fprintf(stderr, " 'opus' and 'flac' is only supported by .mp4/.mkv files. 'opus' is recommended for best performance and smallest audio size.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -oc Overclock memory transfer rate to the maximum performance level. This only applies to NVIDIA and exists to overcome a bug in NVIDIA driver where performance level\n");
- fprintf(stderr, " is dropped when you record a game. Only needed if you are recording a game that is bottlenecked by GPU.\n");
+ fprintf(stderr, " -ab Audio bitrate to use. Optional, by default the bitrate is 128000 for opus and flac and 160000 for aac.\n");
+ fprintf(stderr, " If this is set to 0 then it's the same as if it's absent, in which case the bitrate is determined automatically depending on the audio codec.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -oc Overclock memory transfer rate to the maximum performance level. This only applies to NVIDIA on X11 and exists to overcome a bug in NVIDIA driver where performance level\n");
+ fprintf(stderr, " is dropped when you record a game. Only needed if you are recording a game that is bottlenecked by GPU. The same issue exists on Wayland but overclocking is not possible on Wayland.\n");
fprintf(stderr, " Works only if your have \"Coolbits\" set to \"12\" in NVIDIA X settings, see README for more information. Note! use at your own risk! Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' or 'vfr'. Defaults to 'cfr' on NVIDIA and 'vfr' on AMD/Intel.\n");
+ fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' or 'vfr'. Defaults to 'vfr'.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Defaults to 'limited'.\n");
+ fprintf(stderr, " Limited color range means that colors are in range 16-235 while full color range means that colors are in range 0-255 (when not recording with hdr).\n");
+ fprintf(stderr, " Note that some buggy video players (such as vlc) are unable to correctly display videos in full color range.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -v Prints per second, fps updates. Optional, set to 'yes' by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -h Show this help.\n");
+ fprintf(stderr, " -h, --help\n");
+ fprintf(stderr, " Show this help.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -mf Organise replays in folders based on the current date.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -sc Run a script on the saved video file (non-blocking). The first argument to the script is the filepath to the saved video file and the second argument is the recording type (either \"regular\" or \"replay\").\n");
+ fprintf(stderr, " Not applicable for live streams.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -cursor\n");
+ fprintf(stderr, " Record cursor. Defaults to 'yes'.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " --list-supported-video-codecs\n");
+ fprintf(stderr, " List supported video codecs and exits. Prints h264, hevc, hevc_hdr, av1 and av1_hdr (if supported).\n");
fprintf(stderr, "\n");
//fprintf(stderr, " -pixfmt The pixel format to use for the output video. yuv420 is the most common format and is best supported, but the color is compressed, so colors can look washed out and certain colors of text can look bad. Use yuv444 for no color compression, but the video may not work everywhere and it may not work with hardware video decoding. Optional, defaults to yuv420\n");
fprintf(stderr, " -o The output file path. If omitted then the encoded data is sent to stdout. Required in replay mode (when using -r).\n");
- fprintf(stderr, " In replay mode this has to be an existing directory instead of a file.\n");
+ fprintf(stderr, " In replay mode this has to be a directory instead of a file.\n");
+ fprintf(stderr, " The directory to the file is created (recursively) if it doesn't already exist.\n");
fprintf(stderr, "\n");
fprintf(stderr, "NOTES:\n");
- fprintf(stderr, " Send signal SIGINT to gpu-screen-recorder (Ctrl+C, or killall gpu-screen-recorder) to stop and save the recording (when not using replay mode).\n");
+ fprintf(stderr, " Send signal SIGINT to gpu-screen-recorder (Ctrl+C, or killall -SIGINT gpu-screen-recorder) to stop and save the recording. When in replay mode this stops recording without saving.\n");
fprintf(stderr, " Send signal SIGUSR1 to gpu-screen-recorder (killall -SIGUSR1 gpu-screen-recorder) to save a replay (when in replay mode).\n");
+ fprintf(stderr, " Send signal SIGUSR2 to gpu-screen-recorder (killall -SIGUSR2 gpu-screen-recorder) to pause/unpause recording. Only applicable and useful when recording (not streaming nor replay).\n");
fprintf(stderr, "\n");
- fprintf(stderr, "EXAMPLES\n");
- fprintf(stderr, " gpu-screen-recorder -w screen -f 60 -a \"$(pactl get-default-sink).monitor\" -o video.mp4\n");
- fprintf(stderr, " gpu-screen-recorder -w screen -f 60 -a \"$(pactl get-default-sink).monitor|$(pactl get-default-source)\" -o video.mp4\n");
+ fprintf(stderr, "EXAMPLES:\n");
+ fprintf(stderr, " %s -w screen -f 60 -a \"$(pactl get-default-sink).monitor\" -o \"$HOME/Videos/video.mp4\"\n", program_name);
+ fprintf(stderr, " %s -w screen -f 60 -a \"$(pactl get-default-sink).monitor|$(pactl get-default-source)\" -o \"$HOME/Videos/video.mp4\"\n", program_name);
+ fprintf(stderr, " %s -w screen -f 60 -a \"$(pactl get-default-sink).monitor\" -c mkv -r 60 -o \"$HOME/Videos\"\n", program_name);
//fprintf(stderr, " gpu-screen-recorder -w screen -f 60 -q ultra -pixfmt yuv444 -o video.mp4\n");
_exit(1);
}
@@ -706,8 +913,9 @@ static void usage() {
static sig_atomic_t running = 1;
static sig_atomic_t save_replay = 0;
+static sig_atomic_t toggle_pause = 0;
-static void int_handler(int) {
+static void stop_handler(int) {
running = 0;
}
@@ -715,37 +923,35 @@ static void save_replay_handler(int) {
save_replay = 1;
}
-struct Arg {
- std::vector<const char*> values;
- bool optional = false;
- bool list = false;
-
- const char* value() const {
- if(values.empty())
- return nullptr;
- return values.front();
- }
-};
+static void toggle_pause_handler(int) {
+ toggle_pause = 1;
+}
static bool is_hex_num(char c) {
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
}
static bool contains_non_hex_number(const char *str) {
+ bool hex_start = false;
size_t len = strlen(str);
if(len >= 2 && memcmp(str, "0x", 2) == 0) {
str += 2;
len -= 2;
+ hex_start = true;
}
+ bool is_hex = false;
for(size_t i = 0; i < len; ++i) {
char c = str[i];
if(c == '\0')
return false;
if(!is_hex_num(c))
return true;
+ if((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
+ is_hex = true;
}
- return false;
+
+ return is_hex && !hex_start;
}
static std::string get_date_str() {
@@ -756,6 +962,22 @@ static std::string get_date_str() {
return str;
}
+static std::string get_date_only_str() {
+ char str[128];
+ time_t now = time(NULL);
+ struct tm *t = localtime(&now);
+ strftime(str, sizeof(str)-1, "%Y-%m-%d", t);
+ return str;
+}
+
+static std::string get_time_only_str() {
+ char str[128];
+ time_t now = time(NULL);
+ struct tm *t = localtime(&now);
+ strftime(str, sizeof(str)-1, "%H-%M-%S", t);
+ return str;
+}
+
static AVStream* create_stream(AVFormatContext *av_format_context, AVCodecContext *codec_context) {
AVStream *stream = avformat_new_stream(av_format_context, nullptr);
if (!stream) {
@@ -768,30 +990,109 @@ static AVStream* create_stream(AVFormatContext *av_format_context, AVCodecContex
return stream;
}
+static void run_recording_saved_script_async(const char *script_file, const char *video_file, const char *type) {
+ char script_file_full[PATH_MAX];
+ script_file_full[0] = '\0';
+ if(!realpath(script_file, script_file_full)) {
+ fprintf(stderr, "Error: script file not found: %s\n", script_file);
+ return;
+ }
+
+ const char *args[6];
+ const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
+
+ if(inside_flatpak) {
+ args[0] = "flatpak-spawn";
+ args[1] = "--host";
+ args[2] = script_file_full;
+ args[3] = video_file;
+ args[4] = type;
+ args[5] = NULL;
+ } else {
+ args[0] = script_file_full;
+ args[1] = video_file;
+ args[2] = type;
+ args[3] = NULL;
+ }
+
+ pid_t pid = fork();
+ if(pid == -1) {
+ perror(script_file_full);
+ return;
+ } else if(pid == 0) { // child
+ setsid();
+ signal(SIGHUP, SIG_IGN);
+
+ pid_t second_child = fork();
+ if(second_child == 0) { // child
+ execvp(args[0], (char* const*)args);
+ perror(script_file_full);
+ _exit(127);
+ } else if(second_child != -1) { // parent
+ _exit(0);
+ }
+ } else { // parent
+ waitpid(pid, NULL, 0);
+ }
+}
+
struct AudioDevice {
SoundDevice sound_device;
AudioInput audio_input;
AVFilterContext *src_filter_ctx = nullptr;
+ AVFrame *frame = nullptr;
std::thread thread; // TODO: Instead of having a thread for each track, have one thread for all threads and read the data with non-blocking read
};
// TODO: Cleanup
struct AudioTrack {
AVCodecContext *codec_context = nullptr;
- AVFrame *frame = nullptr;
AVStream *stream = nullptr;
std::vector<AudioDevice> audio_devices;
AVFilterGraph *graph = nullptr;
AVFilterContext *sink = nullptr;
int stream_index = 0;
+ int64_t pts = 0;
};
static std::future<void> save_replay_thread;
-static std::vector<AVPacket> save_replay_packets;
+static std::vector<std::shared_ptr<PacketData>> save_replay_packets;
static std::string save_replay_output_filepath;
-static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, std::vector<AudioTrack> &audio_tracks, const std::deque<AVPacket> &frame_data_queue, bool frames_erased, std::string output_dir, const char *container_format, const std::string &file_extension, std::mutex &write_output_mutex) {
+static int create_directory_recursive(char *path) {
+ int path_len = strlen(path);
+ char *p = path;
+ char *end = path + path_len;
+ for(;;) {
+ char *slash_p = strchr(p, '/');
+
+ // Skips first '/', we don't want to try and create the root directory
+ if(slash_p == path) {
+ ++p;
+ continue;
+ }
+
+ if(!slash_p)
+ slash_p = end;
+
+ char prev_char = *slash_p;
+ *slash_p = '\0';
+ int err = mkdir(path, S_IRWXU);
+ *slash_p = prev_char;
+
+ if(err == -1 && errno != EEXIST)
+ return err;
+
+ if(slash_p == end)
+ break;
+ else
+ p = slash_p + 1;
+ }
+ return 0;
+}
+
+static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, std::vector<AudioTrack> &audio_tracks, std::deque<std::shared_ptr<PacketData>> &frame_data_queue, bool frames_erased, std::string output_dir, const char *container_format, const std::string &file_extension, std::mutex &write_output_mutex, bool make_folders) {
if(save_replay_thread.valid())
return;
@@ -803,7 +1104,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
std::lock_guard<std::mutex> lock(write_output_mutex);
start_index = (size_t)-1;
for(size_t i = 0; i < frame_data_queue.size(); ++i) {
- const AVPacket &av_packet = frame_data_queue[i];
+ const AVPacket &av_packet = frame_data_queue[i]->data;
if((av_packet.flags & AV_PKT_FLAG_KEY) && av_packet.stream_index == video_stream_index) {
start_index = i;
break;
@@ -814,11 +1115,11 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
return;
if(frames_erased) {
- video_pts_offset = frame_data_queue[start_index].pts;
+ video_pts_offset = frame_data_queue[start_index]->data.pts;
// Find the next audio packet to use as audio pts offset
for(size_t i = start_index; i < frame_data_queue.size(); ++i) {
- const AVPacket &av_packet = frame_data_queue[i];
+ const AVPacket &av_packet = frame_data_queue[i]->data;
if(av_packet.stream_index != video_stream_index) {
audio_pts_offset = av_packet.pts;
break;
@@ -830,11 +1131,19 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
save_replay_packets.resize(frame_data_queue.size());
for(size_t i = 0; i < frame_data_queue.size(); ++i) {
- av_packet_ref(&save_replay_packets[i], &frame_data_queue[i]);
+ save_replay_packets[i] = frame_data_queue[i];
}
}
- save_replay_output_filepath = output_dir + "/Replay_" + get_date_str() + "." + file_extension;
+ if (make_folders) {
+ std::string output_folder = output_dir + '/' + get_date_only_str();
+ create_directory_recursive(&output_folder[0]);
+ save_replay_output_filepath = output_folder + "/Replay_" + get_time_only_str() + "." + file_extension;
+ } else {
+ create_directory_recursive(&output_dir[0]);
+ save_replay_output_filepath = output_dir + "/Replay_" + get_date_str() + "." + file_extension;
+ }
+
save_replay_thread = std::async(std::launch::async, [video_stream_index, container_format, start_index, video_pts_offset, audio_pts_offset, video_codec_context, &audio_tracks]() mutable {
AVFormatContext *av_format_context;
avformat_alloc_output_context2(&av_format_context, nullptr, container_format, nullptr);
@@ -866,7 +1175,16 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
}
for(size_t i = start_index; i < save_replay_packets.size(); ++i) {
- AVPacket &av_packet = save_replay_packets[i];
+ // TODO: Check if successful
+ AVPacket av_packet;
+ memset(&av_packet, 0, sizeof(av_packet));
+ //av_packet_from_data(av_packet, save_replay_packets[i]->data.data, save_replay_packets[i]->data.size);
+ av_packet.data = save_replay_packets[i]->data.data;
+ av_packet.size = save_replay_packets[i]->data.size;
+ av_packet.stream_index = save_replay_packets[i]->data.stream_index;
+ av_packet.pts = save_replay_packets[i]->data.pts;
+ av_packet.dts = save_replay_packets[i]->data.pts;
+ av_packet.flags = save_replay_packets[i]->data.flags;
AVStream *stream = video_stream;
AVCodecContext *codec_context = video_codec_context;
@@ -886,9 +1204,11 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
av_packet.stream_index = stream->index;
av_packet_rescale_ts(&av_packet, codec_context->time_base, stream->time_base);
- int ret = av_interleaved_write_frame(av_format_context, &av_packet);
+ ret = av_write_frame(av_format_context, &av_packet);
if(ret < 0)
fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", stream->index, av_error_to_string(ret), ret);
+
+ //av_packet_free(&av_packet);
}
if (av_write_trailer(av_format_context) != 0)
@@ -949,6 +1269,7 @@ static bool is_livestream_path(const char *str) {
static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph **graph, AVFilterContext **sink, std::vector<AVFilterContext*> &src_filter_ctx, size_t num_sources) {
char ch_layout[64];
int err = 0;
+ ch_layout[0] = '\0';
AVFilterGraph *filter_graph = avfilter_graph_alloc();
if (!filter_graph) {
@@ -1048,9 +1369,11 @@ static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph
return 0;
}
-static void xwayland_check_callback(const XRROutputInfo *output_info, const XRRCrtcInfo*, const XRRModeInfo*, void *userdata) {
+static void xwayland_check_callback(const gsr_monitor *monitor, void *userdata) {
bool *xwayland_found = (bool*)userdata;
- if(output_info->nameLen >= 8 && strncmp(output_info->name, "XWAYLAND", 8) == 0)
+ if(monitor->name_len >= 8 && strncmp(monitor->name, "XWAYLAND", 8) == 0)
+ *xwayland_found = true;
+ else if(memmem(monitor->name, monitor->name_len, "X11", 3))
*xwayland_found = true;
}
@@ -1060,20 +1383,252 @@ static bool is_xwayland(Display *display) {
return true;
bool xwayland_found = false;
- for_each_active_monitor_output(display, xwayland_check_callback, &xwayland_found);
+ for_each_active_monitor_output_x11(display, xwayland_check_callback, &xwayland_found);
return xwayland_found;
}
-struct ReceivePacketData {
- AVCodecContext *codec_context;
- int stream_index;
- AVStream *stream;
- int64_t pts;
+static void list_supported_video_codecs() {
+ bool wayland = false;
+ Display *dpy = XOpenDisplay(nullptr);
+ if (!dpy) {
+ wayland = true;
+ fprintf(stderr, "Warning: failed to connect to the X server. Assuming wayland is running without Xwayland\n");
+ }
+
+ XSetErrorHandler(x11_error_handler);
+ XSetIOErrorHandler(x11_io_error_handler);
+
+ if(!wayland)
+ wayland = is_xwayland(dpy);
+
+ gsr_egl egl;
+ if(!gsr_egl_load(&egl, dpy, wayland, false)) {
+ fprintf(stderr, "gsr error: failed to load opengl\n");
+ _exit(1);
+ }
+
+ char card_path[128];
+ card_path[0] = '\0';
+ if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
+ // TODO: Allow specifying another card, and in other places
+ if(!gsr_get_valid_card_path(&egl, card_path)) {
+ fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it\n");
+ _exit(2);
+ }
+ }
+
+ av_log_set_level(AV_LOG_FATAL);
+
+ // TODO: Output hdr
+ if(find_h264_encoder(egl.gpu_info.vendor, card_path))
+ puts("h264");
+ if(find_h265_encoder(egl.gpu_info.vendor, card_path))
+ puts("hevc");
+ if(find_av1_encoder(egl.gpu_info.vendor, card_path))
+ puts("av1");
+
+ fflush(stdout);
+
+ gsr_egl_unload(&egl);
+ if(dpy)
+ XCloseDisplay(dpy);
+}
+
+static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_gpu_info gpu_inf, gsr_egl &egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor) {
+ vec2i region_size = { 0, 0 };
+ Window src_window_id = None;
+ bool follow_focused = false;
+
+ gsr_capture *capture = nullptr;
+ if(strcmp(window_str, "focused") == 0) {
+ if(wayland) {
+ fprintf(stderr, "Error: GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland\n");
+ _exit(2);
+ }
+
+ if(!screen_region) {
+ fprintf(stderr, "Error: option -s is required when using -w focused\n");
+ usage();
+ }
+
+ if(sscanf(screen_region, "%dx%d", &region_size.x, &region_size.y) != 2) {
+ fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", screen_region);
+ usage();
+ }
+
+ if(region_size.x <= 0 || region_size.y <= 0) {
+ fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater than 0\n", screen_region);
+ usage();
+ }
+
+ follow_focused = true;
+ } else if(contains_non_hex_number(window_str)) {
+ if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
+ if(strcmp(window_str, "screen") == 0) {
+ FirstOutputCallback first_output;
+ first_output.output_name = NULL;
+ for_each_active_monitor_output(&egl, GSR_CONNECTION_DRM, get_first_output, &first_output);
+
+ if(first_output.output_name) {
+ window_str = first_output.output_name;
+ } else {
+ fprintf(stderr, "Error: no available output found\n");
+ }
+ }
+
+ gsr_monitor gmon;
+ if(!get_monitor_by_name(&egl, GSR_CONNECTION_DRM, window_str, &gmon)) {
+ fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str);
+ fprintf(stderr, " \"screen\"\n");
+ for_each_active_monitor_output(&egl, GSR_CONNECTION_DRM, monitor_output_callback_print, NULL);
+ _exit(1);
+ }
+ } else {
+ if(strcmp(window_str, "screen") != 0 && strcmp(window_str, "screen-direct") != 0 && strcmp(window_str, "screen-direct-force") != 0) {
+ gsr_monitor gmon;
+ if(!get_monitor_by_name(&egl, GSR_CONNECTION_X11, window_str, &gmon)) {
+ const int screens_width = XWidthOfScreen(DefaultScreenOfDisplay(egl.x11.dpy));
+ const int screens_height = XWidthOfScreen(DefaultScreenOfDisplay(egl.x11.dpy));
+ fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str);
+ fprintf(stderr, " \"screen\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0);
+ fprintf(stderr, " \"screen-direct\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0);
+ fprintf(stderr, " \"screen-direct-force\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0);
+ for_each_active_monitor_output(&egl, GSR_CONNECTION_X11, monitor_output_callback_print, NULL);
+ _exit(1);
+ }
+ }
+ }
+
+ if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA) {
+ if(wayland) {
+ gsr_capture_kms_cuda_params kms_params;
+ kms_params.egl = &egl;
+ kms_params.display_to_capture = window_str;
+ kms_params.gpu_inf = gpu_inf;
+ kms_params.hdr = video_codec_is_hdr(video_codec);
+ kms_params.color_range = color_range;
+ kms_params.record_cursor = record_cursor;
+ capture = gsr_capture_kms_cuda_create(&kms_params);
+ if(!capture)
+ _exit(1);
+ } else {
+ const char *capture_target = window_str;
+ bool direct_capture = strcmp(window_str, "screen-direct") == 0;
+ if(direct_capture) {
+ capture_target = "screen";
+ // TODO: Temporary disable direct capture because push model causes stuttering when it's direct capturing. This might be a nvfbc bug. This does not happen when using a compositor.
+ direct_capture = false;
+ fprintf(stderr, "Warning: screen-direct has temporary been disabled as it causes stuttering. This is likely a NvFBC bug. Falling back to \"screen\".\n");
+ }
+
+ if(strcmp(window_str, "screen-direct-force") == 0) {
+ direct_capture = true;
+ capture_target = "screen";
+ }
+
+ gsr_capture_nvfbc_params nvfbc_params;
+ nvfbc_params.egl = &egl;
+ nvfbc_params.display_to_capture = capture_target;
+ nvfbc_params.fps = fps;
+ nvfbc_params.pos = { 0, 0 };
+ nvfbc_params.size = { 0, 0 };
+ nvfbc_params.direct_capture = direct_capture;
+ nvfbc_params.overclock = overclock;
+ nvfbc_params.hdr = video_codec_is_hdr(video_codec);
+ nvfbc_params.color_range = color_range;
+ nvfbc_params.record_cursor = record_cursor;
+ capture = gsr_capture_nvfbc_create(&nvfbc_params);
+ if(!capture)
+ _exit(1);
+ }
+ } else {
+ gsr_capture_kms_vaapi_params kms_params;
+ kms_params.egl = &egl;
+ kms_params.display_to_capture = window_str;
+ kms_params.gpu_inf = gpu_inf;
+ kms_params.hdr = video_codec_is_hdr(video_codec);
+ kms_params.color_range = color_range;
+ kms_params.record_cursor = record_cursor;
+ capture = gsr_capture_kms_vaapi_create(&kms_params);
+ if(!capture)
+ _exit(1);
+ }
+ } else {
+ if(wayland) {
+ fprintf(stderr, "Error: GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland\n");
+ _exit(2);
+ }
+
+ errno = 0;
+ src_window_id = strtol(window_str, nullptr, 0);
+ if(src_window_id == None || errno == EINVAL) {
+ fprintf(stderr, "Invalid window number %s\n", window_str);
+ usage();
+ }
+ }
+
+ if(!capture) {
+ switch(egl.gpu_info.vendor) {
+ case GSR_GPU_VENDOR_AMD:
+ case GSR_GPU_VENDOR_INTEL: {
+ gsr_capture_xcomposite_vaapi_params xcomposite_params;
+ xcomposite_params.base.egl = &egl;
+ xcomposite_params.base.window = src_window_id;
+ xcomposite_params.base.follow_focused = follow_focused;
+ xcomposite_params.base.region_size = region_size;
+ xcomposite_params.base.color_range = color_range;
+ xcomposite_params.base.record_cursor = record_cursor;
+ capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params);
+ if(!capture)
+ _exit(1);
+ break;
+ }
+ case GSR_GPU_VENDOR_NVIDIA: {
+ gsr_capture_xcomposite_cuda_params xcomposite_params;
+ xcomposite_params.base.egl = &egl;
+ xcomposite_params.base.window = src_window_id;
+ xcomposite_params.base.follow_focused = follow_focused;
+ xcomposite_params.base.region_size = region_size;
+ xcomposite_params.base.color_range = color_range;
+ xcomposite_params.base.record_cursor = record_cursor;
+ xcomposite_params.overclock = overclock;
+ capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params);
+ if(!capture)
+ _exit(1);
+ break;
+ }
+ }
+ }
+
+ return capture;
+}
+
+struct Arg {
+ std::vector<const char*> values;
+ bool optional = false;
+ bool list = false;
+
+ const char* value() const {
+ if(values.empty())
+ return nullptr;
+ return values.front();
+ }
};
int main(int argc, char **argv) {
- signal(SIGINT, int_handler);
+ signal(SIGINT, stop_handler);
signal(SIGUSR1, save_replay_handler);
+ signal(SIGUSR2, toggle_pause_handler);
+
+ // Stop nvidia driver from buffering frames
+ setenv("__GL_MaxFramesAllowed", "1", true);
+ // If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context,
+ // so we overwrite it
+ setenv("__GL_THREADED_OPTIMIZATIONS", "0", true);
+ // Some people set this to nvidia (for nvdec) or vdpau (for nvidia vdpau), which breaks gpu screen recorder since
+ // nvidia doesn't support vaapi and nvidia-vaapi-driver doesn't support encoding yet.
+ // Let vaapi find the match vaapi driver instead of forcing a specific one.
+ unsetenv("LIBVA_DRIVER_NAME");
if(argc <= 1)
usage_full();
@@ -1081,6 +1636,11 @@ int main(int argc, char **argv) {
if(argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0))
usage_full();
+ if(argc == 2 && strcmp(argv[1], "--list-supported-video-codecs") == 0) {
+ list_supported_video_codecs();
+ _exit(0);
+ }
+
//av_log_set_level(AV_LOG_TRACE);
std::map<std::string, Arg> args = {
@@ -1094,10 +1654,15 @@ int main(int argc, char **argv) {
{ "-r", Arg { {}, true, false } },
{ "-k", Arg { {}, true, false } },
{ "-ac", Arg { {}, true, false } },
+ { "-ab", Arg { {}, true, false } },
{ "-oc", Arg { {}, true, false } },
{ "-fm", Arg { {}, true, false } },
{ "-pixfmt", Arg { {}, true, false } },
{ "-v", Arg { {}, true, false } },
+ { "-mf", Arg { {}, true, false } },
+ { "-sc", Arg { {}, true, false } },
+ { "-cr", Arg { {}, true, false } },
+ { "-cursor", Arg { {}, true, false } },
};
for(int i = 1; i < argc; i += 2) {
@@ -1127,21 +1692,27 @@ int main(int argc, char **argv) {
}
}
- VideoCodec video_codec = VideoCodec::H265;
+ VideoCodec video_codec = VideoCodec::HEVC;
const char *video_codec_to_use = args["-k"].value();
if(!video_codec_to_use)
video_codec_to_use = "auto";
if(strcmp(video_codec_to_use, "h264") == 0) {
video_codec = VideoCodec::H264;
- } else if(strcmp(video_codec_to_use, "h265") == 0) {
- video_codec = VideoCodec::H265;
+ } else if(strcmp(video_codec_to_use, "h265") == 0 || strcmp(video_codec_to_use, "hevc") == 0) {
+ video_codec = VideoCodec::HEVC;
+ } else if(strcmp(video_codec_to_use, "hevc_hdr") == 0) {
+ video_codec = VideoCodec::HEVC_HDR;
+ } else if(strcmp(video_codec_to_use, "av1") == 0) {
+ video_codec = VideoCodec::AV1;
+ } else if(strcmp(video_codec_to_use, "av1_hdr") == 0) {
+ video_codec = VideoCodec::AV1_HDR;
} else if(strcmp(video_codec_to_use, "auto") != 0) {
- fprintf(stderr, "Error: -k should either be either 'auto', 'h264' or 'h265', got: '%s'\n", video_codec_to_use);
+ fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'hevc', 'hevc_hdr', 'av1' or 'av1_hdr', got: '%s'\n", video_codec_to_use);
usage();
}
- AudioCodec audio_codec = AudioCodec::OPUS;
+ AudioCodec audio_codec = AudioCodec::AAC;
const char *audio_codec_to_use = args["-ac"].value();
if(!audio_codec_to_use)
audio_codec_to_use = "aac";
@@ -1157,10 +1728,19 @@ int main(int argc, char **argv) {
usage();
}
- if(audio_codec != AudioCodec::AAC) {
+ if(audio_codec == AudioCodec::OPUS || audio_codec == AudioCodec::FLAC) {
+ fprintf(stderr, "Warning: opus and flac audio codecs are temporary disabled, using aac audio codec instead\n");
audio_codec_to_use = "aac";
audio_codec = AudioCodec::AAC;
- fprintf(stderr, "Info: audio codec is forcefully set to aac at the moment because of issues with opus/flac. This is a temporary issue\n");
+ }
+
+ int audio_bitrate = 0;
+ const char *audio_bitrate_str = args["-ab"].value();
+ if(audio_bitrate_str) {
+ if(sscanf(audio_bitrate_str, "%d", &audio_bitrate) != 1) {
+ fprintf(stderr, "Error: -ab argument \"%s\" is not an integer\n", audio_bitrate_str);
+ usage();
+ }
}
bool overclock = false;
@@ -1191,6 +1771,48 @@ int main(int argc, char **argv) {
usage();
}
+ bool record_cursor = true;
+ const char *record_cursor_str = args["-cursor"].value();
+ if(!record_cursor_str)
+ record_cursor_str = "yes";
+
+ if(strcmp(record_cursor_str, "yes") == 0) {
+ record_cursor = true;
+ } else if(strcmp(record_cursor_str, "no") == 0) {
+ record_cursor = false;
+ } else {
+ fprintf(stderr, "Error: -cursor should either be either 'yes' or 'no', got: '%s'\n", record_cursor_str);
+ usage();
+ }
+
+ bool make_folders = false;
+ const char *make_folders_str = args["-mf"].value();
+ if(!make_folders_str)
+ make_folders_str = "no";
+
+ if(strcmp(make_folders_str, "yes") == 0) {
+ make_folders = true;
+ } else if(strcmp(make_folders_str, "no") == 0) {
+ make_folders = false;
+ } else {
+ fprintf(stderr, "Error: -mf should either be either 'yes' or 'no', got: '%s'\n", make_folders_str);
+ usage();
+ }
+
+ const char *recording_saved_script = args["-sc"].value();
+ if(recording_saved_script) {
+ struct stat buf;
+ if(stat(recording_saved_script, &buf) == -1 || !S_ISREG(buf.st_mode)) {
+ fprintf(stderr, "Error: Script \"%s\" either doesn't exist or it's not a file\n", recording_saved_script);
+ usage();
+ }
+
+ if(!(buf.st_mode & S_IXUSR)) {
+ fprintf(stderr, "Error: Script \"%s\" is not executable\n", recording_saved_script);
+ usage();
+ }
+ }
+
PixelFormat pixel_format = PixelFormat::YUV420;
const char *pixfmt = args["-pixfmt"].value();
if(!pixfmt)
@@ -1206,14 +1828,23 @@ int main(int argc, char **argv) {
}
const Arg &audio_input_arg = args["-a"];
- const std::vector<AudioInput> audio_inputs = get_pulseaudio_inputs();
+ std::vector<AudioInput> audio_inputs;
+ if(!audio_input_arg.values.empty())
+ audio_inputs = get_pulseaudio_inputs();
std::vector<MergedAudioInputs> requested_audio_inputs;
+ bool uses_amix = false;
// Manually check if the audio inputs we give exist. This is only needed for pipewire, not pulseaudio.
// Pipewire instead DEFAULTS TO THE DEFAULT AUDIO INPUT. THAT'S RETARDED.
// OH, YOU MISSPELLED THE AUDIO INPUT? FUCK YOU
for(const char *audio_input : audio_input_arg.values) {
+ if(!audio_input || audio_input[0] == '\0')
+ continue;
+
requested_audio_inputs.push_back({parse_audio_input_arg(audio_input)});
+ if(requested_audio_inputs.back().audio_inputs.size() > 1)
+ uses_amix = true;
+
for(AudioInput &request_audio_input : requested_audio_inputs.back().audio_inputs) {
bool match = false;
for(const auto &existing_audio_input : audio_inputs) {
@@ -1229,7 +1860,7 @@ int main(int argc, char **argv) {
if(!match) {
fprintf(stderr, "Error: Audio input device '%s' is not a valid audio device, expected one of:\n", request_audio_input.name.c_str());
for(const auto &existing_audio_input : audio_inputs) {
- fprintf(stderr, " %s\n", existing_audio_input.name.c_str());
+ fprintf(stderr, " %s (%s)\n", existing_audio_input.name.c_str(), existing_audio_input.description.c_str());
}
_exit(2);
}
@@ -1237,6 +1868,9 @@ int main(int argc, char **argv) {
}
const char *container_format = args["-c"].value();
+ if(container_format && strcmp(container_format, "mkv") == 0)
+ container_format = "matroska";
+
int fps = atoi(args["-f"].value());
if(fps == 0) {
fprintf(stderr, "Invalid fps argument: %s\n", args["-f"].value());
@@ -1271,53 +1905,67 @@ int main(int argc, char **argv) {
fprintf(stderr, "Error: option -r has to be between 5 and 1200, was: %s\n", replay_buffer_size_secs_str);
_exit(1);
}
- replay_buffer_size_secs += 5; // Add a few seconds to account of lost packets because of non-keyframe packets skipped
+ replay_buffer_size_secs += 3; // Add a few seconds to account of lost packets because of non-keyframe packets skipped
}
+ const char *window_str = strdup(args["-w"].value());
+
+ bool wayland = false;
Display *dpy = XOpenDisplay(nullptr);
if (!dpy) {
- fprintf(stderr, "Error: Failed to open display. Make sure you are running x11\n");
- _exit(2);
+ wayland = true;
+ fprintf(stderr, "Warning: failed to connect to the X server. Assuming wayland is running without Xwayland\n");
}
XSetErrorHandler(x11_error_handler);
XSetIOErrorHandler(x11_io_error_handler);
- if(is_xwayland(dpy)) {
- fprintf(stderr, "Error: GPU Screen Recorder only works in a pure X11 session. Xwayland is not supported\n");
- _exit(2);
+ if(!wayland)
+ wayland = is_xwayland(dpy);
+
+ if(video_codec_is_hdr(video_codec) && !wayland) {
+ fprintf(stderr, "Error: hdr video codec option %s is not available on X11\n", video_codec_to_use);
+ _exit(1);
+ }
+
+ const bool is_monitor_capture = strcmp(window_str, "focused") != 0 && contains_non_hex_number(window_str);
+ gsr_egl egl;
+ if(!gsr_egl_load(&egl, dpy, wayland, is_monitor_capture)) {
+ fprintf(stderr, "gsr error: failed to load opengl\n");
+ _exit(1);
}
- gsr_gpu_info gpu_inf;
bool very_old_gpu = false;
- if(!gl_get_gpu_info(dpy, &gpu_inf))
- _exit(2);
- if(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA && gpu_inf.gpu_version != 0 && gpu_inf.gpu_version < 900) {
+ if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA && egl.gpu_info.gpu_version != 0 && egl.gpu_info.gpu_version < 900) {
fprintf(stderr, "Info: your gpu appears to be very old (older than maxwell architecture). Switching to lower preset\n");
very_old_gpu = true;
}
- if(gpu_inf.vendor != GSR_GPU_VENDOR_NVIDIA && overclock) {
- fprintf(stderr, "Info: overclock option has no effect on amd/intel, ignoring option...\n");
+ if(egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA && overclock) {
+ fprintf(stderr, "Info: overclock option has no effect on amd/intel, ignoring option\n");
}
- char card_path[128];
- card_path[0] = '\0';
- if(gpu_inf.vendor != GSR_GPU_VENDOR_NVIDIA) {
+ if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA && overclock && wayland) {
+ fprintf(stderr, "Info: overclocking is not possible on nvidia on wayland, ignoring option\n");
+ }
+
+ egl.card_path[0] = '\0';
+ if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
// TODO: Allow specifying another card, and in other places
- if(!gsr_get_valid_card_path(card_path)) {
- fprintf(stderr, "Error: no /dev/dri/cardX device found\n");
+ if(!gsr_get_valid_card_path(&egl, egl.card_path)) {
+ fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it\n");
_exit(2);
}
}
// TODO: Fix constant framerate not working properly on amd/intel because capture framerate gets locked to the same framerate as
// game framerate, which doesn't work well when you need to encode multiple duplicate frames (AMD/Intel is slow at encoding!).
+ // It also appears to skip audio frames on nvidia wayland? why? that should be fine, but it causes video stuttering because of audio/video sync.
FramerateMode framerate_mode;
const char *framerate_mode_str = args["-fm"].value();
if(!framerate_mode_str)
- framerate_mode_str = gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA ? "cfr" : "vfr";
+ framerate_mode_str = "vfr";
if(strcmp(framerate_mode_str, "cfr") == 0) {
framerate_mode = FramerateMode::CONSTANT;
@@ -1328,148 +1976,58 @@ int main(int argc, char **argv) {
usage();
}
- const char *screen_region = args["-s"].value();
- const char *window_str = args["-w"].value();
+ gsr_color_range color_range;
+ const char *color_range_str = args["-cr"].value();
+ if(!color_range_str)
+ color_range_str = "limited";
- if(screen_region && strcmp(window_str, "focused") != 0) {
- fprintf(stderr, "Error: option -s is only available when using -w focused\n");
+ if(strcmp(color_range_str, "limited") == 0) {
+ color_range = GSR_COLOR_RANGE_LIMITED;
+ } else if(strcmp(color_range_str, "full") == 0) {
+ color_range = GSR_COLOR_RANGE_FULL;
+ } else {
+ fprintf(stderr, "Error: -cr should either be either 'limited' or 'full', got: '%s'\n", color_range_str);
usage();
}
- vec2i region_size = { 0, 0 };
- Window src_window_id = None;
- bool follow_focused = false;
-
- gsr_capture *capture = nullptr;
- if(strcmp(window_str, "focused") == 0) {
- if(!screen_region) {
- fprintf(stderr, "Error: option -s is required when using -w focused\n");
- usage();
- }
-
- if(sscanf(screen_region, "%dx%d", &region_size.x, &region_size.y) != 2) {
- fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", screen_region);
- usage();
- }
-
- if(region_size.x <= 0 || region_size.y <= 0) {
- fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater than 0\n", screen_region);
- usage();
- }
-
- follow_focused = true;
- } else if(contains_non_hex_number(window_str)) {
- if(strcmp(window_str, "screen") != 0 && strcmp(window_str, "screen-direct") != 0 && strcmp(window_str, "screen-direct-force") != 0) {
- gsr_monitor gmon;
- if(!get_monitor_by_name(dpy, window_str, &gmon)) {
- fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str);
- fprintf(stderr, " \"screen\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0);
- fprintf(stderr, " \"screen-direct\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0);
- fprintf(stderr, " \"screen-direct-force\" (%dx%d+%d+%d)\n", XWidthOfScreen(DefaultScreenOfDisplay(dpy)), XHeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0);
- for_each_active_monitor_output(dpy, monitor_output_callback_print, NULL);
- _exit(1);
- }
- }
-
- if(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA) {
- const char *capture_target = window_str;
- bool direct_capture = strcmp(window_str, "screen-direct") == 0;
- if(direct_capture) {
- capture_target = "screen";
- // TODO: Temporary disable direct capture because push model causes stuttering when it's direct capturing. This might be a nvfbc bug. This does not happen when using a compositor.
- direct_capture = false;
- fprintf(stderr, "Warning: screen-direct has temporary been disabled as it causes stuttering. This is likely a NvFBC bug. Falling back to \"screen\".\n");
- }
-
- if(strcmp(window_str, "screen-direct-force") == 0) {
- direct_capture = true;
- capture_target = "screen";
- }
-
- gsr_capture_nvfbc_params nvfbc_params;
- nvfbc_params.dpy = dpy;
- nvfbc_params.display_to_capture = capture_target;
- nvfbc_params.fps = fps;
- nvfbc_params.pos = { 0, 0 };
- nvfbc_params.size = { 0, 0 };
- nvfbc_params.direct_capture = direct_capture;
- nvfbc_params.overclock = overclock;
- capture = gsr_capture_nvfbc_create(&nvfbc_params);
- if(!capture)
- _exit(1);
- } else {
- const char *capture_target = window_str;
- if(strcmp(window_str, "screen-direct") == 0 || strcmp(window_str, "screen-direct-force") == 0) {
- capture_target = "screen";
- }
-
- gsr_capture_kms_vaapi_params kms_params;
- kms_params.display_to_capture = capture_target;
- kms_params.gpu_inf = gpu_inf;
- kms_params.card_path = card_path;
- capture = gsr_capture_kms_vaapi_create(&kms_params);
- if(!capture)
- _exit(1);
- }
- } else {
- errno = 0;
- src_window_id = strtol(window_str, nullptr, 0);
- if(src_window_id == None || errno == EINVAL) {
- fprintf(stderr, "Invalid window number %s\n", window_str);
- usage();
- }
- }
+ const char *screen_region = args["-s"].value();
- if(!capture) {
- switch(gpu_inf.vendor) {
- case GSR_GPU_VENDOR_AMD: {
- gsr_capture_xcomposite_vaapi_params xcomposite_params;
- xcomposite_params.window = src_window_id;
- xcomposite_params.follow_focused = follow_focused;
- xcomposite_params.region_size = region_size;
- xcomposite_params.card_path = card_path;
- capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params);
- if(!capture)
- _exit(1);
- break;
- }
- case GSR_GPU_VENDOR_INTEL: {
- gsr_capture_xcomposite_vaapi_params xcomposite_params;
- xcomposite_params.window = src_window_id;
- xcomposite_params.follow_focused = follow_focused;
- xcomposite_params.region_size = region_size;
- xcomposite_params.card_path = card_path;
- capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params);
- if(!capture)
- _exit(1);
- break;
- }
- case GSR_GPU_VENDOR_NVIDIA: {
- gsr_capture_xcomposite_cuda_params xcomposite_params;
- xcomposite_params.window = src_window_id;
- xcomposite_params.follow_focused = follow_focused;
- xcomposite_params.region_size = region_size;
- xcomposite_params.overclock = overclock;
- capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params);
- if(!capture)
- _exit(1);
- break;
- }
- }
+ if(screen_region && strcmp(window_str, "focused") != 0) {
+ fprintf(stderr, "Error: option -s is only available when using -w focused\n");
+ usage();
}
+ bool is_livestream = false;
const char *filename = args["-o"].value();
if(filename) {
- if(replay_buffer_size_secs != -1) {
- if(!container_format) {
- fprintf(stderr, "Error: option -c is required when using option -r\n");
- usage();
+ is_livestream = is_livestream_path(filename);
+ if(is_livestream) {
+ if(replay_buffer_size_secs != -1) {
+ fprintf(stderr, "Error: replay mode is not applicable to live streaming\n");
+ _exit(1);
}
+ } else {
+ if(replay_buffer_size_secs == -1) {
+ char directory_buf[PATH_MAX];
+ strcpy(directory_buf, filename);
+ char *directory = dirname(directory_buf);
+ if(strcmp(directory, ".") != 0 && strcmp(directory, "/") != 0) {
+ if(create_directory_recursive(directory) != 0) {
+ fprintf(stderr, "Error: failed to create directory for output file: %s\n", filename);
+ _exit(1);
+ }
+ }
+ } else {
+ if(!container_format) {
+ fprintf(stderr, "Error: option -c is required when using option -r\n");
+ usage();
+ }
- struct stat buf;
- if(stat(filename, &buf) == -1 || !S_ISDIR(buf.st_mode)) {
- fprintf(stderr, "Error: directory \"%s\" does not exist or is not a directory\n", filename);
- usage();
+ struct stat buf;
+ if(stat(filename, &buf) != -1 && !S_ISDIR(buf.st_mode)) {
+ fprintf(stderr, "Error: File \"%s\" exists but it's not a directory\n", filename);
+ usage();
+ }
}
}
} else {
@@ -1490,7 +2048,10 @@ int main(int argc, char **argv) {
// The output format is automatically guessed by the file extension
avformat_alloc_output_context2(&av_format_context, nullptr, container_format, filename);
if (!av_format_context) {
- fprintf(stderr, "Error: Failed to deduce container format from file extension\n");
+ if(container_format)
+ fprintf(stderr, "Error: Container format '%s' (argument -c) is not valid\n", container_format);
+ else
+ fprintf(stderr, "Error: Failed to deduce container format from file extension\n");
_exit(1);
}
@@ -1503,11 +2064,18 @@ int main(int argc, char **argv) {
file_extension = file_extension.substr(0, comma_index);
}
+ if(egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA && file_extension == "mkv" && strcmp(video_codec_to_use, "h264") == 0) {
+ video_codec_to_use = "hevc";
+ video_codec = VideoCodec::HEVC;
+ fprintf(stderr, "Warning: video codec was forcefully set to hevc because mkv container is used and mesa (AMD and Intel driver) does not support h264 in mkv files\n");
+ }
+
switch(audio_codec) {
case AudioCodec::AAC: {
break;
}
case AudioCodec::OPUS: {
+ // TODO: Also check mpegts?
if(file_extension != "mp4" && file_extension != "mkv") {
audio_codec_to_use = "aac";
audio_codec = AudioCodec::AAC;
@@ -1516,10 +2084,15 @@ int main(int argc, char **argv) {
break;
}
case AudioCodec::FLAC: {
+ // TODO: Also check mpegts?
if(file_extension != "mp4" && file_extension != "mkv") {
audio_codec_to_use = "aac";
audio_codec = AudioCodec::AAC;
fprintf(stderr, "Warning: flac audio codec is only supported by .mp4 and .mkv files, falling back to aac instead\n");
+ } else if(uses_amix) {
+ audio_codec_to_use = "opus";
+ audio_codec = AudioCodec::OPUS;
+ fprintf(stderr, "Warning: flac audio codec is not supported when mixing audio sources, falling back to opus instead\n");
}
break;
}
@@ -1527,68 +2100,125 @@ int main(int argc, char **argv) {
const double target_fps = 1.0 / (double)fps;
- if(strcmp(video_codec_to_use, "auto") == 0) {
- if(gpu_inf.vendor == GSR_GPU_VENDOR_INTEL) {
- const AVCodec *h264_codec = find_h264_encoder(gpu_inf.vendor, card_path);
+ const bool video_codec_auto = strcmp(video_codec_to_use, "auto") == 0;
+ if(video_codec_auto) {
+ if(egl.gpu_info.vendor == GSR_GPU_VENDOR_INTEL) {
+ const AVCodec *h264_codec = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
if(!h264_codec) {
- fprintf(stderr, "Info: using h265 encoder because a codec was not specified and your gpu does not support h264\n");
- video_codec_to_use = "h265";
- video_codec = VideoCodec::H265;
+ fprintf(stderr, "Info: using hevc encoder because a codec was not specified and your gpu does not support h264\n");
+ video_codec_to_use = "hevc";
+ video_codec = VideoCodec::HEVC;
} else {
fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n");
video_codec_to_use = "h264";
video_codec = VideoCodec::H264;
}
} else {
- const AVCodec *h265_codec = find_h265_encoder(gpu_inf.vendor, card_path);
+ const AVCodec *h265_codec = find_h265_encoder(egl.gpu_info.vendor, egl.card_path);
- // h265 generally allows recording at a higher resolution than h264 on nvidia cards. On a gtx 1080 4k is the max resolution for h264 but for h265 it's 8k.
- // Another important info is that when recording at a higher fps than.. 60? h265 has very bad performance. For example when recording at 144 fps the fps drops to 1
+ if(h265_codec && fps > 60) {
+ fprintf(stderr, "Warning: recording at higher fps than 60 with hevc might result in recording at a very low fps. If this happens, switch to h264 or av1\n");
+ }
+
+ // hevc generally allows recording at a higher resolution than h264 on nvidia cards. On a gtx 1080 4k is the max resolution for h264 but for hevc it's 8k.
+ // Another important info is that when recording at a higher fps than.. 60? hevc has very bad performance. For example when recording at 144 fps the fps drops to 1
// while with h264 the fps doesn't drop.
if(!h265_codec) {
- fprintf(stderr, "Info: using h264 encoder because a codec was not specified and your gpu does not support h265\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- } else if(fps > 60) {
- fprintf(stderr, "Info: using h264 encoder because a codec was not specified and fps is more than 60\n");
+ fprintf(stderr, "Info: using h264 encoder because a codec was not specified and your gpu does not support hevc\n");
video_codec_to_use = "h264";
video_codec = VideoCodec::H264;
} else {
- fprintf(stderr, "Info: using h265 encoder because a codec was not specified\n");
- video_codec_to_use = "h265";
- video_codec = VideoCodec::H265;
+ fprintf(stderr, "Info: using hevc encoder because a codec was not specified\n");
+ video_codec_to_use = "hevc";
+ video_codec = VideoCodec::HEVC;
}
}
}
- //bool use_hevc = strcmp(window_str, "screen") == 0 || strcmp(window_str, "screen-direct") == 0;
- if(video_codec != VideoCodec::H264 && strcmp(file_extension.c_str(), "flv") == 0) {
+ // TODO: Allow hevc, vp9 and av1 in (enhanced) flv (supported since ffmpeg 6.1)
+ const bool is_flv = strcmp(file_extension.c_str(), "flv") == 0;
+ if(video_codec != VideoCodec::H264 && is_flv) {
video_codec_to_use = "h264";
video_codec = VideoCodec::H264;
- fprintf(stderr, "Warning: h265 is not compatible with flv, falling back to h264 instead.\n");
+ fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n");
}
const AVCodec *video_codec_f = nullptr;
switch(video_codec) {
case VideoCodec::H264:
- video_codec_f = find_h264_encoder(gpu_inf.vendor, card_path);
+ video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
break;
- case VideoCodec::H265:
- video_codec_f = find_h265_encoder(gpu_inf.vendor, card_path);
+ case VideoCodec::HEVC:
+ case VideoCodec::HEVC_HDR:
+ video_codec_f = find_h265_encoder(egl.gpu_info.vendor, egl.card_path);
+ break;
+ case VideoCodec::AV1:
+ case VideoCodec::AV1_HDR:
+ video_codec_f = find_av1_encoder(egl.gpu_info.vendor, egl.card_path);
break;
}
+ if(!video_codec_auto && !video_codec_f && !is_flv) {
+ switch(video_codec) {
+ case VideoCodec::H264: {
+ fprintf(stderr, "Warning: selected video codec h264 is not supported, trying hevc instead\n");
+ video_codec_to_use = "hevc";
+ video_codec = VideoCodec::HEVC;
+ video_codec_f = find_h265_encoder(egl.gpu_info.vendor, egl.card_path);
+ break;
+ }
+ case VideoCodec::HEVC:
+ case VideoCodec::HEVC_HDR: {
+ fprintf(stderr, "Warning: selected video codec hevc is not supported, trying h264 instead\n");
+ video_codec_to_use = "h264";
+ video_codec = VideoCodec::H264;
+ video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
+ break;
+ }
+ case VideoCodec::AV1:
+ case VideoCodec::AV1_HDR: {
+ fprintf(stderr, "Warning: selected video codec av1 is not supported, trying h264 instead\n");
+ video_codec_to_use = "h264";
+ video_codec = VideoCodec::H264;
+ video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
+ break;
+ }
+ }
+ }
+
if(!video_codec_f) {
- const char *video_codec_name = video_codec == VideoCodec::H264 ? "h264" : "h265";
+ const char *video_codec_name = "";
+ switch(video_codec) {
+ case VideoCodec::H264: {
+ video_codec_name = "h264";
+ break;
+ }
+ case VideoCodec::HEVC:
+ case VideoCodec::HEVC_HDR: {
+ video_codec_name = "hevc";
+ break;
+ }
+ case VideoCodec::AV1:
+ case VideoCodec::AV1_HDR: {
+ video_codec_name = "av1";
+ break;
+ }
+ }
+
fprintf(stderr, "Error: your gpu does not support '%s' video codec. If you are sure that your gpu does support '%s' video encoding and you are using an AMD/Intel GPU,\n"
- " then it's possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n"
- " This may be the case on corporate distros such as Manjaro.\n"
- " You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC profile. vainfo is part of libva-utils.\n"
- " On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro.\n", video_codec_name, video_codec_name, video_codec_name);
+ " then make sure you have installed the GPU specific vaapi packages (intel-media-driver, libva-intel-driver or libva-mesa-driver).\n"
+ " It's also possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n"
+ " This may be the case on corporate distros such as Manjaro, Fedora or OpenSUSE.\n"
+ " You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC profile.\n"
+ " On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro. Alternatively record with AV1 if supported by your GPU.\n"
+ " You can alternatively use the flatpak version of GPU Screen Recorder (https://flathub.org/apps/com.dec05eba.gpu_screen_recorder) which bypasses system issues with patented H264/HEVC codecs.\n"
+ " Make sure you have mesa-extra freedesktop runtime installed when using the flatpak (this should be the default), which can be installed with this command:\n"
+ " flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra", video_codec_name, video_codec_name, video_codec_name);
_exit(2);
}
- const bool is_livestream = is_livestream_path(filename);
+ gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, egl.gpu_info, egl, fps, overclock, video_codec, color_range, record_cursor);
+
// (Some?) livestreaming services require at least one audio track to work.
// If not audio is provided then create one silent audio track.
if(is_livestream && requested_audio_inputs.empty()) {
@@ -1598,31 +2228,59 @@ int main(int argc, char **argv) {
requested_audio_inputs.push_back(std::move(mai));
}
+ if(is_livestream && framerate_mode != FramerateMode::CONSTANT) {
+ fprintf(stderr, "Info: framerate mode was forcefully set to \"cfr\" because live streaming was detected\n");
+ framerate_mode = FramerateMode::CONSTANT;
+ framerate_mode_str = "cfr";
+ }
+
+ if(is_livestream && recording_saved_script) {
+ fprintf(stderr, "Warning: live stream detected, -sc script is ignored\n");
+ recording_saved_script = nullptr;
+ }
+
AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks;
+ const bool hdr = video_codec_is_hdr(video_codec);
- AVCodecContext *video_codec_context = create_video_codec_context(gpu_inf.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, gpu_inf.vendor, framerate_mode);
+ AVCodecContext *video_codec_context = create_video_codec_context(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, egl.gpu_info.vendor, framerate_mode, hdr, color_range);
if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context);
- if(gsr_capture_start(capture, video_codec_context) != 0) {
- fprintf(stderr, "gsr error: gsr_capture_start failed\n");
+ AVFrame *video_frame = av_frame_alloc();
+ if(!video_frame) {
+ fprintf(stderr, "Error: Failed to allocate video frame\n");
_exit(1);
}
+ video_frame->format = video_codec_context->pix_fmt;
+ video_frame->width = video_codec_context->width;
+ video_frame->height = video_codec_context->height;
+ video_frame->color_range = video_codec_context->color_range;
+ video_frame->color_primaries = video_codec_context->color_primaries;
+ video_frame->color_trc = video_codec_context->color_trc;
+ video_frame->colorspace = video_codec_context->colorspace;
+ video_frame->chroma_location = video_codec_context->chroma_sample_location;
+
+ int capture_result = gsr_capture_start(capture, video_codec_context, video_frame);
+ if(capture_result != 0) {
+ fprintf(stderr, "gsr error: gsr_capture_start failed\n");
+ _exit(capture_result);
+ }
- open_video(video_codec_context, quality, very_old_gpu, gpu_inf.vendor, pixel_format);
+ open_video(video_codec_context, quality, very_old_gpu, egl.gpu_info.vendor, pixel_format, hdr);
if(video_stream)
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
int audio_stream_index = VIDEO_STREAM_INDEX + 1;
for(const MergedAudioInputs &merged_audio_inputs : requested_audio_inputs) {
- AVCodecContext *audio_codec_context = create_audio_codec_context(fps, audio_codec);
+ const bool use_amix = merged_audio_inputs.audio_inputs.size() > 1;
+ AVCodecContext *audio_codec_context = create_audio_codec_context(fps, audio_codec, use_amix, audio_bitrate);
AVStream *audio_stream = nullptr;
if(replay_buffer_size_secs == -1)
audio_stream = create_stream(av_format_context, audio_codec_context);
- AVFrame *audio_frame = open_audio(audio_codec_context);
+ open_audio(audio_codec_context);
if(audio_stream)
avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_context);
@@ -1637,7 +2295,6 @@ int main(int argc, char **argv) {
std::vector<AVFilterContext*> src_filter_ctx;
AVFilterGraph *graph = nullptr;
AVFilterContext *sink = nullptr;
- bool use_amix = merged_audio_inputs.audio_inputs.size() > 1;
if(use_amix) {
int err = init_filter_graph(audio_codec_context, &graph, &sink, src_filter_ctx, merged_audio_inputs.audio_inputs.size());
if(err < 0) {
@@ -1669,12 +2326,14 @@ int main(int argc, char **argv) {
}
}
+ audio_device.frame = create_audio_frame(audio_codec_context);
+ audio_device.frame->pts = 0;
+
audio_devices.push_back(std::move(audio_device));
}
AudioTrack audio_track;
audio_track.codec_context = audio_codec_context;
- audio_track.frame = audio_frame;
audio_track.stream = audio_stream;
audio_track.audio_devices = std::move(audio_devices);
audio_track.graph = graph;
@@ -1710,29 +2369,19 @@ int main(int argc, char **argv) {
const double start_time_pts = clock_get_monotonic_seconds();
- double start_time = clock_get_monotonic_seconds(); // todo - target_fps to make first frame start immediately?
- double frame_timer_start = start_time;
+ double start_time = clock_get_monotonic_seconds();
+ double frame_timer_start = start_time - target_fps; // We want to capture the first frame immediately
int fps_counter = 0;
- AVFrame *frame = av_frame_alloc();
- if (!frame) {
- fprintf(stderr, "Error: Failed to allocate frame\n");
- _exit(1);
- }
- frame->format = video_codec_context->pix_fmt;
- frame->width = video_codec_context->width;
- frame->height = video_codec_context->height;
- frame->color_range = video_codec_context->color_range;
- frame->color_primaries = video_codec_context->color_primaries;
- frame->color_trc = video_codec_context->color_trc;
- frame->colorspace = video_codec_context->colorspace;
- frame->chroma_location = video_codec_context->chroma_sample_location;
+ bool paused = false;
+ double paused_time_offset = 0.0;
+ double paused_time_start = 0.0;
std::mutex write_output_mutex;
std::mutex audio_filter_mutex;
const double record_start_time = clock_get_monotonic_seconds();
- std::deque<AVPacket> frame_data_queue;
+ std::deque<std::shared_ptr<PacketData>> frame_data_queue;
bool frames_erased = false;
const size_t audio_buffer_size = 1024 * 4 * 2; // max 4 bytes/sample, 2 channels
@@ -1743,11 +2392,14 @@ int main(int argc, char **argv) {
}
memset(empty_audio, 0, audio_buffer_size);
+ const double audio_startup_time_seconds = std::max(0.0, 0.089166 - target_fps);
+
for(AudioTrack &audio_track : audio_tracks) {
for(AudioDevice &audio_device : audio_track.audio_devices) {
audio_device.thread = std::thread([&]() mutable {
const AVSampleFormat sound_device_sample_format = audio_format_to_sample_format(audio_codec_context_get_audio_format(audio_track.codec_context));
- const bool needs_audio_conversion = audio_track.codec_context->sample_fmt != sound_device_sample_format;
+ // TODO: Always do conversion for now. This fixes issue with stuttering audio on pulseaudio with opus + multiple audio sources merged
+ const bool needs_audio_conversion = true;//audio_track.codec_context->sample_fmt != sound_device_sample_format;
SwrContext *swr = nullptr;
if(needs_audio_conversion) {
swr = swr_alloc();
@@ -1755,8 +2407,13 @@ int main(int argc, char **argv) {
fprintf(stderr, "Failed to create SwrContext\n");
_exit(1);
}
- av_opt_set_int(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
- av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
+ #if LIBAVUTIL_VERSION_MAJOR <= 56
+ av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
+ av_opt_set_channel_layout(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
+ #else
+ av_opt_set_chlayout(swr, "in_channel_layout", &audio_track.codec_context->ch_layout, 0);
+ av_opt_set_chlayout(swr, "out_channel_layout", &audio_track.codec_context->ch_layout, 0);
+ #endif
av_opt_set_int(swr, "in_sample_rate", audio_track.codec_context->sample_rate, 0);
av_opt_set_int(swr, "out_sample_rate", audio_track.codec_context->sample_rate, 0);
av_opt_set_sample_fmt(swr, "in_sample_fmt", sound_device_sample_format, 0);
@@ -1764,37 +2421,56 @@ int main(int argc, char **argv) {
swr_init(swr);
}
- const double target_audio_hz = 1.0 / (double)audio_track.codec_context->sample_rate;
double received_audio_time = clock_get_monotonic_seconds();
- const int64_t timeout_ms = std::round((1000.0 / (double)audio_track.codec_context->sample_rate) * 1000.0);
- int64_t prev_pts = 0;
+ const double timeout_sec = 1000.0 / (double)audio_track.codec_context->sample_rate;
+ const int64_t timeout_ms = std::round(timeout_sec * 1000.0);
while(running) {
void *sound_buffer;
int sound_buffer_size = -1;
- if(audio_device.sound_device.handle)
- sound_buffer_size = sound_device_read_next_chunk(&audio_device.sound_device, &sound_buffer);
+ //const double time_before_read_seconds = clock_get_monotonic_seconds();
+ if(audio_device.sound_device.handle) {
+ // TODO: use this instead of calculating time to read. But this can fluctuate and we dont want to go back in time,
+ // also it's 0.0 for some users???
+ double latency_seconds = 0.0;
+ sound_buffer_size = sound_device_read_next_chunk(&audio_device.sound_device, &sound_buffer, timeout_sec, &latency_seconds);
+ }
+
const bool got_audio_data = sound_buffer_size >= 0;
+ //const double time_after_read_seconds = clock_get_monotonic_seconds();
+ //const double time_to_read_seconds = time_after_read_seconds - time_before_read_seconds;
+ const double this_audio_frame_time = (clock_get_monotonic_seconds() - audio_startup_time_seconds) - paused_time_offset;
- const double this_audio_frame_time = clock_get_monotonic_seconds();
- if(got_audio_data)
- received_audio_time = this_audio_frame_time;
+ if(paused) {
+ if(got_audio_data)
+ received_audio_time = this_audio_frame_time;
- int ret = av_frame_make_writable(audio_track.frame);
+ if(!audio_device.sound_device.handle)
+ usleep(timeout_ms * 1000);
+
+ continue;
+ }
+
+ int ret = av_frame_make_writable(audio_device.frame);
if (ret < 0) {
fprintf(stderr, "Failed to make audio frame writable\n");
break;
}
// TODO: Is this |received_audio_time| really correct?
- int64_t num_missing_frames = std::round((this_audio_frame_time - received_audio_time) / target_audio_hz / (int64_t)audio_track.frame->nb_samples);
+ const double prev_audio_time = received_audio_time;
+ const double audio_receive_time_diff = this_audio_frame_time - received_audio_time;
+ int64_t num_missing_frames = std::round(audio_receive_time_diff / timeout_sec);
if(got_audio_data)
num_missing_frames = std::max((int64_t)0, num_missing_frames - 1);
if(!audio_device.sound_device.handle)
num_missing_frames = std::max((int64_t)1, num_missing_frames);
- // Jesus is there a better way to do this? I JUST WANT TO KEEP VIDEO AND AUDIO SYNCED HOLY FUCK I WANT TO KILL MYSELF NOW.
+ if(got_audio_data)
+ received_audio_time = this_audio_frame_time;
+
+ // Fucking hell is there a better way to do this? I JUST WANT TO KEEP VIDEO AND AUDIO SYNCED HOLY FUCK I WANT TO KILL MYSELF NOW.
// THIS PIECE OF SHIT WANTS EMPTY FRAMES OTHERWISE VIDEO PLAYS TOO FAST TO KEEP UP WITH AUDIO OR THE AUDIO PLAYS TOO EARLY.
// BUT WE CANT USE DELAYS TO GIVE DUMMY DATA BECAUSE PULSEAUDIO MIGHT GIVE AUDIO A BIG DELAYED!!!
// This garbage is needed because we want to produce constant frame rate videos instead of variable frame rate
@@ -1806,29 +2482,28 @@ int main(int argc, char **argv) {
//audio_track.frame->data[0] = empty_audio;
received_audio_time = this_audio_frame_time;
if(needs_audio_conversion)
- swr_convert(swr, &audio_track.frame->data[0], audio_track.frame->nb_samples, (const uint8_t**)&empty_audio, audio_track.codec_context->frame_size);
+ swr_convert(swr, &audio_device.frame->data[0], audio_track.codec_context->frame_size, (const uint8_t**)&empty_audio, audio_track.codec_context->frame_size);
else
- audio_track.frame->data[0] = empty_audio;
+ audio_device.frame->data[0] = empty_audio;
// TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again
std::lock_guard<std::mutex> lock(audio_filter_mutex);
for(int i = 0; i < num_missing_frames; ++i) {
+ const int64_t new_pts = ((prev_audio_time - record_start_time) + timeout_sec * i) * AV_TIME_BASE;
+ if(new_pts == audio_device.frame->pts)
+ continue;
+
+ audio_device.frame->pts = new_pts;
if(audio_track.graph) {
// TODO: av_buffersrc_add_frame
- if(av_buffersrc_write_frame(audio_device.src_filter_ctx, audio_track.frame) < 0) {
+ if(av_buffersrc_write_frame(audio_device.src_filter_ctx, audio_device.frame) < 0) {
fprintf(stderr, "Error: failed to add audio frame to filter\n");
}
} else {
- audio_track.frame->pts = (this_audio_frame_time - record_start_time) * (double)AV_TIME_BASE;
- const bool same_pts = audio_track.frame->pts == prev_pts;
- prev_pts = audio_track.frame->pts;
- if(same_pts)
- continue;
-
- ret = avcodec_send_frame(audio_track.codec_context, audio_track.frame);
+ ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
if(ret >= 0) {
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
- receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_track.frame->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex);
+ receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_device.frame->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
} else {
fprintf(stderr, "Failed to encode audio!\n");
}
@@ -1842,29 +2517,28 @@ int main(int argc, char **argv) {
if(got_audio_data) {
// TODO: Instead of converting audio, get float audio from alsa. Or does alsa do conversion internally to get this format?
if(needs_audio_conversion)
- swr_convert(swr, &audio_track.frame->data[0], audio_track.frame->nb_samples, (const uint8_t**)&sound_buffer, audio_track.codec_context->frame_size);
+ swr_convert(swr, &audio_device.frame->data[0], audio_track.codec_context->frame_size, (const uint8_t**)&sound_buffer, audio_track.codec_context->frame_size);
else
- audio_track.frame->data[0] = (uint8_t*)sound_buffer;
+ audio_device.frame->data[0] = (uint8_t*)sound_buffer;
- audio_track.frame->pts = (this_audio_frame_time - record_start_time) * (double)AV_TIME_BASE;
- const bool same_pts = audio_track.frame->pts == prev_pts;
- prev_pts = audio_track.frame->pts;
- if(same_pts)
- continue;
+ const int64_t new_pts = (this_audio_frame_time - record_start_time) * AV_TIME_BASE;
+ if(new_pts != audio_device.frame->pts) {
+ audio_device.frame->pts = new_pts;
- if(audio_track.graph) {
- std::lock_guard<std::mutex> lock(audio_filter_mutex);
- // TODO: av_buffersrc_add_frame
- if(av_buffersrc_write_frame(audio_device.src_filter_ctx, audio_track.frame) < 0) {
- fprintf(stderr, "Error: failed to add audio frame to filter\n");
- }
- } else {
- ret = avcodec_send_frame(audio_track.codec_context, audio_track.frame);
- if(ret >= 0) {
- // TODO: Move to separate thread because this could write to network (for example when livestreaming)
- receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_track.frame->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex);
+ if(audio_track.graph) {
+ std::lock_guard<std::mutex> lock(audio_filter_mutex);
+ // TODO: av_buffersrc_add_frame
+ if(av_buffersrc_write_frame(audio_device.src_filter_ctx, audio_device.frame) < 0) {
+ fprintf(stderr, "Error: failed to add audio frame to filter\n");
+ }
} else {
- fprintf(stderr, "Failed to encode audio!\n");
+ ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
+ if(ret >= 0) {
+ // TODO: Move to separate thread because this could write to network (for example when livestreaming)
+ receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_device.frame->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
+ } else {
+ fprintf(stderr, "Failed to encode audio!\n");
+ }
}
}
}
@@ -1884,12 +2558,11 @@ int main(int argc, char **argv) {
int64_t video_pts_counter = 0;
int64_t video_prev_pts = 0;
- int64_t audio_prev_pts = 0;
while(running) {
double frame_start = clock_get_monotonic_seconds();
- gsr_capture_tick(capture, video_codec_context, &frame);
+ gsr_capture_tick(capture, video_codec_context);
should_stop_error = false;
if(gsr_capture_should_stop(capture, &should_stop_error)) {
running = 0;
@@ -1905,19 +2578,15 @@ int main(int argc, char **argv) {
int err = 0;
while ((err = av_buffersink_get_frame(audio_track.sink, aframe)) >= 0) {
- const double this_audio_frame_time = clock_get_monotonic_seconds();
- aframe->pts = (this_audio_frame_time - record_start_time) * (double)AV_TIME_BASE;
- const bool same_pts = aframe->pts == audio_prev_pts;
- audio_prev_pts = aframe->pts;
- if(same_pts) {
- av_frame_unref(aframe);
+ const double this_audio_frame_time = (clock_get_monotonic_seconds() - audio_startup_time_seconds) - paused_time_offset;
+ const int64_t new_pts = (this_audio_frame_time - record_start_time) * AV_TIME_BASE;
+ if(new_pts == aframe->pts)
continue;
- }
-
+ aframe->pts = new_pts;
err = avcodec_send_frame(audio_track.codec_context, aframe);
if(err >= 0){
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
- receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex);
+ receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
} else {
fprintf(stderr, "Failed to encode audio!\n");
}
@@ -1942,51 +2611,67 @@ int main(int argc, char **argv) {
frame_time_overflow = std::min(frame_time_overflow, target_fps);
frame_timer_start = time_now - frame_time_overflow;
- const double this_video_frame_time = clock_get_monotonic_seconds();
+ const double this_video_frame_time = clock_get_monotonic_seconds() - paused_time_offset;
const int64_t expected_frames = std::round((this_video_frame_time - start_time_pts) / target_fps);
- const int num_frames = framerate_mode == FramerateMode::CONSTANT ? std::max(0L, expected_frames - video_pts_counter) : 1;
+ const int num_frames = framerate_mode == FramerateMode::CONSTANT ? std::max((int64_t)0LL, expected_frames - video_pts_counter) : 1;
- if(num_frames > 0) {
- gsr_capture_capture(capture, frame);
+ if(num_frames > 0 && !paused) {
+ gsr_capture_capture(capture, video_frame);
// TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again
for(int i = 0; i < num_frames; ++i) {
if(framerate_mode == FramerateMode::CONSTANT) {
- frame->pts = video_pts_counter + i;
+ video_frame->pts = video_pts_counter + i;
} else {
- frame->pts = (this_video_frame_time - record_start_time) * (double)AV_TIME_BASE;
- const bool same_pts = frame->pts == video_prev_pts;
- video_prev_pts = frame->pts;
+ video_frame->pts = (this_video_frame_time - record_start_time) * (double)AV_TIME_BASE;
+ const bool same_pts = video_frame->pts == video_prev_pts;
+ video_prev_pts = video_frame->pts;
if(same_pts)
continue;
}
- int ret = avcodec_send_frame(video_codec_context, frame);
+ int ret = avcodec_send_frame(video_codec_context, video_frame);
if(ret == 0) {
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
- receive_frames(video_codec_context, VIDEO_STREAM_INDEX, video_stream, frame->pts, av_format_context,
- record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex);
+ receive_frames(video_codec_context, VIDEO_STREAM_INDEX, video_stream, video_frame->pts, av_format_context,
+ record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
} else {
fprintf(stderr, "Error: avcodec_send_frame failed, error: %s\n", av_error_to_string(ret));
}
}
+
+ gsr_capture_end(capture, video_frame);
video_pts_counter += num_frames;
}
}
+ if(toggle_pause == 1) {
+ const bool new_paused_state = !paused;
+ if(new_paused_state) {
+ paused_time_start = clock_get_monotonic_seconds();
+ fprintf(stderr, "Paused\n");
+ } else {
+ paused_time_offset += (clock_get_monotonic_seconds() - paused_time_start);
+ fprintf(stderr, "Unpaused\n");
+ }
+
+ toggle_pause = 0;
+ paused = !paused;
+ }
+
if(save_replay_thread.valid() && save_replay_thread.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
save_replay_thread.get();
puts(save_replay_output_filepath.c_str());
+ fflush(stdout);
+ if(recording_saved_script)
+ run_recording_saved_script_async(recording_saved_script, save_replay_output_filepath.c_str(), "replay");
std::lock_guard<std::mutex> lock(write_output_mutex);
- for(AVPacket &packet : save_replay_packets) {
- av_packet_unref(&packet);
- }
save_replay_packets.clear();
}
if(save_replay == 1 && !save_replay_thread.valid() && replay_buffer_size_secs != -1) {
save_replay = 0;
- save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, frame_data_queue, frames_erased, filename, container_format, file_extension, write_output_mutex);
+ save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, frame_data_queue, frames_erased, filename, container_format, file_extension, write_output_mutex, make_folders);
}
double frame_end = clock_get_monotonic_seconds();
@@ -2001,10 +2686,10 @@ int main(int argc, char **argv) {
if(save_replay_thread.valid()) {
save_replay_thread.get();
puts(save_replay_output_filepath.c_str());
+ fflush(stdout);
+ if(recording_saved_script)
+ run_recording_saved_script_async(recording_saved_script, save_replay_output_filepath.c_str(), "replay");
std::lock_guard<std::mutex> lock(write_output_mutex);
- for(AVPacket &packet : save_replay_packets) {
- av_packet_unref(&packet);
- }
save_replay_packets.clear();
}
@@ -2026,11 +2711,22 @@ int main(int argc, char **argv) {
gsr_capture_destroy(capture, video_codec_context);
+ if(replay_buffer_size_secs == -1 && recording_saved_script)
+ run_recording_saved_script_async(recording_saved_script, filename, "regular");
+
if(dpy) {
// TODO: This causes a crash, why? maybe some other library dlclose xlib and that also happened to unload this???
//XCloseDisplay(dpy);
}
+ //av_frame_free(&video_frame);
+ free((void*)window_str);
free(empty_audio);
+ // We do an _exit here because cuda uses at_exit to do _something_ that causes the program to freeze,
+ // but only on some nvidia driver versions on some gpus (RTX?), and _exit exits the program without calling
+ // the at_exit registered functions.
+ // Cuda (cuvid library in this case) seems to be waiting for a thread that never finishes execution.
+ // Maybe this happens because we dont clean up all ffmpeg resources?
+ // TODO: Investigate this.
_exit(should_stop_error ? 3 : 0);
}
diff --git a/src/overclock.c b/src/overclock.c
index 9160a04..2cba623 100644
--- a/src/overclock.c
+++ b/src/overclock.c
@@ -2,6 +2,7 @@
#include <X11/Xlib.h>
#include <stdio.h>
#include <string.h>
+#include <stdlib.h>
// HACK!!!: When a program uses cuda (including nvenc) then the nvidia driver drops to performance level 2 (memory transfer rate is dropped and possibly graphics clock).
// Nvidia does this because in some very extreme cases of cuda there can be memory corruption when running at max memory transfer rate.
@@ -76,13 +77,13 @@ static unsigned int attribute_type_to_attribute_param_all_levels(NvCTRLAttribute
}
// Returns 0 on error
-static int xnvctrl_get_attribute_max_value(gsr_xnvctrl *xnvctrl, const NVCTRLPerformanceLevelQuery *query, NvCTRLAttributeType attribute_type) {
+static int xnvctrl_get_attribute_max_value(gsr_xnvctrl *xnvctrl, int num_performance_levels, NvCTRLAttributeType attribute_type) {
NVCTRLAttributeValidValuesRec valid;
if(xnvctrl->XNVCTRLQueryValidTargetAttributeValues(xnvctrl->display, NV_CTRL_TARGET_TYPE_GPU, 0, 0, attribute_type_to_attribute_param_all_levels(attribute_type), &valid)) {
return valid.u.range.max;
}
- if(query->num_performance_levels > 0 && xnvctrl->XNVCTRLQueryValidTargetAttributeValues(xnvctrl->display, NV_CTRL_TARGET_TYPE_GPU, 0, query->num_performance_levels - 1, attribute_type_to_attribute_param(attribute_type), &valid)) {
+ if(num_performance_levels > 0 && xnvctrl->XNVCTRLQueryValidTargetAttributeValues(xnvctrl->display, NV_CTRL_TARGET_TYPE_GPU, 0, num_performance_levels - 1, attribute_type_to_attribute_param(attribute_type), &valid)) {
return valid.u.range.max;
}
@@ -208,6 +209,12 @@ static bool xnvctrl_get_performance_levels(gsr_xnvctrl *xnvctrl, NVCTRLPerforman
return success;
}
+static int compare_mem_transfer_rate_max_asc(const void *a, const void *b) {
+ const NVCTRLPerformanceLevel *perf_a = a;
+ const NVCTRLPerformanceLevel *perf_b = b;
+ return perf_a->mem_transfer_rate_max - perf_b->mem_transfer_rate_max;
+}
+
bool gsr_overclock_load(gsr_overclock *self, Display *display) {
memset(self, 0, sizeof(gsr_overclock));
self->num_performance_levels = 0;
@@ -234,31 +241,32 @@ bool gsr_overclock_start(gsr_overclock *self) {
}
self->num_performance_levels = query.num_performance_levels;
- int target_transfer_rate_offset = xnvctrl_get_attribute_max_value(&self->xnvctrl, &query, NVCTRL_ATTRIB_GPU_MEM_TRANSFER_RATE) / 2; // Divide by 2 just to be safe that we dont set it too high
- if(query.num_performance_levels > 2) {
- const int transfer_rate_max_diff = query.performance_level[query.num_performance_levels - 1].mem_transfer_rate_max - query.performance_level[2].mem_transfer_rate_max;
- target_transfer_rate_offset = min_int(target_transfer_rate_offset, transfer_rate_max_diff);
- }
+ qsort(query.performance_level, query.num_performance_levels, sizeof(NVCTRLPerformanceLevel), compare_mem_transfer_rate_max_asc);
- if(xnvctrl_set_attribute_offset(&self->xnvctrl, self->num_performance_levels, target_transfer_rate_offset, NVCTRL_ATTRIB_GPU_MEM_TRANSFER_RATE)) {
- fprintf(stderr, "gsr info: gsr_overclock_start: sucessfully set memory transfer rate offset to %d\n", target_transfer_rate_offset);
- } else {
- fprintf(stderr, "gsr info: gsr_overclock_start: failed to overclock memory transfer rate offset to %d\n", target_transfer_rate_offset);
+ int target_transfer_rate_offset = xnvctrl_get_attribute_max_value(&self->xnvctrl, query.num_performance_levels, NVCTRL_ATTRIB_GPU_MEM_TRANSFER_RATE);
+ if(query.num_performance_levels > 1) {
+ const int transfer_rate_max_diff = query.performance_level[query.num_performance_levels - 1].mem_transfer_rate_max - query.performance_level[query.num_performance_levels - 2].mem_transfer_rate_max;
+ target_transfer_rate_offset = min_int(target_transfer_rate_offset, transfer_rate_max_diff);
+ if(target_transfer_rate_offset >= 0 && xnvctrl_set_attribute_offset(&self->xnvctrl, self->num_performance_levels, target_transfer_rate_offset, NVCTRL_ATTRIB_GPU_MEM_TRANSFER_RATE)) {
+ fprintf(stderr, "gsr info: gsr_overclock_start: sucessfully set memory transfer rate offset to %d\n", target_transfer_rate_offset);
+ } else {
+ fprintf(stderr, "gsr info: gsr_overclock_start: failed to overclock memory transfer rate offset to %d\n", target_transfer_rate_offset);
+ }
}
+ // TODO: Sort by nv_clock_max
// TODO: Enable. Crashes on my system (gtx 1080) so it's disabled for now. Seems to crash even if graphics clock is increasd by 1, let alone 1200
/*
- int target_nv_clock_offset = xnvctrl_get_attribute_max_value(&self->xnvctrl, &query, NVCTRL_GPU_NVCLOCK) / 2; // Divide by 2 just to be safe that we dont set it too high
- if(query.num_performance_levels > 2) {
- const int nv_clock_max_diff = query.performance_level[query.num_performance_levels - 1].nv_clock_max - query.performance_level[2].nv_clock_max;
+ int target_nv_clock_offset = xnvctrl_get_attribute_max_value(&self->xnvctrl, query.num_performance_levels, NVCTRL_GPU_NVCLOCK);
+ if(query.num_performance_levels > 1) {
+ const int nv_clock_max_diff = query.performance_level[query.num_performance_levels - 1].nv_clock_max - query.performance_level[query.num_performance_levels - 2].nv_clock_max;
target_nv_clock_offset = min_int(target_nv_clock_offset, nv_clock_max_diff);
- }
-
- if(xnvctrl_set_attribute_offset(&self->xnvctrl, self->num_performance_levels, target_nv_clock_offset, NVCTRL_GPU_NVCLOCK)) {
- fprintf(stderr, "gsr info: gsr_overclock_start: sucessfully set nv clock offset to %d\n", target_nv_clock_offset);
- } else {
- fprintf(stderr, "gsr info: gsr_overclock_start: failed to overclock nv clock offset to %d\n", target_nv_clock_offset);
+ if(target_nv_clock_offset >= 0 && xnvctrl_set_attribute_offset(&self->xnvctrl, self->num_performance_levels, target_nv_clock_offset, NVCTRL_GPU_NVCLOCK)) {
+ fprintf(stderr, "gsr info: gsr_overclock_start: sucessfully set nv clock offset to %d\n", target_nv_clock_offset);
+ } else {
+ fprintf(stderr, "gsr info: gsr_overclock_start: failed to overclock nv clock offset to %d\n", target_nv_clock_offset);
+ }
}
*/
diff --git a/src/shader.c b/src/shader.c
index f8d7eb2..dcb956b 100644
--- a/src/shader.c
+++ b/src/shader.c
@@ -1,4 +1,5 @@
#include "../include/shader.h"
+#include "../include/egl.h"
#include <stdio.h>
#include <assert.h>
@@ -25,7 +26,7 @@ static unsigned int loader_shader(gsr_egl *egl, unsigned int type, const char *s
if(info_length > 1) {
char info_log[4096];
egl->glGetShaderInfoLog(shader_id, min_int(4096, info_length), NULL, info_log);
- fprintf(stderr, "gsr error: loader shader: failed to compile shader, error:\n%s\n", info_log);
+ fprintf(stderr, "gsr error: loader shader: failed to compile shader, error:\n%s\nshader source:\n%s\n", info_log, source);
}
egl->glDeleteShader(shader_id);
diff --git a/src/sound.cpp b/src/sound.cpp
index c3aa4d4..53000bd 100644
--- a/src/sound.cpp
+++ b/src/sound.cpp
@@ -41,6 +41,7 @@ struct pa_handle {
size_t output_index, output_length;
int operation_success;
+ double latency_seconds;
};
static void pa_sound_device_free(pa_handle *s) {
@@ -79,8 +80,9 @@ static pa_handle* pa_sound_device_new(const char *server,
p->read_data = NULL;
p->read_length = 0;
p->read_index = 0;
+ p->latency_seconds = 0.0;
- const int buffer_size = attr->maxlength;
+ const int buffer_size = attr->fragsize;
void *buffer = malloc(buffer_size);
if(!buffer) {
fprintf(stderr, "failed to allocate buffer for audio\n");
@@ -153,20 +155,21 @@ fail:
return NULL;
}
-// Returns a negative value on failure or if |p->output_length| data is not available within the time frame specified by the sample rate
-static int pa_sound_device_read(pa_handle *p) {
+static int pa_sound_device_read(pa_handle *p, double timeout_seconds) {
assert(p);
- const int64_t timeout_ms = std::round((1000.0 / (double)pa_stream_get_sample_spec(p->stream)->rate) * 1000.0);
const double start_time = clock_get_monotonic_seconds();
bool success = false;
int r = 0;
int *rerror = &r;
+ pa_usec_t latency = 0;
+ int negative = 0;
+
CHECK_DEAD_GOTO(p, rerror, fail);
while (p->output_index < p->output_length) {
- if((clock_get_monotonic_seconds() - start_time) * 1000 >= timeout_ms)
+ if(clock_get_monotonic_seconds() - start_time >= timeout_seconds)
return -1;
if(!p->read_data) {
@@ -195,6 +198,15 @@ static int pa_sound_device_read(pa_handle *p) {
CHECK_DEAD_GOTO(p, rerror, fail);
continue;
}
+
+ pa_operation_unref(pa_stream_update_timing_info(p->stream, NULL, NULL));
+ // TODO: Deal with one pa_stream_peek not being enough. In that case we need to add multiple of these together(?)
+ if(pa_stream_get_latency(p->stream, &latency, &negative) >= 0) {
+ p->latency_seconds = negative ? -(double)latency : latency;
+ if(p->latency_seconds < 0.0)
+ p->latency_seconds = 0.0;
+ p->latency_seconds *= 0.0000001;
+ }
}
const size_t space_free_in_output_buffer = p->output_length - p->output_index;
@@ -254,11 +266,11 @@ int sound_device_get_by_name(SoundDevice *device, const char *device_name, const
ss.channels = num_channels;
pa_buffer_attr buffer_attr;
+ buffer_attr.fragsize = period_frame_size * audio_format_to_get_bytes_per_sample(audio_format) * num_channels; // 2/4 bytes/sample, @num_channels channels
buffer_attr.tlength = -1;
buffer_attr.prebuf = -1;
buffer_attr.minreq = -1;
- buffer_attr.maxlength = period_frame_size * audio_format_to_get_bytes_per_sample(audio_format) * num_channels; // 2/4 bytes/sample, @num_channels channels
- buffer_attr.fragsize = buffer_attr.maxlength;
+ buffer_attr.maxlength = buffer_attr.fragsize;
int error = 0;
pa_handle *handle = pa_sound_device_new(nullptr, description, device_name, description, &ss, &buffer_attr, &error);
@@ -278,13 +290,15 @@ void sound_device_close(SoundDevice *device) {
device->handle = NULL;
}
-int sound_device_read_next_chunk(SoundDevice *device, void **buffer) {
+int sound_device_read_next_chunk(SoundDevice *device, void **buffer, double timeout_sec, double *latency_seconds) {
pa_handle *pa = (pa_handle*)device->handle;
- if(pa_sound_device_read(pa) < 0) {
+ if(pa_sound_device_read(pa, timeout_sec) < 0) {
//fprintf(stderr, "pa_simple_read() failed: %s\n", pa_strerror(error));
+ *latency_seconds = 0.0;
return -1;
}
*buffer = pa->output_data;
+ *latency_seconds = pa->latency_seconds;
return device->frames;
}
diff --git a/src/utils.c b/src/utils.c
index bee57f3..7dd1890 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,9 +1,13 @@
#include "../include/utils.h"
-#include "../include/egl.h"
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
+#include <fcntl.h>
+#include <xf86drmMode.h>
+#include <xf86drm.h>
+#include <stdlib.h>
+#include <X11/Xatom.h>
double clock_get_monotonic_seconds(void) {
struct timespec ts;
@@ -21,19 +25,72 @@ static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id)
return NULL;
}
-void for_each_active_monitor_output(Display *display, active_monitor_callback callback, void *userdata) {
+static gsr_monitor_rotation x11_rotation_to_gsr_rotation(int rot) {
+ switch(rot) {
+ case RR_Rotate_0: return GSR_MONITOR_ROT_0;
+ case RR_Rotate_90: return GSR_MONITOR_ROT_90;
+ case RR_Rotate_180: return GSR_MONITOR_ROT_180;
+ case RR_Rotate_270: return GSR_MONITOR_ROT_270;
+ }
+ return GSR_MONITOR_ROT_0;
+}
+
+static gsr_monitor_rotation wayland_transform_to_gsr_rotation(int32_t rot) {
+ switch(rot) {
+ case 0: return GSR_MONITOR_ROT_0;
+ case 1: return GSR_MONITOR_ROT_90;
+ case 2: return GSR_MONITOR_ROT_180;
+ case 3: return GSR_MONITOR_ROT_270;
+ }
+ return GSR_MONITOR_ROT_0;
+}
+
+static uint32_t x11_output_get_connector_id(Display *dpy, RROutput output, Atom randr_connector_id_atom) {
+ Atom type = 0;
+ int format = 0;
+ unsigned long bytes_after = 0;
+ unsigned long nitems = 0;
+ unsigned char *prop = NULL;
+ XRRGetOutputProperty(dpy, output, randr_connector_id_atom, 0, 128, false, false, AnyPropertyType, &type, &format, &nitems, &bytes_after, &prop);
+
+ long result = 0;
+ if(type == XA_INTEGER && format == 32)
+ result = *(long*)prop;
+
+ free(prop);
+ return result;
+}
+
+void for_each_active_monitor_output_x11(Display *display, active_monitor_callback callback, void *userdata) {
XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
if(!screen_res)
return;
+ const Atom randr_connector_id_atom = XInternAtom(display, "CONNECTOR_ID", False);
+
+ char display_name[256];
for(int i = 0; i < screen_res->noutput; ++i) {
XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
if(crt_info && crt_info->mode) {
const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
- if(mode_info)
- callback(out_info, crt_info, mode_info, userdata);
+ if(mode_info && out_info->nameLen < (int)sizeof(display_name)) {
+ memcpy(display_name, out_info->name, out_info->nameLen);
+ display_name[out_info->nameLen] = '\0';
+
+ const gsr_monitor monitor = {
+ .name = display_name,
+ .name_len = out_info->nameLen,
+ .pos = { .x = crt_info->x, .y = crt_info->y },
+ .size = { .x = (int)crt_info->width, .y = (int)crt_info->height },
+ .crt_info = crt_info,
+ .connector_id = x11_output_get_connector_id(display, screen_res->outputs[i], randr_connector_id_atom),
+ .rotation = x11_rotation_to_gsr_rotation(crt_info->rotation),
+ .monitor_identifier = 0
+ };
+ callback(&monitor, userdata);
+ }
}
if(crt_info)
XRRFreeCrtcInfo(crt_info);
@@ -45,36 +102,234 @@ void for_each_active_monitor_output(Display *display, active_monitor_callback ca
XRRFreeScreenResources(screen_res);
}
-static void get_monitor_by_name_callback(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata) {
- (void)mode_info;
+typedef struct {
+ int type;
+ int count;
+ int count_active;
+} drm_connector_type_count;
+
+#define CONNECTOR_TYPE_COUNTS 32
+
+static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) {
+ for(int i = 0; i < *num_type_counts; ++i) {
+ if(type_counts[i].type == connector_type)
+ return &type_counts[i];
+ }
+
+ if(*num_type_counts == CONNECTOR_TYPE_COUNTS)
+ return NULL;
+
+ const int index = *num_type_counts;
+ type_counts[index].type = connector_type;
+ type_counts[index].count = 0;
+ type_counts[index].count_active = 0;
+ ++*num_type_counts;
+ return &type_counts[index];
+}
+
+static bool connector_get_property_by_name(int drmfd, drmModeConnectorPtr props, const char *name, uint64_t *result) {
+ for(int i = 0; i < props->count_props; ++i) {
+ drmModePropertyPtr prop = drmModeGetProperty(drmfd, props->props[i]);
+ if(prop) {
+ if(strcmp(name, prop->name) == 0) {
+ *result = props->prop_values[i];
+ drmModeFreeProperty(prop);
+ return true;
+ }
+ drmModeFreeProperty(prop);
+ }
+ }
+ return false;
+}
+
+/* TODO: Support more connector types*/
+static int get_connector_type_by_name(const char *name) {
+ int len = strlen(name);
+ if(len >= 5 && strncmp(name, "HDMI-", 5) == 0)
+ return 1;
+ else if(len >= 3 && strncmp(name, "DP-", 3) == 0)
+ return 2;
+ else if(len >= 12 && strncmp(name, "DisplayPort-", 12) == 0)
+ return 3;
+ else if(len >= 4 && strncmp(name, "eDP-", 4) == 0)
+ return 4;
+ else
+ return -1;
+}
+
+static uint32_t monitor_identifier_from_type_and_count(int monitor_type_index, int monitor_type_count) {
+ return ((uint32_t)monitor_type_index << 16) | ((uint32_t)monitor_type_count);
+}
+
+static void for_each_active_monitor_output_wayland(const gsr_egl *egl, active_monitor_callback callback, void *userdata) {
+ drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS];
+ int num_type_counts = 0;
+
+ for(int i = 0; i < egl->wayland.num_outputs; ++i) {
+ const gsr_wayland_output *output = &egl->wayland.outputs[i];
+ if(!output->name)
+ continue;
+
+ const int connector_type_index = get_connector_type_by_name(output->name);
+ drm_connector_type_count *connector_type = NULL;
+ if(connector_type_index != -1)
+ connector_type = drm_connector_types_get_index(type_counts, &num_type_counts, connector_type_index);
+
+ if(connector_type) {
+ ++connector_type->count;
+ ++connector_type->count_active;
+ }
+
+ const gsr_monitor monitor = {
+ .name = output->name,
+ .name_len = strlen(output->name),
+ .pos = { .x = output->pos.x, .y = output->pos.y },
+ .size = { .x = output->size.x, .y = output->size.y },
+ .crt_info = NULL,
+ .connector_id = 0,
+ .rotation = wayland_transform_to_gsr_rotation(output->transform),
+ .monitor_identifier = connector_type ? monitor_identifier_from_type_and_count(connector_type_index, connector_type->count_active) : 0
+ };
+ callback(&monitor, userdata);
+ }
+}
+
+static void for_each_active_monitor_output_drm(const gsr_egl *egl, active_monitor_callback callback, void *userdata) {
+ int fd = open(egl->card_path, O_RDONLY);
+ if(fd == -1)
+ return;
+
+ drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
+
+ drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS];
+ int num_type_counts = 0;
+
+ char display_name[256];
+ drmModeResPtr resources = drmModeGetResources(fd);
+ if(resources) {
+ for(int i = 0; i < resources->count_connectors; ++i) {
+ drmModeConnectorPtr connector = drmModeGetConnectorCurrent(fd, resources->connectors[i]);
+ if(!connector)
+ continue;
+
+ drm_connector_type_count *connector_type = drm_connector_types_get_index(type_counts, &num_type_counts, connector->connector_type);
+ const char *connection_name = drmModeGetConnectorTypeName(connector->connector_type);
+ const int connection_name_len = strlen(connection_name);
+ if(connector_type)
+ ++connector_type->count;
+
+ if(connector->connection != DRM_MODE_CONNECTED) {
+ drmModeFreeConnector(connector);
+ continue;
+ }
+
+ if(connector_type)
+ ++connector_type->count_active;
+
+ uint64_t crtc_id = 0;
+ connector_get_property_by_name(fd, connector, "CRTC_ID", &crtc_id);
+
+ drmModeCrtcPtr crtc = drmModeGetCrtc(fd, crtc_id);
+ if(connector_type && crtc_id > 0 && crtc && connection_name_len + 5 < (int)sizeof(display_name)) {
+ const int display_name_len = snprintf(display_name, sizeof(display_name), "%s-%d", connection_name, connector_type->count);
+ const int connector_type_index_name = get_connector_type_by_name(display_name);
+ const gsr_monitor monitor = {
+ .name = display_name,
+ .name_len = display_name_len,
+ .pos = { .x = crtc->x, .y = crtc->y },
+ .size = { .x = (int)crtc->width, .y = (int)crtc->height },
+ .crt_info = NULL,
+ .connector_id = connector->connector_id,
+ .rotation = GSR_MONITOR_ROT_0,
+ .monitor_identifier = connector_type_index_name != -1 ? monitor_identifier_from_type_and_count(connector_type_index_name, connector_type->count_active) : 0
+ };
+ callback(&monitor, userdata);
+ }
+
+ if(crtc)
+ drmModeFreeCrtc(crtc);
+
+ drmModeFreeConnector(connector);
+ }
+ drmModeFreeResources(resources);
+ }
+
+ close(fd);
+}
+
+void for_each_active_monitor_output(const gsr_egl *egl, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata) {
+ switch(connection_type) {
+ case GSR_CONNECTION_X11:
+ for_each_active_monitor_output_x11(egl->x11.dpy, callback, userdata);
+ break;
+ case GSR_CONNECTION_WAYLAND:
+ for_each_active_monitor_output_wayland(egl, callback, userdata);
+ break;
+ case GSR_CONNECTION_DRM:
+ for_each_active_monitor_output_drm(egl, callback, userdata);
+ break;
+ }
+}
+
+static void get_monitor_by_name_callback(const gsr_monitor *monitor, void *userdata) {
get_monitor_by_name_userdata *data = (get_monitor_by_name_userdata*)userdata;
- if(!data->found_monitor && data->name_len == output_info->nameLen && memcmp(data->name, output_info->name, data->name_len) == 0) {
- data->monitor->pos = (vec2i){ .x = crt_info->x, .y = crt_info->y };
- data->monitor->size = (vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height };
+ if(!data->found_monitor && strcmp(data->name, monitor->name) == 0) {
+ data->monitor->pos = monitor->pos;
+ data->monitor->size = monitor->size;
+ data->monitor->connector_id = monitor->connector_id;
+ data->monitor->rotation = monitor->rotation;
+ data->monitor->monitor_identifier = monitor->monitor_identifier;
data->found_monitor = true;
}
}
-bool get_monitor_by_name(Display *display, const char *name, gsr_monitor *monitor) {
+bool get_monitor_by_name(const gsr_egl *egl, gsr_connection_type connection_type, const char *name, gsr_monitor *monitor) {
get_monitor_by_name_userdata userdata;
userdata.name = name;
userdata.name_len = strlen(name);
userdata.monitor = monitor;
userdata.found_monitor = false;
- for_each_active_monitor_output(display, get_monitor_by_name_callback, &userdata);
+ for_each_active_monitor_output(egl, connection_type, get_monitor_by_name_callback, &userdata);
return userdata.found_monitor;
}
-bool gl_get_gpu_info(Display *dpy, gsr_gpu_info *info) {
- gsr_egl gl;
- if(!gsr_egl_load(&gl, dpy)) {
- fprintf(stderr, "gsr error: failed to load opengl\n");
- return false;
+typedef struct {
+ const gsr_monitor *monitor;
+ gsr_monitor_rotation rotation;
+} get_monitor_by_connector_id_userdata;
+
+static void get_monitor_by_connector_id_callback(const gsr_monitor *monitor, void *userdata) {
+ get_monitor_by_connector_id_userdata *data = (get_monitor_by_connector_id_userdata*)userdata;
+ if(monitor->connector_id == data->monitor->connector_id ||
+ (!monitor->connector_id && monitor->monitor_identifier == data->monitor->monitor_identifier))
+ {
+ data->rotation = monitor->rotation;
+ }
+}
+
+gsr_monitor_rotation drm_monitor_get_display_server_rotation(const gsr_egl *egl, const gsr_monitor *monitor) {
+ if(egl->wayland.dpy) {
+ get_monitor_by_connector_id_userdata userdata;
+ userdata.monitor = monitor;
+ userdata.rotation = GSR_MONITOR_ROT_0;
+ for_each_active_monitor_output_wayland(egl, get_monitor_by_connector_id_callback, &userdata);
+ return userdata.rotation;
+ } else {
+ get_monitor_by_connector_id_userdata userdata;
+ userdata.monitor = monitor;
+ userdata.rotation = GSR_MONITOR_ROT_0;
+ for_each_active_monitor_output_x11(egl->x11.dpy, get_monitor_by_connector_id_callback, &userdata);
+ return userdata.rotation;
}
+ return GSR_MONITOR_ROT_0;
+}
+
+bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
+ const char *software_renderers[] = { "llvmpipe", "SWR", "softpipe", NULL };
bool supported = true;
- const unsigned char *gl_vendor = gl.glGetString(GL_VENDOR);
- const unsigned char *gl_renderer = gl.glGetString(GL_RENDERER);
+ const unsigned char *gl_vendor = egl->glGetString(GL_VENDOR);
+ const unsigned char *gl_renderer = egl->glGetString(GL_RENDERER);
info->gpu_version = 0;
@@ -84,6 +339,16 @@ bool gl_get_gpu_info(Display *dpy, gsr_gpu_info *info) {
goto end;
}
+ if(gl_renderer) {
+ for(int i = 0; software_renderers[i]; ++i) {
+ if(strstr((const char*)gl_renderer, software_renderers[i])) {
+ fprintf(stderr, "gsr error: your opengl environment is not properly setup. It's using %s (software rendering) for opengl instead of your graphics card. Please make sure your graphics driver is properly installed\n", software_renderers[i]);
+ supported = false;
+ goto end;
+ }
+ }
+ }
+
if(strstr((const char*)gl_vendor, "AMD"))
info->vendor = GSR_GPU_VENDOR_AMD;
else if(strstr((const char*)gl_vendor, "Intel"))
@@ -102,15 +367,84 @@ bool gl_get_gpu_info(Display *dpy, gsr_gpu_info *info) {
}
end:
- gsr_egl_unload(&gl);
return supported;
}
-bool gsr_get_valid_card_path(char *output) {
+static bool try_card_has_valid_plane(const char *card_path) {
+ drmVersion *ver = NULL;
+ drmModePlaneResPtr planes = NULL;
+ bool found_screen_card = false;
+
+ int fd = open(card_path, O_RDONLY);
+ if(fd == -1)
+ return false;
+
+ ver = drmGetVersion(fd);
+ if(!ver || strstr(ver->name, "nouveau"))
+ goto next;
+
+ drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+
+ planes = drmModeGetPlaneResources(fd);
+ if(!planes)
+ goto next;
+
+ for(uint32_t j = 0; j < planes->count_planes; ++j) {
+ drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[j]);
+ if(!plane)
+ continue;
+
+ if(plane->fb_id)
+ found_screen_card = true;
+
+ drmModeFreePlane(plane);
+ if(found_screen_card)
+ break;
+ }
+
+ next:
+ if(planes)
+ drmModeFreePlaneResources(planes);
+ if(ver)
+ drmFreeVersion(ver);
+ close(fd);
+ if(found_screen_card)
+ return true;
+
+ return false;
+}
+
+bool gsr_get_valid_card_path(gsr_egl *egl, char *output) {
+ if(egl->dri_card_path) {
+ strncpy(output, egl->dri_card_path, 127);
+ return try_card_has_valid_plane(output);
+ }
+
for(int i = 0; i < 10; ++i) {
- sprintf(output, "/dev/dri/card%d", i);
- if(access(output, F_OK) == 0)
+ snprintf(output, 127, DRM_DEV_NAME, DRM_DIR_NAME, i);
+ if(try_card_has_valid_plane(output))
return true;
}
return false;
}
+
+bool gsr_card_path_get_render_path(const char *card_path, char *render_path) {
+ int fd = open(card_path, O_RDONLY);
+ if(fd == -1)
+ return false;
+
+ char *render_path_tmp = drmGetRenderDeviceNameFromFd(fd);
+ if(render_path_tmp) {
+ strncpy(render_path, render_path_tmp, 127);
+ free(render_path_tmp);
+ close(fd);
+ return true;
+ }
+
+ close(fd);
+ return false;
+}
+
+int even_number_ceil(int value) {
+ return value + (value & 1);
+}
diff --git a/src/window_texture.c b/src/window_texture.c
index 7448323..0f4aa2c 100644
--- a/src/window_texture.c
+++ b/src/window_texture.c
@@ -120,4 +120,4 @@ int window_texture_on_resize(WindowTexture *self) {
unsigned int window_texture_get_opengl_texture_id(WindowTexture *self) {
return self->texture_id;
-} \ No newline at end of file
+}
diff --git a/study/color_space_transform_matrix.png b/study/color_space_transform_matrix.png
new file mode 100644
index 0000000..2b7729e5
--- /dev/null
+++ b/study/color_space_transform_matrix.png
Binary files differ
diff --git a/study/create_matrix.py b/study/create_matrix.py
new file mode 100755
index 0000000..1599a12
--- /dev/null
+++ b/study/create_matrix.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+import sys
+
+def usage():
+ print("usage: Kr Kg Kb full|limited")
+ print("examples:")
+ print(" create_matrix.py 0.2126 0.7152 0.0722 full")
+ print(" create_matrix.py 0.2126 0.7152 0.0722 limited")
+ exit(1)
+
+def a(v):
+ if v >= 0:
+ return " %f" % v
+ else:
+ return "%f" % v
+
+def main(argv):
+ if len(argv) != 5:
+ usage()
+
+ Kr = float(sys.argv[1])
+ Kg = float(sys.argv[2])
+ Kb = float(sys.argv[3])
+ color_range = sys.argv[4]
+ luma_offset = 0.0
+ transform_range = 1.0
+
+ if color_range == "full":
+ pass
+ elif color_range == "limited":
+ transform_range = (235.0 - 16.0) / 255.0
+ luma_offset = 16.0 / 255.0
+
+ matrix = [
+ [Kr, Kg, Kb],
+ [-0.5 * (Kr / (1.0 - Kb)), -0.5 * (Kg / (1.0 - Kb)), 0.5],
+ [0.5, -0.5 * (Kg / (1.0 - Kr)), -0.5 * (Kb / (1.0 -Kr))],
+ [0.0, 0.5, 0.5]
+ ]
+
+ # Transform from row major to column major for glsl
+ print("const mat4 RGBtoYUV = mat4(%f, %s, %s, %f," % (matrix[0][0] * transform_range, a(matrix[1][0] * transform_range), a(matrix[2][0] * transform_range), 0.0))
+ print(" %f, %s, %s, %f," % (matrix[0][1] * transform_range, a(matrix[1][1] * transform_range), a(matrix[2][1] * transform_range), 0.0))
+ print(" %f, %s, %s, %f," % (matrix[0][2] * transform_range, a(matrix[1][2] * transform_range), a(matrix[2][2] * transform_range), 0.0))
+ print(" %f, %s, %s, %f);" % (matrix[3][0] + luma_offset, a(matrix[3][1]), a(matrix[3][2]), 1.0))
+
+main(sys.argv)
diff --git a/uninstall.sh b/uninstall.sh
new file mode 100755
index 0000000..9457d1f
--- /dev/null
+++ b/uninstall.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+[ $(id -u) -ne 0 ] && echo "You need root privileges to run the uninstall script" && exit 1
+
+rm -f "/usr/bin/gsr-kms-server"
+rm -f "/usr/bin/gpu-screen-recorder"
+rm -f "/usr/lib/systemd/user/gpu-screen-recorder.service"
+
+echo "Successfully uninstalled gpu-screen-recorder" \ No newline at end of file