mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-22 12:40:11 +00:00
Merge branch 'master' into crct
This commit is contained in:
commit
9e93bb2dd8
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
||||
[submodule "third-party/miniupnp"]
|
||||
path = third-party/miniupnp
|
||||
url = https://github.com/miniupnp/miniupnp
|
||||
[submodule "third-party/nv-codec-headers"]
|
||||
path = third-party/nv-codec-headers
|
||||
url = https://github.com/FFmpeg/nv-codec-headers
|
||||
|
@ -4,15 +4,6 @@ project(Sunshine)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
|
||||
add_subdirectory(third-party/Simple-Web-Server)
|
||||
|
||||
set(UPNPC_BUILD_SHARED OFF CACHE BOOL "no shared libraries")
|
||||
set(UPNPC_BUILD_TESTS OFF CACHE BOOL "Don't build tests for miniupnpc")
|
||||
set(UPNPC_BUILD_SAMPLE OFF CACHE BOOL "Don't build samples for miniupnpc")
|
||||
set(UPNPC_NO_INSTALL ON CACHE BOOL "Don't install any libraries build for miniupnpc")
|
||||
add_subdirectory(third-party/miniupnp/miniupnpc)
|
||||
include_directories(third-party/miniupnp)
|
||||
|
||||
if(WIN32)
|
||||
# Ugly hack to compile with #include <qos2.h>
|
||||
add_compile_definitions(
|
||||
@ -21,11 +12,21 @@ if(WIN32)
|
||||
QOS_NON_ADAPTIVE_FLOW=2)
|
||||
endif()
|
||||
add_subdirectory(third-party/moonlight-common-c/enet)
|
||||
add_subdirectory(third-party/Simple-Web-Server)
|
||||
|
||||
set(UPNPC_BUILD_SHARED OFF CACHE BOOL "no shared libraries")
|
||||
set(UPNPC_BUILD_TESTS OFF CACHE BOOL "Don't build tests for miniupnpc")
|
||||
set(UPNPC_BUILD_SAMPLE OFF CACHE BOOL "Don't build samples for miniupnpc")
|
||||
set(UPNPC_NO_INSTALL ON CACHE BOOL "Don't install any libraries build for miniupnpc")
|
||||
add_subdirectory(third-party/miniupnp/miniupnpc)
|
||||
include_directories(third-party/miniupnp)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
find_package(Boost COMPONENTS log filesystem REQUIRED)
|
||||
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
||||
|
||||
if(WIN32)
|
||||
file(
|
||||
@ -105,12 +106,28 @@ else()
|
||||
option(SUNSHINE_ENABLE_DRM "Enable KMS grab if available" ON)
|
||||
option(SUNSHINE_ENABLE_X11 "Enable X11 grab if available" ON)
|
||||
option(SUNSHINE_ENABLE_WAYLAND "Enable building wayland specific code" ON)
|
||||
option(SUNSHINE_ENABLE_CUDA "Enable cuda specific code" ON)
|
||||
|
||||
if(${SUNSHINE_ENABLE_X11})
|
||||
find_package(X11)
|
||||
else()
|
||||
set(X11_FOUND OFF)
|
||||
endif()
|
||||
|
||||
set(CUDA_FOUND OFF)
|
||||
if(${SUNSHINE_ENABLE_CUDA})
|
||||
include(CheckLanguage)
|
||||
check_language(CUDA)
|
||||
|
||||
if(CMAKE_CUDA_COMPILER)
|
||||
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES 35)
|
||||
endif()
|
||||
|
||||
set(CUDA_FOUND ON)
|
||||
enable_language(CUDA)
|
||||
endif()
|
||||
endif()
|
||||
if(${SUNSHINE_ENABLE_DRM})
|
||||
find_package(LIBDRM)
|
||||
find_package(LIBCAP)
|
||||
@ -131,6 +148,17 @@ else()
|
||||
include_directories(${X11_INCLUDE_DIR})
|
||||
list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp)
|
||||
endif()
|
||||
|
||||
if(CUDA_FOUND)
|
||||
include_directories(third-party/nvfbc)
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
sunshine/platform/linux/cuda.cu
|
||||
sunshine/platform/linux/cuda.cpp
|
||||
third-party/nvfbc/NvFBC.h)
|
||||
|
||||
add_compile_definitions(SUNSHINE_BUILD_CUDA)
|
||||
endif()
|
||||
|
||||
if(LIBDRM_FOUND AND LIBCAP_FOUND)
|
||||
add_compile_definitions(SUNSHINE_BUILD_DRM)
|
||||
include_directories(${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS})
|
||||
@ -180,14 +208,15 @@ else()
|
||||
sunshine/platform/linux/wlgrab.cpp
|
||||
sunshine/platform/linux/wayland.cpp)
|
||||
endif()
|
||||
if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND})
|
||||
message(FATAL_ERROR "Couldn't find either x11, wayland or (libdrm and libcap)")
|
||||
if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND} AND NOT ${})
|
||||
message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)")
|
||||
endif()
|
||||
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
sunshine/platform/linux/publish.cpp
|
||||
sunshine/platform/linux/vaapi.h
|
||||
sunshine/platform/linux/vaapi.cpp
|
||||
sunshine/platform/linux/cuda.h
|
||||
sunshine/platform/linux/graphics.h
|
||||
sunshine/platform/linux/graphics.cpp
|
||||
sunshine/platform/linux/misc.h
|
||||
@ -212,6 +241,7 @@ else()
|
||||
|
||||
include_directories(
|
||||
/usr/include/libevdev-1.0
|
||||
third-party/nv-codec-headers/include
|
||||
third-party/glad/include)
|
||||
|
||||
if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH)
|
||||
@ -221,11 +251,6 @@ else()
|
||||
configure_file(sunshine.service.in sunshine.service @ONLY)
|
||||
endif()
|
||||
|
||||
add_subdirectory(third-party/cbs)
|
||||
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
find_package(Boost COMPONENTS log filesystem REQUIRED)
|
||||
|
||||
set(SUNSHINE_TARGET_FILES
|
||||
third-party/moonlight-common-c/reedsolomon/rs.c
|
||||
third-party/moonlight-common-c/reedsolomon/rs.h
|
||||
@ -285,9 +310,11 @@ include_directories(
|
||||
${PLATFORM_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
add_subdirectory(third-party/cbs)
|
||||
|
||||
string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE)
|
||||
if("${BUILD_TYPE}" STREQUAL "XDEBUG")
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -pedantic -ggdb3)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -ggdb3)
|
||||
if(WIN32)
|
||||
set_source_files_properties(sunshine/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2)
|
||||
endif()
|
||||
@ -323,6 +350,10 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
${OPENSSL_LIBRARIES}
|
||||
${PLATFORM_LIBRARIES})
|
||||
|
||||
if (NOT WIN32)
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES Boost::log)
|
||||
endif()
|
||||
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}")
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}")
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_DEFAULT_DIR="${SUNSHINE_DEFAULT_DIR}")
|
||||
@ -331,4 +362,13 @@ target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES})
|
||||
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
|
||||
set_target_properties(sunshine PROPERTIES CXX_STANDARD 17)
|
||||
|
||||
target_compile_options(sunshine PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
|
||||
if(NOT DEFINED CMAKE_CUDA_STANDARD)
|
||||
set(CMAKE_CUDA_STANDARD 17)
|
||||
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
|
||||
endif()
|
||||
|
||||
foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS_CUDA "$<$<COMPILE_LANGUAGE:CUDA>:--compiler-options=${flag}>")
|
||||
endforeach()
|
||||
|
||||
target_compile_options(sunshine PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${SUNSHINE_COMPILE_OPTIONS}>;$<$<COMPILE_LANGUAGE:CUDA>:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>)
|
||||
|
45
README.md
45
README.md
@ -13,31 +13,47 @@ Sunshine is a Gamestream host for Moonlight
|
||||
|
||||
## Linux
|
||||
|
||||
If you do not wish to clutter your PC with development files, yet you want the very latest version...
|
||||
You can use these [build scripts](scripts/README.md)
|
||||
They make use of docker to handle building Sunshine automatically
|
||||
|
||||
### Requirements:
|
||||
|
||||
Ubuntu 20.04:
|
||||
Install the following:
|
||||
#### X11 Only
|
||||
|
||||
#### Common
|
||||
```
|
||||
sudo apt install cmake gcc-10 g++-10 libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
||||
sudo apt install cmake gcc-10 g++-10 libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libevdev-dev
|
||||
```
|
||||
#### X11
|
||||
```
|
||||
sudo apt install libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
||||
```
|
||||
|
||||
#### X11 + KMS (Requires additional setup)
|
||||
KMS allows Sunshine to grab the monitor with lower latency then through X11
|
||||
#### KMS
|
||||
This requires additional [setup](README.md#Setup).
|
||||
```
|
||||
sudo apt install libdrm-dev libcap-dev
|
||||
```
|
||||
|
||||
#### Wayland
|
||||
This is for wlroots based compositores, such as Sway
|
||||
```
|
||||
sudo apt install cmake gcc-10 g++-10 libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev
|
||||
sudo apt install libwayland-dev
|
||||
```
|
||||
|
||||
#### Cuda + NvFBC
|
||||
This requires proprietary software
|
||||
On Ubuntu 20.04, the cuda compiler will fail since it's version is too old, it's recommended you compile the sources with the [build scripts](scripts/README.md)
|
||||
```
|
||||
sudo apt install nvidia-cuda-dev nvidia-cuda-toolkit
|
||||
```
|
||||
|
||||
#### Warning:
|
||||
You might require ffmpeg version >= 4.3. Check the troubleshooting section for more information.
|
||||
|
||||
### Compilation:
|
||||
|
||||
#### X11 Only
|
||||
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules`
|
||||
- `cd sunshine && mkdir build && cd build`
|
||||
- `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DSUNSHINE_ENABLE_DRM=OFF ..`
|
||||
- `make -j ${nproc}`
|
||||
|
||||
#### X11 + KMS
|
||||
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules`
|
||||
- `cd sunshine && mkdir build && cd build`
|
||||
- `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ..`
|
||||
@ -88,6 +104,9 @@ It's necessary to allow Sunshine to use KMS
|
||||
- If you get "Error: Failed to create client: Daemon not running", ensure that your avahi-daemon is running:
|
||||
- `systemctl status avahi-daemon`
|
||||
|
||||
- If you use hardware acceleration on Linux using an Intel or an AMD GPU (with VAAPI), you will get tons of [graphical issues](https://github.com/loki-47-6F-64/sunshine/issues/228) if your ffmpeg version is < 4.3. If it is not available in your distribution's repositories, consider using a newer version of your distribution.
|
||||
- Ubuntu started to ship ffmpeg 4.3 starting with groovy (20.10). If you're using an older version, you could use [this PPA](https://launchpad.net/%7Esavoury1/+archive/ubuntu/ffmpeg4) instead of upgrading. **Using PPAs is dangerous and may break your system. Use it at your own risk.**
|
||||
|
||||
## Windows 10
|
||||
|
||||
### Requirements:
|
||||
|
33
appveyor.yml
33
appveyor.yml
@ -1,33 +1,36 @@
|
||||
image:
|
||||
- Ubuntu2004
|
||||
- Visual Studio 2019
|
||||
services:
|
||||
- docker
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- BUILD_TYPE: Debug
|
||||
- BUILD_TYPE: Release
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004
|
||||
DOCKERFILE: Dockerfile-2004
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004
|
||||
DOCKERFILE: Dockerfile-2104
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004
|
||||
DOCKERFILE: Dockerfile-debian
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
BUILD_TYPE: Release
|
||||
|
||||
install:
|
||||
- sh: sudo apt update --ignore-missing
|
||||
- sh: sudo apt install -y build-essential fakeroot gcc-10 g++-10 cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev
|
||||
- cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make"
|
||||
|
||||
before_build:
|
||||
- git submodule update --init --recursive
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmd: git submodule update --init --recursive
|
||||
- cmd: mkdir build
|
||||
- cmd: cd build
|
||||
- sh: cd scripts
|
||||
- sh: ./build-container.sh -f $DOCKERFILE
|
||||
|
||||
build_script:
|
||||
- cmd: set OLDPATH=%PATH%
|
||||
- cmd: set PATH=C:\msys64\mingw64\bin
|
||||
- sh: cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine ..
|
||||
- cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" ..
|
||||
- sh: make -j$(nproc)
|
||||
- cmd: mingw32-make -j2
|
||||
- cmd: set PATH=%OLDPATH%
|
||||
- sh: ./build-sunshine.sh -pu
|
||||
|
||||
after_build:
|
||||
- sh: ./gen-deb
|
||||
- cmd: Del ..\assets\apps_linux.json
|
||||
- cmd: 7z a Sunshine-Windows.zip ..\assets
|
||||
- cmd: 7z a Sunshine-Windows.zip sunshine.exe
|
||||
@ -37,5 +40,5 @@ after_build:
|
||||
- cmd: 7z a Sunshine-Windows.zip ..\tools\install-service.bat
|
||||
- cmd: 7z a Sunshine-Windows.zip ..\tools\uninstall-service.bat
|
||||
- cmd: appveyor PushArtifact Sunshine-Windows.zip
|
||||
- sh: appveyor PushArtifact package-deb/sunshine.deb
|
||||
- sh: appveyor PushArtifact sunshine.service
|
||||
- sh: appveyor PushArtifact sunshine-build/sunshine.deb
|
||||
|
||||
|
@ -140,7 +140,7 @@
|
||||
# You can find the name of the audio sink using the following command:
|
||||
# !! Linux only !!
|
||||
# pacmd list-sinks | grep "name:" if running vanilla pulseaudio
|
||||
# pactl info | grep Source` if running pipewire
|
||||
# pPipewire: Use `pactl info | grep Source`. In some causes you'd need to use the `sink` device. Try `pactl info | grep Sink`, if _Source_ doesn't work
|
||||
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo
|
||||
#
|
||||
# !! Windows only !!
|
||||
|
@ -37,8 +37,8 @@ Package: sunshine
|
||||
Architecture: amd64
|
||||
Maintainer: @loki
|
||||
Priority: optional
|
||||
Version: 0.10.2
|
||||
Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0, libboost-log1.67.0 | libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2
|
||||
Version: 0.11.0
|
||||
Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2
|
||||
Description: Gamestream host for Moonlight
|
||||
EOF
|
||||
|
||||
|
18
scripts/Dockerfile-2004
Normal file
18
scripts/Dockerfile-2004
Normal file
@ -0,0 +1,18 @@
|
||||
FROM ubuntu:20.04 AS sunshine-2004
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
git wget gcc-10 g++-10 build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev
|
||||
|
||||
RUN cp /usr/bin/gcc-10 /usr/bin/gcc && cp /usr/bin/g++-10 /usr/bin/gcc-10
|
||||
|
||||
RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run
|
||||
RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run
|
||||
|
||||
COPY build-private.sh /root/build.sh
|
||||
|
||||
|
||||
ENTRYPOINT ["/root/build.sh"]
|
13
scripts/Dockerfile-2104
Normal file
13
scripts/Dockerfile-2104
Normal file
@ -0,0 +1,13 @@
|
||||
FROM ubuntu:21.04 AS sunshine-2104
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
git build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev nvidia-cuda-dev nvidia-cuda-toolkit
|
||||
|
||||
COPY build-private.sh /root/build.sh
|
||||
|
||||
|
||||
ENTRYPOINT ["/root/build.sh"]
|
14
scripts/Dockerfile-debian
Normal file
14
scripts/Dockerfile-debian
Normal file
@ -0,0 +1,14 @@
|
||||
FROM debian:bullseye AS sunshine-debian
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
RUN echo deb http://deb.debian.org/debian/ bullseye main contrib non-free | tee /etc/apt/sources.list.d/non-free.list
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
git build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev libcap-dev libwayland-dev nvidia-cuda-dev nvidia-cuda-toolkit
|
||||
|
||||
COPY build-private.sh /root/build.sh
|
||||
|
||||
|
||||
ENTRYPOINT ["/root/build.sh"]
|
53
scripts/README.md
Normal file
53
scripts/README.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Introduction
|
||||
Sunshine is a Gamestream host for Moonlight
|
||||
|
||||
[](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master)
|
||||
[](https://github.com/Loki-47-6F-64/sunshine/releases)
|
||||
|
||||
You may wish to simply build sunshine from source, without bloating your OS with development files.
|
||||
These scripts will create a docker images that have the necessary packages. As a result, removing the development files after you're done is a single command away.
|
||||
These scripts use docker under the hood, as such, they can only be used to compile the Linux version
|
||||
|
||||
|
||||
#### Requirements
|
||||
|
||||
```
|
||||
sudo apt install docker
|
||||
```
|
||||
|
||||
#### instructions
|
||||
|
||||
You'll require one of the following Dockerfiles:
|
||||
* Dockerfile-2004 --> Ubuntu 20.04
|
||||
* Dockerfile-2104 --> Ubuntu 21.04
|
||||
* Dockerfile-debian --> Debian Bullseye
|
||||
|
||||
Depending on your system, the build-* scripts may need root privilleges
|
||||
|
||||
First, the docker container needs to be created:
|
||||
```
|
||||
cd scripts
|
||||
./build-container.sh -f Dockerfile-<name>
|
||||
```
|
||||
|
||||
Then, the sources will be compiled and the debian package generated:
|
||||
```
|
||||
./build-sunshine -p -s ..
|
||||
```
|
||||
You can run `build-sunshine -p -s ..` again as long as the docker container exists.
|
||||
|
||||
```
|
||||
git pull
|
||||
./build-sunshine -p -s ..
|
||||
```
|
||||
|
||||
Optionally, the docker container can be removed after you're finished:
|
||||
```
|
||||
./build-container.sh -c delete
|
||||
```
|
||||
|
||||
Finally install the resulting package:
|
||||
```
|
||||
sudo apt install -f sunshine-build/sunshine.deb
|
||||
```
|
||||
|
174
scripts/build-container.sh
Executable file
174
scripts/build-container.sh
Executable file
@ -0,0 +1,174 @@
|
||||
#/bin/bash -e
|
||||
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo " -c: command --> default [build]"
|
||||
echo " | delete --> Delete the container, Dockerfile isn't mandatory"
|
||||
echo " | build --> Build the container, Dockerfile is mandatory"
|
||||
echo " | compile --> Builds the container, then compiles it. Dockerfile is mandatory"
|
||||
echo ""
|
||||
echo " -n: name: Docker container name --> default [sunshine]"
|
||||
echo " --> all: Build/Compile/Delete all available docker containers"
|
||||
echo " -f: Dockerfile: The name of the docker file"
|
||||
}
|
||||
|
||||
# Attempt to turn relative paths into absolute paths
|
||||
function absolute_path() {
|
||||
RELATIVE_PATH=$1
|
||||
if which realpath >/dev/null 2>/dev/null
|
||||
then
|
||||
RELATIVE_PATH=$(realpath $RELATIVE_PATH)
|
||||
else
|
||||
echo "Warning: realpath is not installed on your system, ensure [$1] is absolute"
|
||||
fi
|
||||
|
||||
RETURN=$RELATIVE_PATH
|
||||
}
|
||||
|
||||
CONTAINER_NAME=sunshine
|
||||
COMMAND=BUILD
|
||||
|
||||
function build_container() {
|
||||
CONTAINER_NAME=$1
|
||||
DOCKER_FILE=$2
|
||||
|
||||
if [ ! -f "$DOCKER_FILE" ]
|
||||
then
|
||||
echo "Error: $DOCKER_FILE doesn't exist"
|
||||
exit 7
|
||||
fi
|
||||
|
||||
echo "docker build . -t $CONTAINER_NAME -f $DOCKER_FILE"
|
||||
docker build . -t "$CONTAINER_NAME" -f "$DOCKER_FILE"
|
||||
}
|
||||
|
||||
function delete() {
|
||||
CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]')
|
||||
if [ "$CONTAINER_NAME_UPPER" == "ALL" ]
|
||||
then
|
||||
shopt -s nullglob
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
|
||||
if docker inspect "$CURRENT_CONTAINER" > /dev/null 2> /dev/null
|
||||
then
|
||||
echo "docker rmi $CURRENT_CONTAINER"
|
||||
docker rmi "$CURRENT_CONTAINER"
|
||||
fi
|
||||
done
|
||||
shopt -u nullglob #revert nullglob back to it's normal default state
|
||||
else
|
||||
if docker inspect "$CONTAINER_NAME" > /dev/null 2> /dev/null
|
||||
then
|
||||
echo "docker rmi $CONTAINER_NAME"
|
||||
docker rmi $CONTAINER_NAME
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function build() {
|
||||
CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]')
|
||||
if [ "$CONTAINER_NAME_UPPER" == "ALL" ]
|
||||
then
|
||||
shopt -s nullglob
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
build_container "$CURRENT_CONTAINER" "$file"
|
||||
done
|
||||
shopt -u nullglob #revert nullglob back to it's normal default state
|
||||
else
|
||||
if [[ -z "$DOCKER_FILE" ]]
|
||||
then
|
||||
echo "Error: if container name isn't equal to 'all', you need to specify the Dockerfile"
|
||||
exit 6
|
||||
fi
|
||||
|
||||
build_container "$CONTAINER_NAME" "$DOCKER_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
function abort() {
|
||||
echo "$1"
|
||||
exit 10
|
||||
}
|
||||
|
||||
function compile() {
|
||||
CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]')
|
||||
if [ "$CONTAINER_NAME_UPPER" == "ALL" ]
|
||||
then
|
||||
shopt -s nullglob
|
||||
|
||||
# If any docker container doesn't exist, we cannot compile all of them
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
|
||||
# If container doesn't exist --> abort.
|
||||
docker inspect "$CURRENT_CONTAINER" > /dev/null 2> /dev/null || abort "Error: container image [$CURRENT_CONTAINER] doesn't exist"
|
||||
done
|
||||
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
|
||||
echo "$PWD/build-sunshine.sh -p -n $CURRENT_CONTAINER"
|
||||
"$PWD/build-sunshine.sh" -p -n "$CURRENT_CONTAINER"
|
||||
done
|
||||
shopt -u nullglob #revert nullglob back to it's normal default state
|
||||
else
|
||||
# If container exists
|
||||
if docker inspect "$CURRENT_CONTAINER" > /dev/null 2> /dev/null
|
||||
then
|
||||
echo "$PWD/build-sunshine.sh -p -n $CONTAINER_NAME"
|
||||
"$PWD/build-sunshine.sh" -p -n "$CONTAINER_NAME"
|
||||
else
|
||||
echo "Error: container image [$CONTAINER_NAME] doesn't exist"
|
||||
exit 9
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
while getopts ":c:hn:f:" arg; do
|
||||
case ${arg} in
|
||||
c)
|
||||
COMMAND=$(echo $OPTARG | tr '[:lower:]' '[:upper:]')
|
||||
;;
|
||||
n)
|
||||
echo "Container name: $OPTARG"
|
||||
CONTAINER_NAME="$OPTARG"
|
||||
;;
|
||||
f)
|
||||
echo "Using Dockerfile [$OPTARG]"
|
||||
DOCKER_FILE="$OPTARG"
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "$0 set to $(echo $COMMAND | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [[ "$COMMAND" == "BUILD" ]]
|
||||
then
|
||||
echo "Start building..."
|
||||
delete
|
||||
build
|
||||
echo "Done."
|
||||
elif [[ "$COMMAND" == "COMPILE" ]]
|
||||
then
|
||||
echo "Start compiling..."
|
||||
compile
|
||||
echo "Done."
|
||||
elif [[ "$COMMAND" == "DELETE" ]]
|
||||
then
|
||||
echo "Start deleting..."
|
||||
delete
|
||||
echo "Done."
|
||||
else
|
||||
echo "Unknown command [$(echo $COMMAND | tr '[:upper:]' '[:lower:]')]"
|
||||
exit 4
|
||||
fi
|
34
scripts/build-private.sh
Executable file
34
scripts/build-private.sh
Executable file
@ -0,0 +1,34 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}"
|
||||
SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}"
|
||||
SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-/etc/sunshine}"
|
||||
|
||||
|
||||
SUNSHINE_ROOT="${SUNSHINE_ROOT:-/root/sunshine}"
|
||||
SUNSHINE_TAG="${SUNSHINE_TAG:-master}"
|
||||
SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/loki-47-6F-64/sunshine.git}"
|
||||
|
||||
|
||||
SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON}
|
||||
SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON}
|
||||
SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON}
|
||||
SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON}
|
||||
|
||||
# For debugging, it would be usefull to have the sources on the host.
|
||||
if [[ ! -d "$SUNSHINE_ROOT" ]]
|
||||
then
|
||||
git clone --depth 1 --branch "$SUNSHINE_TAG" "$SUNSHINE_GIT_URL" --recurse-submodules "$SUNSHINE_ROOT"
|
||||
fi
|
||||
|
||||
if [[ ! -d /root/sunshine-build ]]
|
||||
then
|
||||
mkdir -p /root/sunshine-build
|
||||
fi
|
||||
cd /root/sunshine-build
|
||||
|
||||
cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "$SUNSHINE_ROOT"
|
||||
|
||||
make -j ${nproc}
|
||||
|
||||
./gen-deb
|
113
scripts/build-sunshine.sh
Executable file
113
scripts/build-sunshine.sh
Executable file
@ -0,0 +1,113 @@
|
||||
#/bin/bash -e
|
||||
|
||||
function usage {
|
||||
echo "Usage: $0"
|
||||
echo " -d: Generate a debug build"
|
||||
echo " -p: Generate a debian package"
|
||||
echo " -u: The input device is not a TTY"
|
||||
echo " -n name: Docker container name --> default [sunshine]"
|
||||
echo " -s path/to/sources/sunshine: Use local sources instead of a git repository"
|
||||
echo " -c path/to/cmake/binary/dir: Store cmake output on host OS"
|
||||
}
|
||||
|
||||
# Attempt to turn relative paths into absolute paths
|
||||
function absolute_path() {
|
||||
RELATIVE_PATH=$1
|
||||
if which realpath >/dev/null 2>/dev/null
|
||||
then
|
||||
RELATIVE_PATH=$(realpath $RELATIVE_PATH)
|
||||
else
|
||||
echo "Warning: realpath is not installed on your system, ensure [$1] is absolute"
|
||||
fi
|
||||
|
||||
RETURN=$RELATIVE_PATH
|
||||
}
|
||||
|
||||
CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release"
|
||||
SUNSHINE_PACKAGE_BUILD=OFF
|
||||
SUNSHINE_GIT_URL=https://github.com/loki-47-6F-64/sunshine.git
|
||||
CONTAINER_NAME=sunshine
|
||||
|
||||
# Docker will fail if ctrl+c is passed through and the input is not a tty
|
||||
DOCKER_INTERACTIVE=-ti
|
||||
|
||||
while getopts ":dpuhc:s:n:" arg; do
|
||||
case ${arg} in
|
||||
u)
|
||||
echo "Input device is not a TTY"
|
||||
USERNAME="$USER"
|
||||
unset DOCKER_INTERACTIVE
|
||||
;;
|
||||
d)
|
||||
echo "Creating debug build"
|
||||
CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Debug"
|
||||
;;
|
||||
p)
|
||||
echo "Creating package build"
|
||||
SUNSHINE_PACKAGE_BUILD=ON
|
||||
SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=/etc/sunshine"
|
||||
SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine"
|
||||
;;
|
||||
s)
|
||||
absolute_path "$OPTARG"
|
||||
OPTARG="$RETURN"
|
||||
echo "Using sources from $OPTARG"
|
||||
SUNSHINE_ROOT="-v $OPTARG:/root/sunshine"
|
||||
;;
|
||||
c)
|
||||
[ "$USERNAME" == "" ] && USERNAME=$(logname)
|
||||
|
||||
absolute_path "$OPTARG"
|
||||
OPTARG="$RETURN"
|
||||
|
||||
echo "Using $OPTARG as cmake binary dir"
|
||||
if [[ ! -d $OPTARG ]]
|
||||
then
|
||||
echo "cmake binary dir doesn't exist, a new one will be created."
|
||||
mkdir -p "$OPTARG"
|
||||
[ "$USERNAME" == "$USER"] || chown $USERNAME:$USERNAME "$OPTARG"
|
||||
fi
|
||||
|
||||
CMAKE_ROOT="-v $OPTARG:/root/sunshine-build"
|
||||
;;
|
||||
n)
|
||||
echo "Container name: $OPTARG"
|
||||
CONTAINER_NAME=$OPTARG
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "$USERNAME" == "" ] && USERNAME=$(logname)
|
||||
|
||||
BUILD_DIR="$PWD/$CONTAINER_NAME-build"
|
||||
SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=$BUILD_DIR/assets"
|
||||
SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=$BUILD_DIR/sunshine"
|
||||
|
||||
docker run $DOCKER_INTERACTIVE --privileged $SUNSHINE_ROOT $CMAKE_ROOT $SUNSHINE_ASSETS_DIR $SUNSHINE_EXECUTABLE_PATH $CMAKE_BUILD_TYPE --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
exit_code=$?
|
||||
|
||||
if [ $exit_code -eq 0 ]
|
||||
then
|
||||
mkdir -p $BUILD_DIR
|
||||
case $SUNSHINE_PACKAGE_BUILD in
|
||||
ON)
|
||||
echo "Downloading package to: $BUILD_DIR/$CONTAINER_NAME.deb"
|
||||
docker cp $CONTAINER_NAME:/root/sunshine-build/package-deb/sunshine.deb "$BUILD_DIR/$CONTAINER_NAME.deb"
|
||||
;;
|
||||
*)
|
||||
echo "Downloading binary and assets to: $BUILD_DIR"
|
||||
docker cp $CONTAINER_NAME:/root/sunshine/assets "$BUILD_DIR"
|
||||
docker cp $CONTAINER_NAME:/root/sunshine-build/sunshine "$BUILD_DIR"
|
||||
;;
|
||||
esac
|
||||
echo "chown --recursive $USERNAME:$USERNAME $BUILD_DIR"
|
||||
chown --recursive $USERNAME:$USERNAME "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
echo "Removing docker container $CONTAINER_NAME"
|
||||
docker rm $CONTAINER_NAME
|
@ -99,16 +99,22 @@ enum quality_e : int {
|
||||
_default = 0,
|
||||
speed,
|
||||
balanced,
|
||||
//quality2,
|
||||
};
|
||||
|
||||
enum rc_e : int {
|
||||
enum class rc_hevc_e : int {
|
||||
constqp, /**< Constant QP mode */
|
||||
vbr_latency, /**< Latency Constrained Variable Bitrate */
|
||||
vbr_peak, /**< Peak Contrained Variable Bitrate */
|
||||
cbr, /**< Constant bitrate mode */
|
||||
};
|
||||
|
||||
enum class rc_h264_e : int {
|
||||
constqp, /**< Constant QP mode */
|
||||
cbr, /**< Constant bitrate mode */
|
||||
vbr_peak, /**< Peak Contrained Variable Bitrate */
|
||||
vbr_latency, /**< Latency Constrained Variable Bitrate */
|
||||
};
|
||||
|
||||
enum coder_e : int {
|
||||
_auto = 0,
|
||||
cabac,
|
||||
@ -120,15 +126,25 @@ std::optional<quality_e> quality_from_view(const std::string_view &quality) {
|
||||
if(quality == #x##sv) return x
|
||||
_CONVERT_(speed);
|
||||
_CONVERT_(balanced);
|
||||
//_CONVERT_(quality2);
|
||||
if(quality == "default"sv) return _default;
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<rc_e> rc_from_view(const std::string_view &rc) {
|
||||
std::optional<int> rc_h264_from_view(const std::string_view &rc) {
|
||||
#define _CONVERT_(x) \
|
||||
if(rc == #x##sv) return x
|
||||
if(rc == #x##sv) return (int)rc_h264_e::x
|
||||
_CONVERT_(constqp);
|
||||
_CONVERT_(vbr_latency);
|
||||
_CONVERT_(vbr_peak);
|
||||
_CONVERT_(cbr);
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> rc_hevc_from_view(const std::string_view &rc) {
|
||||
#define _CONVERT_(x) \
|
||||
if(rc == #x##sv) return (int)rc_hevc_e::x
|
||||
_CONVERT_(constqp);
|
||||
_CONVERT_(vbr_latency);
|
||||
_CONVERT_(vbr_peak);
|
||||
@ -165,6 +181,7 @@ video_t video {
|
||||
{
|
||||
amd::balanced,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
-1 }, // amd
|
||||
|
||||
{}, // encoder
|
||||
@ -659,8 +676,14 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view);
|
||||
|
||||
int_f(vars, "amd_quality", video.amd.quality, amd::quality_from_view);
|
||||
int_f(vars, "amd_rc", video.amd.rc, amd::rc_from_view);
|
||||
|
||||
std::string rc;
|
||||
string_f(vars, "amd_rc", rc);
|
||||
int_f(vars, "amd_coder", video.amd.coder, amd::coder_from_view);
|
||||
if(!rc.empty()) {
|
||||
video.amd.rc_h264 = amd::rc_h264_from_view(rc);
|
||||
video.amd.rc_hevc = amd::rc_hevc_from_view(rc);
|
||||
}
|
||||
|
||||
string_f(vars, "encoder", video.encoder);
|
||||
string_f(vars, "adapter_name", video.adapter_name);
|
||||
|
@ -29,7 +29,8 @@ struct video_t {
|
||||
|
||||
struct {
|
||||
std::optional<int> quality;
|
||||
std::optional<int> rc;
|
||||
std::optional<int> rc_h264;
|
||||
std::optional<int> rc_hevc;
|
||||
int coder;
|
||||
} amd;
|
||||
|
||||
|
@ -141,6 +141,13 @@ public:
|
||||
|
||||
struct img_t {
|
||||
public:
|
||||
img_t() = default;
|
||||
|
||||
img_t(img_t &&) = delete;
|
||||
img_t(const img_t &) = delete;
|
||||
img_t &operator=(img_t &&) = delete;
|
||||
img_t &operator=(const img_t &) = delete;
|
||||
|
||||
std::uint8_t *data {};
|
||||
std::int32_t width {};
|
||||
std::int32_t height {};
|
||||
@ -279,8 +286,8 @@ std::unique_ptr<audio_control_t> audio_control();
|
||||
*/
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
|
||||
|
||||
// A list of names of displays accepted as display_name
|
||||
std::vector<std::string> display_names();
|
||||
// A list of names of displays accepted as display_name with the mem_type_e
|
||||
std::vector<std::string> display_names(mem_type_e hwdevice_type);
|
||||
|
||||
input_t input();
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY);
|
||||
|
717
sunshine/platform/linux/cuda.cpp
Normal file
717
sunshine/platform/linux/cuda.cpp
Normal file
@ -0,0 +1,717 @@
|
||||
#include <bitset>
|
||||
|
||||
#include <NvFBC.h>
|
||||
#include <ffnvcodec/dynlink_loader.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/hwcontext_cuda.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
}
|
||||
|
||||
#include "cuda.h"
|
||||
#include "graphics.h"
|
||||
#include "sunshine/main.h"
|
||||
#include "sunshine/utility.h"
|
||||
#include "wayland.h"
|
||||
|
||||
#define SUNSHINE_STRINGVIEW_HELPER(x) x##sv
|
||||
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
|
||||
|
||||
#define CU_CHECK(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
|
||||
|
||||
#define CU_CHECK_IGNORE(x, y) \
|
||||
check((x), SUNSHINE_STRINGVIEW(y ": "))
|
||||
|
||||
using namespace std::literals;
|
||||
namespace cuda {
|
||||
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute)1;
|
||||
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute)39;
|
||||
|
||||
void pass_error(const std::string_view &sv, const char *name, const char *description) {
|
||||
BOOST_LOG(error) << sv << name << ':' << description;
|
||||
}
|
||||
|
||||
void cff(CudaFunctions *cf) {
|
||||
cuda_free_functions(&cf);
|
||||
}
|
||||
|
||||
using cdf_t = util::safe_ptr<CudaFunctions, cff>;
|
||||
|
||||
static cdf_t cdf;
|
||||
|
||||
inline static int check(CUresult result, const std::string_view &sv) {
|
||||
if(result != CUDA_SUCCESS) {
|
||||
const char *name;
|
||||
const char *description;
|
||||
|
||||
cdf->cuGetErrorName(result, &name);
|
||||
cdf->cuGetErrorString(result, &description);
|
||||
|
||||
BOOST_LOG(error) << sv << name << ':' << description;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void freeStream(CUstream stream) {
|
||||
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
|
||||
}
|
||||
|
||||
class img_t : public platf::img_t {
|
||||
public:
|
||||
tex_t tex;
|
||||
};
|
||||
|
||||
int init() {
|
||||
auto status = cuda_load_functions(&cdf, nullptr);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Couldn't load cuda: "sv << status;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
CU_CHECK(cdf->cuInit(0), "Couldn't initialize cuda");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
class cuda_t : public platf::hwdevice_t {
|
||||
public:
|
||||
int init(int in_width, int in_height) {
|
||||
if(!cdf) {
|
||||
BOOST_LOG(warning) << "cuda not initialized"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
data = (void *)0x1;
|
||||
|
||||
width = in_width;
|
||||
height = in_height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
auto hwframe_ctx = (AVHWFramesContext *)frame->hw_frames_ctx->data;
|
||||
if(hwframe_ctx->sw_format != AV_PIX_FMT_NV12) {
|
||||
BOOST_LOG(error) << "cuda::cuda_t doesn't support any format other than AV_PIX_FMT_NV12"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) {
|
||||
BOOST_LOG(error) << "Couldn't get hwframe for NVENC"sv;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto cuda_ctx = (AVCUDADeviceContext *)hwframe_ctx->device_ctx->hwctx;
|
||||
|
||||
stream = make_stream();
|
||||
if(!stream) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cuda_ctx->stream = stream.get();
|
||||
|
||||
auto sws_opt = sws_t::make(width, height, frame->width, frame->height, width * 4);
|
||||
if(!sws_opt) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sws = std::move(*sws_opt);
|
||||
|
||||
linear_interpolation = width != frame->width || height != frame->height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override {
|
||||
sws.set_colorspace(colorspace, color_range);
|
||||
|
||||
auto tex = tex_t::make(height, width * 4);
|
||||
if(!tex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The default green color is ugly.
|
||||
// Update the background color
|
||||
platf::img_t img;
|
||||
img.width = width;
|
||||
img.height = height;
|
||||
img.pixel_pitch = 4;
|
||||
img.row_pitch = img.width * img.pixel_pitch;
|
||||
|
||||
std::vector<std::uint8_t> image_data;
|
||||
image_data.resize(img.row_pitch * img.height);
|
||||
|
||||
img.data = image_data.data();
|
||||
|
||||
if(sws.load_ram(img, tex->array)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex->texture.linear, stream.get(), { frame->width, frame->height, 0, 0 });
|
||||
}
|
||||
|
||||
cudaTextureObject_t tex_obj(const tex_t &tex) const {
|
||||
return linear_interpolation ? tex.texture.linear : tex.texture.point;
|
||||
}
|
||||
|
||||
stream_t stream;
|
||||
frame_t hwframe;
|
||||
|
||||
int width, height;
|
||||
|
||||
// When heigth and width don't change, it's not necessary to use linear interpolation
|
||||
bool linear_interpolation;
|
||||
|
||||
sws_t sws;
|
||||
};
|
||||
|
||||
class cuda_ram_t : public cuda_t {
|
||||
public:
|
||||
int convert(platf::img_t &img) override {
|
||||
return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame) {
|
||||
if(cuda_t::set_frame(frame)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto tex_opt = tex_t::make(height, width * 4);
|
||||
if(!tex_opt) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tex = std::move(*tex_opt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
tex_t tex;
|
||||
};
|
||||
|
||||
class cuda_vram_t : public cuda_t {
|
||||
public:
|
||||
int convert(platf::img_t &img) override {
|
||||
return sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(((img_t *)&img)->tex), stream.get());
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, bool vram) {
|
||||
if(init()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<cuda_t> cuda;
|
||||
|
||||
if(vram) {
|
||||
cuda = std::make_shared<cuda_vram_t>();
|
||||
}
|
||||
else {
|
||||
cuda = std::make_shared<cuda_ram_t>();
|
||||
}
|
||||
|
||||
if(cuda->init(width, height)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return cuda;
|
||||
}
|
||||
|
||||
namespace nvfbc {
|
||||
static PNVFBCCREATEINSTANCE createInstance {};
|
||||
static NVFBC_API_FUNCTION_LIST func { NVFBC_VERSION };
|
||||
|
||||
static constexpr inline NVFBC_BOOL nv_bool(bool b) {
|
||||
return b ? NVFBC_TRUE : NVFBC_FALSE;
|
||||
}
|
||||
|
||||
static void *handle { nullptr };
|
||||
int init() {
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
if(funcs_loaded) return 0;
|
||||
|
||||
if(!handle) {
|
||||
handle = dyn::handle({ "libnvidia-fbc.so.1", "libnvidia-fbc.so" });
|
||||
if(!handle) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
|
||||
{ (dyn::apiproc *)&createInstance, "NvFBCCreateInstance" },
|
||||
};
|
||||
|
||||
if(dyn::load(handle, funcs)) {
|
||||
dlclose(handle);
|
||||
handle = nullptr;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto status = cuda::nvfbc::createInstance(&cuda::nvfbc::func);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Unable to create NvFBC instance"sv;
|
||||
|
||||
dlclose(handle);
|
||||
handle = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
funcs_loaded = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
class ctx_t {
|
||||
public:
|
||||
ctx_t(NVFBC_SESSION_HANDLE handle) {
|
||||
NVFBC_BIND_CONTEXT_PARAMS params { NVFBC_BIND_CONTEXT_PARAMS_VER };
|
||||
|
||||
if(func.nvFBCBindContext(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't bind NvFBC context to current thread: " << func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
|
||||
this->handle = handle;
|
||||
}
|
||||
|
||||
~ctx_t() {
|
||||
NVFBC_RELEASE_CONTEXT_PARAMS params { NVFBC_RELEASE_CONTEXT_PARAMS_VER };
|
||||
if(func.nvFBCReleaseContext(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't release NvFBC context from current thread: " << func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
}
|
||||
|
||||
NVFBC_SESSION_HANDLE handle;
|
||||
};
|
||||
|
||||
class handle_t {
|
||||
enum flag_e {
|
||||
SESSION_HANDLE,
|
||||
SESSION_CAPTURE,
|
||||
MAX_FLAGS,
|
||||
};
|
||||
|
||||
public:
|
||||
handle_t() = default;
|
||||
handle_t(handle_t &&other) : handle_flags { other.handle_flags }, handle { other.handle } {
|
||||
other.handle_flags.reset();
|
||||
}
|
||||
|
||||
handle_t &operator=(handle_t &&other) {
|
||||
std::swap(handle_flags, other.handle_flags);
|
||||
std::swap(handle, other.handle);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
static std::optional<handle_t> make() {
|
||||
NVFBC_CREATE_HANDLE_PARAMS params { NVFBC_CREATE_HANDLE_PARAMS_VER };
|
||||
|
||||
handle_t handle;
|
||||
auto status = func.nvFBCCreateHandle(&handle.handle, ¶ms);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create session: "sv << handle.last_error();
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
handle.handle_flags[SESSION_HANDLE] = true;
|
||||
|
||||
return std::move(handle);
|
||||
}
|
||||
|
||||
const char *last_error() {
|
||||
return func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
|
||||
std::optional<NVFBC_GET_STATUS_PARAMS> status() {
|
||||
NVFBC_GET_STATUS_PARAMS params { NVFBC_GET_STATUS_PARAMS_VER };
|
||||
|
||||
auto status = func.nvFBCGetStatus(handle, ¶ms);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to get NvFBC status: "sv << last_error();
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
|
||||
if(func.nvFBCCreateCaptureSession(handle, &capture_params)) {
|
||||
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
|
||||
return -1;
|
||||
}
|
||||
|
||||
handle_flags[SESSION_CAPTURE] = true;
|
||||
|
||||
NVFBC_TOCUDA_SETUP_PARAMS setup_params {
|
||||
NVFBC_TOCUDA_SETUP_PARAMS_VER,
|
||||
NVFBC_BUFFER_FORMAT_BGRA,
|
||||
};
|
||||
|
||||
if(func.nvFBCToCudaSetUp(handle, &setup_params)) {
|
||||
BOOST_LOG(error) << "Failed to setup cuda interop with nvFBC: "sv << last_error();
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stop() {
|
||||
if(!handle_flags[SESSION_CAPTURE]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS params { NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER };
|
||||
|
||||
if(func.nvFBCDestroyCaptureSession(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't destroy capture session: "sv << last_error();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
handle_flags[SESSION_CAPTURE] = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int reset() {
|
||||
if(!handle_flags[SESSION_HANDLE]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
NVFBC_DESTROY_HANDLE_PARAMS params { NVFBC_DESTROY_HANDLE_PARAMS_VER };
|
||||
|
||||
if(func.nvFBCDestroyHandle(handle, ¶ms)) {
|
||||
BOOST_LOG(error) << "Couldn't destroy session handle: "sv << func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
|
||||
handle_flags[SESSION_HANDLE] = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
~handle_t() {
|
||||
reset();
|
||||
}
|
||||
|
||||
std::bitset<MAX_FLAGS> handle_flags;
|
||||
|
||||
NVFBC_SESSION_HANDLE handle;
|
||||
};
|
||||
|
||||
class display_t : public platf::display_t {
|
||||
public:
|
||||
int init(const std::string_view &display_name, int framerate) {
|
||||
auto handle = handle_t::make();
|
||||
if(!handle) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx_t ctx { handle->handle };
|
||||
|
||||
auto status_params = handle->status();
|
||||
if(!status_params) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int streamedMonitor = -1;
|
||||
if(!display_name.empty()) {
|
||||
if(status_params->bXRandRAvailable) {
|
||||
auto monitor_nr = util::from_view(display_name);
|
||||
|
||||
if(monitor_nr < 0 || monitor_nr >= status_params->dwOutputNum) {
|
||||
BOOST_LOG(warning) << "Can't stream monitor ["sv << monitor_nr << "], it needs to be between [0] and ["sv << status_params->dwOutputNum - 1 << "], defaulting to virtual desktop"sv;
|
||||
}
|
||||
else {
|
||||
streamedMonitor = monitor_nr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(warning) << "XrandR not available, streaming entire virtual desktop"sv;
|
||||
}
|
||||
}
|
||||
|
||||
delay = std::chrono::nanoseconds { 1s } / framerate;
|
||||
|
||||
capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS { NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER };
|
||||
|
||||
capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
|
||||
capture_params.bDisableAutoModesetRecovery = nv_bool(true);
|
||||
|
||||
capture_params.dwSamplingRateMs = 1000 /* ms */ / framerate;
|
||||
|
||||
if(streamedMonitor != -1) {
|
||||
auto &output = status_params->outputs[streamedMonitor];
|
||||
|
||||
width = output.trackedBox.w;
|
||||
height = output.trackedBox.h;
|
||||
offset_x = output.trackedBox.x;
|
||||
offset_y = output.trackedBox.y;
|
||||
|
||||
capture_params.eTrackingType = NVFBC_TRACKING_OUTPUT;
|
||||
capture_params.dwOutputId = output.dwId;
|
||||
}
|
||||
else {
|
||||
capture_params.eTrackingType = NVFBC_TRACKING_SCREEN;
|
||||
|
||||
width = status_params->screenSize.w;
|
||||
height = status_params->screenSize.h;
|
||||
}
|
||||
|
||||
env_width = status_params->screenSize.w;
|
||||
env_height = status_params->screenSize.h;
|
||||
|
||||
this->handle = std::move(*handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
platf::capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<platf::img_t> img, bool *cursor) override {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
// Force display_t::capture to initialize handle_t::capture
|
||||
cursor_visible = !*cursor;
|
||||
|
||||
ctx_t ctx { handle.handle };
|
||||
auto fg = util::fail_guard([&]() {
|
||||
handle.reset();
|
||||
});
|
||||
|
||||
while(img) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if(next_frame > now) {
|
||||
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
|
||||
}
|
||||
while(next_frame > now) {
|
||||
std::this_thread::sleep_for(1ns);
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 150ms, *cursor);
|
||||
switch(status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
std::this_thread::sleep_for(1ms);
|
||||
continue;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']';
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
// Reinitialize the capture session.
|
||||
platf::capture_e reinit(bool cursor) {
|
||||
if(handle.stop()) {
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
|
||||
cursor_visible = cursor;
|
||||
if(cursor) {
|
||||
capture_params.bPushModel = nv_bool(false);
|
||||
capture_params.bWithCursor = nv_bool(true);
|
||||
capture_params.bAllowDirectCapture = nv_bool(false);
|
||||
}
|
||||
else {
|
||||
capture_params.bPushModel = nv_bool(true);
|
||||
capture_params.bWithCursor = nv_bool(false);
|
||||
capture_params.bAllowDirectCapture = nv_bool(true);
|
||||
}
|
||||
|
||||
if(handle.capture(capture_params)) {
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
|
||||
// If trying to capture directly, test if it actually does.
|
||||
if(capture_params.bAllowDirectCapture) {
|
||||
CUdeviceptr device_ptr;
|
||||
NVFBC_FRAME_GRAB_INFO info;
|
||||
|
||||
NVFBC_TOCUDA_GRAB_FRAME_PARAMS grab {
|
||||
NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER,
|
||||
NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT,
|
||||
&device_ptr,
|
||||
&info,
|
||||
0,
|
||||
};
|
||||
|
||||
// Direct Capture may fail the first few times, even if it's possible
|
||||
for(int x = 0; x < 3; ++x) {
|
||||
if(auto status = func.nvFBCToCudaGrabFrame(handle.handle, &grab)) {
|
||||
if(status == NVFBC_ERR_MUST_RECREATE) {
|
||||
return platf::capture_e::reinit;
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "Couldn't capture nvFramebuffer: "sv << handle.last_error();
|
||||
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
|
||||
if(info.bDirectCapture) {
|
||||
break;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Direct capture failed attempt ["sv << x << ']';
|
||||
}
|
||||
|
||||
if(!info.bDirectCapture) {
|
||||
BOOST_LOG(debug) << "Direct capture failed, trying the extra copy method"sv;
|
||||
// Direct capture failed
|
||||
capture_params.bPushModel = nv_bool(false);
|
||||
capture_params.bWithCursor = nv_bool(false);
|
||||
capture_params.bAllowDirectCapture = nv_bool(false);
|
||||
|
||||
if(handle.stop() || handle.capture(capture_params)) {
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
platf::capture_e snapshot(platf::img_t *img, std::chrono::milliseconds timeout, bool cursor) {
|
||||
if(cursor != cursor_visible) {
|
||||
auto status = reinit(cursor);
|
||||
if(status != platf::capture_e::ok) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
CUdeviceptr device_ptr;
|
||||
NVFBC_FRAME_GRAB_INFO info;
|
||||
|
||||
NVFBC_TOCUDA_GRAB_FRAME_PARAMS grab {
|
||||
NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER,
|
||||
NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT,
|
||||
&device_ptr,
|
||||
&info,
|
||||
(std::uint32_t)timeout.count(),
|
||||
};
|
||||
|
||||
if(auto status = func.nvFBCToCudaGrabFrame(handle.handle, &grab)) {
|
||||
if(status == NVFBC_ERR_MUST_RECREATE) {
|
||||
return platf::capture_e::reinit;
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "Couldn't capture nvFramebuffer: "sv << handle.last_error();
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
|
||||
if(((img_t *)img)->tex.copy((std::uint8_t *)device_ptr, img->height, img->row_pitch)) {
|
||||
return platf::capture_e::error;
|
||||
}
|
||||
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::hwdevice_t> make_hwdevice(platf::pix_fmt_e pix_fmt) override {
|
||||
return ::cuda::make_hwdevice(width, height, true);
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
auto img = std::make_shared<cuda::img_t>();
|
||||
|
||||
img->data = nullptr;
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->pixel_pitch = 4;
|
||||
img->row_pitch = img->width * img->pixel_pitch;
|
||||
|
||||
auto tex_opt = tex_t::make(height, width * img->pixel_pitch);
|
||||
if(!tex_opt) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
img->tex = std::move(*tex_opt);
|
||||
|
||||
return img;
|
||||
};
|
||||
|
||||
int dummy_img(platf::img_t *) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds delay;
|
||||
|
||||
bool cursor_visible;
|
||||
handle_t handle;
|
||||
|
||||
NVFBC_CREATE_CAPTURE_SESSION_PARAMS capture_params;
|
||||
};
|
||||
} // namespace nvfbc
|
||||
} // namespace cuda
|
||||
|
||||
namespace platf {
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
if(hwdevice_type != mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto display = std::make_shared<cuda::nvfbc::display_t>();
|
||||
|
||||
if(display->init(display_name, framerate)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
std::vector<std::string> nvfbc_display_names() {
|
||||
if(cuda::init() || cuda::nvfbc::init()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> display_names;
|
||||
|
||||
auto handle = cuda::nvfbc::handle_t::make();
|
||||
if(!handle) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto status_params = handle->status();
|
||||
if(!status_params) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if(!status_params->bIsCapturePossible) {
|
||||
BOOST_LOG(error) << "NVidia driver doesn't support NvFBC screencasting"sv;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Found ["sv << status_params->dwOutputNum << "] outputs"sv;
|
||||
BOOST_LOG(info) << "Virtual Desktop: "sv << status_params->screenSize.w << 'x' << status_params->screenSize.h;
|
||||
BOOST_LOG(info) << "XrandR: "sv << (status_params->bXRandRAvailable ? "available"sv : "unavailable"sv);
|
||||
|
||||
for(auto x = 0; x < status_params->dwOutputNum; ++x) {
|
||||
auto &output = status_params->outputs[x];
|
||||
BOOST_LOG(info) << "-- Output --"sv;
|
||||
BOOST_LOG(debug) << " ID: "sv << output.dwId;
|
||||
BOOST_LOG(debug) << " Name: "sv << output.name;
|
||||
BOOST_LOG(info) << " Resolution: "sv << output.trackedBox.w << 'x' << output.trackedBox.h;
|
||||
BOOST_LOG(info) << " Offset: "sv << output.trackedBox.x << 'x' << output.trackedBox.y;
|
||||
display_names.emplace_back(std::to_string(x));
|
||||
}
|
||||
|
||||
return display_names;
|
||||
}
|
||||
} // namespace platf
|
331
sunshine/platform/linux/cuda.cu
Normal file
331
sunshine/platform/linux/cuda.cu
Normal file
@ -0,0 +1,331 @@
|
||||
// #include <algorithm>
|
||||
#include <helper_math.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "cuda.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#define SUNSHINE_STRINGVIEW_HELPER(x) x##sv
|
||||
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
|
||||
|
||||
#define CU_CHECK(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return -1
|
||||
|
||||
#define CU_CHECK_VOID(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return;
|
||||
|
||||
#define CU_CHECK_PTR(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return nullptr;
|
||||
|
||||
#define CU_CHECK_OPT(x, y) \
|
||||
if(check((x), SUNSHINE_STRINGVIEW(y ": "))) return std::nullopt;
|
||||
|
||||
#define CU_CHECK_IGNORE(x, y) \
|
||||
check((x), SUNSHINE_STRINGVIEW(y ": "))
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
//////////////////// Special desclarations
|
||||
/**
|
||||
* NVCC segfaults when including <chrono>
|
||||
* Therefore, some declarations need to be added explicitely
|
||||
*/
|
||||
namespace platf {
|
||||
struct img_t {
|
||||
public:
|
||||
std::uint8_t *data {};
|
||||
std::int32_t width {};
|
||||
std::int32_t height {};
|
||||
std::int32_t pixel_pitch {};
|
||||
std::int32_t row_pitch {};
|
||||
|
||||
virtual ~img_t() = default;
|
||||
};
|
||||
} // namespace platf
|
||||
|
||||
namespace video {
|
||||
using __float4 = float[4];
|
||||
using __float3 = float[3];
|
||||
using __float2 = float[2];
|
||||
|
||||
struct __attribute__((__aligned__(16))) color_t {
|
||||
float4 color_vec_y;
|
||||
float4 color_vec_u;
|
||||
float4 color_vec_v;
|
||||
float2 range_y;
|
||||
float2 range_uv;
|
||||
};
|
||||
|
||||
struct __attribute__((__aligned__(16))) color_extern_t {
|
||||
__float4 color_vec_y;
|
||||
__float4 color_vec_u;
|
||||
__float4 color_vec_v;
|
||||
__float2 range_y;
|
||||
__float2 range_uv;
|
||||
};
|
||||
|
||||
static_assert(sizeof(video::color_t) == sizeof(video::color_extern_t), "color matrix struct mismatch");
|
||||
|
||||
extern color_t colors[4];
|
||||
} // namespace video
|
||||
|
||||
//////////////////// End special declarations
|
||||
|
||||
namespace cuda {
|
||||
auto constexpr INVALID_TEXTURE = std::numeric_limits<cudaTextureObject_t>::max();
|
||||
|
||||
template<class T>
|
||||
inline T div_align(T l, T r) {
|
||||
return (l + r - 1) / r;
|
||||
}
|
||||
|
||||
void pass_error(const std::string_view &sv, const char *name, const char *description);
|
||||
inline static int check(cudaError_t result, const std::string_view &sv) {
|
||||
if(result) {
|
||||
auto name = cudaGetErrorName(result);
|
||||
auto description = cudaGetErrorString(result);
|
||||
|
||||
pass_error(sv, name, description);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ptr_t make_ptr() {
|
||||
void *p;
|
||||
CU_CHECK_PTR(cudaMalloc(&p, sizeof(T)), "Couldn't allocate color matrix");
|
||||
|
||||
ptr_t ptr { p };
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void freeCudaPtr_t::operator()(void *ptr) {
|
||||
CU_CHECK_IGNORE(cudaFree(ptr), "Couldn't free cuda device pointer");
|
||||
}
|
||||
|
||||
void freeCudaStream_t::operator()(cudaStream_t ptr) {
|
||||
CU_CHECK_IGNORE(cudaStreamDestroy(ptr), "Couldn't free cuda stream");
|
||||
}
|
||||
|
||||
stream_t make_stream(int flags) {
|
||||
cudaStream_t stream;
|
||||
|
||||
if(!flags) {
|
||||
CU_CHECK_PTR(cudaStreamCreate(&stream), "Couldn't create cuda stream");
|
||||
}
|
||||
else {
|
||||
CU_CHECK_PTR(cudaStreamCreateWithFlags(&stream, flags), "Couldn't create cuda stream with flags");
|
||||
}
|
||||
|
||||
return stream_t { stream };
|
||||
}
|
||||
|
||||
inline __device__ float3 bgra_to_rgb(uchar4 vec) {
|
||||
return make_float3((float)vec.z, (float)vec.y, (float)vec.x);
|
||||
}
|
||||
|
||||
inline __device__ float3 bgra_to_rgb(float4 vec) {
|
||||
return make_float3(vec.z, vec.y, vec.x);
|
||||
}
|
||||
|
||||
inline __device__ float2 calcUV(float3 pixel, const video::color_t *const color_matrix) {
|
||||
float4 vec_u = color_matrix->color_vec_u;
|
||||
float4 vec_v = color_matrix->color_vec_v;
|
||||
|
||||
float u = dot(pixel, make_float3(vec_u)) + vec_u.w;
|
||||
float v = dot(pixel, make_float3(vec_v)) + vec_v.w;
|
||||
|
||||
u = u * color_matrix->range_uv.x + color_matrix->range_uv.y;
|
||||
v = (v * color_matrix->range_uv.x + color_matrix->range_uv.y) * 224.0f / 256.0f + 0.0625f;
|
||||
|
||||
return make_float2(u, v);
|
||||
}
|
||||
|
||||
inline __device__ float calcY(float3 pixel, const video::color_t *const color_matrix) {
|
||||
float4 vec_y = color_matrix->color_vec_y;
|
||||
|
||||
return (dot(pixel, make_float3(vec_y)) + vec_y.w) * color_matrix->range_y.x + color_matrix->range_y.y;
|
||||
}
|
||||
|
||||
__global__ void RGBA_to_NV12(
|
||||
cudaTextureObject_t srcImage, std::uint8_t *dstY, std::uint8_t *dstUV,
|
||||
std::uint32_t dstPitchY, std::uint32_t dstPitchUV,
|
||||
float scale, const viewport_t viewport, const video::color_t *const color_matrix) {
|
||||
|
||||
int idX = (threadIdx.x + blockDim.x * blockIdx.x) * 2;
|
||||
int idY = (threadIdx.y + blockDim.y * blockIdx.y);
|
||||
|
||||
if(idX >= viewport.width) return;
|
||||
if(idY >= viewport.height) return;
|
||||
|
||||
float x = idX * scale;
|
||||
float y = idY * scale;
|
||||
|
||||
idX += viewport.offsetX;
|
||||
idY += viewport.offsetY;
|
||||
|
||||
dstY = dstY + idX + idY * dstPitchY;
|
||||
dstUV = dstUV + idX + (idY / 2 * dstPitchUV);
|
||||
|
||||
float3 rgb_l = bgra_to_rgb(tex2D<float4>(srcImage, x, y));
|
||||
float3 rgb_r = bgra_to_rgb(tex2D<float4>(srcImage, x + scale, y));
|
||||
|
||||
float2 uv = calcUV((rgb_l + rgb_r) * 0.5f, color_matrix) * 256.0f;
|
||||
|
||||
dstUV[0] = uv.x;
|
||||
dstUV[1] = uv.y;
|
||||
dstY[0] = calcY(rgb_l, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble
|
||||
dstY[1] = calcY(rgb_r, color_matrix) * 245.0f; // 245.0f is a magic number to ensure slight changes in luminosity are more visisble
|
||||
}
|
||||
|
||||
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
|
||||
CU_CHECK(cudaMemcpy2DToArray(array, 0, 0, src, pitch, pitch, height, cudaMemcpyDeviceToDevice), "Couldn't copy to cuda array from deviceptr");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<tex_t> tex_t::make(int height, int pitch) {
|
||||
tex_t tex;
|
||||
|
||||
auto format = cudaCreateChannelDesc<uchar4>();
|
||||
CU_CHECK_OPT(cudaMallocArray(&tex.array, &format, pitch, height, cudaArrayDefault), "Couldn't allocate cuda array");
|
||||
|
||||
cudaResourceDesc res {};
|
||||
res.resType = cudaResourceTypeArray;
|
||||
res.res.array.array = tex.array;
|
||||
|
||||
cudaTextureDesc desc {};
|
||||
|
||||
desc.readMode = cudaReadModeNormalizedFloat;
|
||||
desc.filterMode = cudaFilterModePoint;
|
||||
desc.normalizedCoords = false;
|
||||
|
||||
std::fill_n(std::begin(desc.addressMode), 2, cudaAddressModeClamp);
|
||||
|
||||
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.point, &res, &desc, nullptr), "Couldn't create cuda texture that uses point interpolation");
|
||||
|
||||
desc.filterMode = cudaFilterModeLinear;
|
||||
|
||||
CU_CHECK_OPT(cudaCreateTextureObject(&tex.texture.linear, &res, &desc, nullptr), "Couldn't create cuda texture that uses linear interpolation");
|
||||
|
||||
return std::move(tex);
|
||||
}
|
||||
|
||||
tex_t::tex_t() : array {}, texture { INVALID_TEXTURE } {}
|
||||
tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } {
|
||||
other.array = 0;
|
||||
other.texture.point = INVALID_TEXTURE;
|
||||
other.texture.linear = INVALID_TEXTURE;
|
||||
}
|
||||
|
||||
tex_t &tex_t::operator=(tex_t &&other) {
|
||||
std::swap(array, other.array);
|
||||
std::swap(texture, other.texture);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
tex_t::~tex_t() {
|
||||
if(texture.point != INVALID_TEXTURE) {
|
||||
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.point), "Couldn't deallocate cuda texture that uses point interpolation");
|
||||
|
||||
texture.point = INVALID_TEXTURE;
|
||||
}
|
||||
|
||||
if(texture.linear != INVALID_TEXTURE) {
|
||||
CU_CHECK_IGNORE(cudaDestroyTextureObject(texture.linear), "Couldn't deallocate cuda texture that uses linear interpolation");
|
||||
|
||||
texture.linear = INVALID_TEXTURE;
|
||||
}
|
||||
|
||||
if(array) {
|
||||
CU_CHECK_IGNORE(cudaFreeArray(array), "Couldn't deallocate cuda array");
|
||||
|
||||
array = cudaArray_t {};
|
||||
}
|
||||
}
|
||||
|
||||
sws_t::sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix)
|
||||
: threadsPerBlock { threadsPerBlock }, color_matrix { std::move(color_matrix) } {
|
||||
// Ensure aspect ratio is maintained
|
||||
auto scalar = std::fminf(out_width / (float)in_width, out_height / (float)in_height);
|
||||
auto out_width_f = in_width * scalar;
|
||||
auto out_height_f = in_height * scalar;
|
||||
|
||||
// result is always positive
|
||||
auto offsetX_f = (out_width - out_width_f) / 2;
|
||||
auto offsetY_f = (out_height - out_height_f) / 2;
|
||||
|
||||
viewport.width = out_width_f;
|
||||
viewport.height = out_height_f;
|
||||
|
||||
viewport.offsetX = offsetX_f;
|
||||
viewport.offsetY = offsetY_f;
|
||||
|
||||
scale = 1.0f / scalar;
|
||||
}
|
||||
|
||||
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_height, int pitch) {
|
||||
cudaDeviceProp props;
|
||||
int device;
|
||||
CU_CHECK_OPT(cudaGetDevice(&device), "Couldn't get cuda device");
|
||||
CU_CHECK_OPT(cudaGetDeviceProperties(&props, device), "Couldn't get cuda device properties");
|
||||
|
||||
auto ptr = make_ptr<video::color_t>();
|
||||
if(!ptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_optional<sws_t>(in_width, in_height, out_width, out_height, pitch, props.maxThreadsPerMultiProcessor / props.maxBlocksPerMultiProcessor, std::move(ptr));
|
||||
}
|
||||
|
||||
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream) {
|
||||
return convert(Y, UV, pitchY, pitchUV, texture, stream, viewport);
|
||||
}
|
||||
|
||||
int sws_t::convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
|
||||
int threadsX = viewport.width / 2;
|
||||
int threadsY = viewport.height;
|
||||
|
||||
dim3 block(threadsPerBlock);
|
||||
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
|
||||
|
||||
RGBA_to_NV12<<<grid, block, 0, stream>>>(texture, Y, UV, pitchY, pitchUV, scale, viewport, (video::color_t *)color_matrix.get());
|
||||
|
||||
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_NV12 failed");
|
||||
}
|
||||
|
||||
void sws_t::set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {
|
||||
video::color_t *color_p;
|
||||
switch(colorspace) {
|
||||
case 5: // SWS_CS_SMPTE170M
|
||||
color_p = &video::colors[0];
|
||||
break;
|
||||
case 1: // SWS_CS_ITU709
|
||||
color_p = &video::colors[2];
|
||||
break;
|
||||
case 9: // SWS_CS_BT2020
|
||||
default:
|
||||
color_p = &video::colors[0];
|
||||
};
|
||||
|
||||
if(color_range > 1) {
|
||||
// Full range
|
||||
++color_p;
|
||||
}
|
||||
|
||||
CU_CHECK_IGNORE(cudaMemcpy(color_matrix.get(), color_p, sizeof(video::color_t), cudaMemcpyHostToDevice), "Couldn't copy color matrix to cuda");
|
||||
}
|
||||
|
||||
int sws_t::load_ram(platf::img_t &img, cudaArray_t array) {
|
||||
return CU_CHECK_IGNORE(cudaMemcpy2DToArray(array, 0, 0, img.data, img.row_pitch, img.width * img.pixel_pitch, img.height, cudaMemcpyHostToDevice), "Couldn't copy to cuda array");
|
||||
}
|
||||
|
||||
} // namespace cuda
|
107
sunshine/platform/linux/cuda.h
Normal file
107
sunshine/platform/linux/cuda.h
Normal file
@ -0,0 +1,107 @@
|
||||
#if !defined(SUNSHINE_PLATFORM_CUDA_H) && defined(SUNSHINE_BUILD_CUDA)
|
||||
#define SUNSHINE_PLATFORM_CUDA_H
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace platf {
|
||||
class hwdevice_t;
|
||||
class img_t;
|
||||
} // namespace platf
|
||||
|
||||
namespace cuda {
|
||||
|
||||
namespace nvfbc {
|
||||
std::vector<std::string> display_names();
|
||||
}
|
||||
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, bool vram);
|
||||
int init();
|
||||
} // namespace cuda
|
||||
|
||||
typedef struct cudaArray *cudaArray_t;
|
||||
|
||||
#if !defined(__CUDACC__)
|
||||
typedef struct CUstream_st *cudaStream_t;
|
||||
typedef unsigned long long cudaTextureObject_t;
|
||||
#else /* defined(__CUDACC__) */
|
||||
typedef __location__(device_builtin) struct CUstream_st *cudaStream_t;
|
||||
typedef __location__(device_builtin) unsigned long long cudaTextureObject_t;
|
||||
#endif /* !defined(__CUDACC__) */
|
||||
|
||||
namespace cuda {
|
||||
|
||||
class freeCudaPtr_t {
|
||||
public:
|
||||
void operator()(void *ptr);
|
||||
};
|
||||
|
||||
class freeCudaStream_t {
|
||||
public:
|
||||
void operator()(cudaStream_t ptr);
|
||||
};
|
||||
|
||||
using ptr_t = std::unique_ptr<void, freeCudaPtr_t>;
|
||||
using stream_t = std::unique_ptr<CUstream_st, freeCudaStream_t>;
|
||||
|
||||
stream_t make_stream(int flags = 0);
|
||||
|
||||
struct viewport_t {
|
||||
int width, height;
|
||||
int offsetX, offsetY;
|
||||
};
|
||||
|
||||
class tex_t {
|
||||
public:
|
||||
static std::optional<tex_t> make(int height, int pitch);
|
||||
|
||||
tex_t();
|
||||
tex_t(tex_t &&);
|
||||
|
||||
tex_t &operator=(tex_t &&other);
|
||||
|
||||
~tex_t();
|
||||
|
||||
int copy(std::uint8_t *src, int height, int pitch);
|
||||
|
||||
cudaArray_t array;
|
||||
|
||||
struct texture {
|
||||
cudaTextureObject_t point;
|
||||
cudaTextureObject_t linear;
|
||||
} texture;
|
||||
};
|
||||
|
||||
class sws_t {
|
||||
public:
|
||||
sws_t() = default;
|
||||
sws_t(int in_width, int in_height, int out_width, int out_height, int pitch, int threadsPerBlock, ptr_t &&color_matrix);
|
||||
|
||||
/**
|
||||
* in_width, in_height -- The width and height of the captured image in pixels
|
||||
* out_width, out_height -- the width and height of the NV12 image in pixels
|
||||
*
|
||||
* pitch -- The size of a single row of pixels in bytes
|
||||
*/
|
||||
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, int pitch);
|
||||
|
||||
// Converts loaded image into a CUDevicePtr
|
||||
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream);
|
||||
int convert(std::uint8_t *Y, std::uint8_t *UV, std::uint32_t pitchY, std::uint32_t pitchUV, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport);
|
||||
|
||||
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range);
|
||||
|
||||
int load_ram(platf::img_t &img, cudaArray_t array);
|
||||
|
||||
ptr_t color_matrix;
|
||||
|
||||
int threadsPerBlock;
|
||||
|
||||
viewport_t viewport;
|
||||
|
||||
float scale;
|
||||
};
|
||||
} // namespace cuda
|
||||
|
||||
#endif
|
@ -313,19 +313,30 @@ bool fail() {
|
||||
return eglGetError() != EGL_SUCCESS;
|
||||
}
|
||||
|
||||
display_t make_display(util::Either<gbm::gbm_t::pointer, wl_display *> native_display) {
|
||||
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
|
||||
constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7;
|
||||
constexpr auto EGL_PLATFORM_WAYLAND_KHR = 0x31D8;
|
||||
constexpr auto EGL_PLATFORM_X11_KHR = 0x31D5;
|
||||
|
||||
int egl_platform;
|
||||
void *native_display_p;
|
||||
if(native_display.has_left()) {
|
||||
|
||||
switch(native_display.index()) {
|
||||
case 0:
|
||||
egl_platform = EGL_PLATFORM_GBM_MESA;
|
||||
native_display_p = native_display.left();
|
||||
}
|
||||
else {
|
||||
native_display_p = std::get<0>(native_display);
|
||||
break;
|
||||
case 1:
|
||||
egl_platform = EGL_PLATFORM_WAYLAND_KHR;
|
||||
native_display_p = native_display.right();
|
||||
native_display_p = std::get<1>(native_display);
|
||||
break;
|
||||
case 2:
|
||||
egl_platform = EGL_PLATFORM_X11_KHR;
|
||||
native_display_p = std::get<2>(native_display);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "egl::make_display(): Index ["sv << native_display.index() << "] not implemented"sv;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// native_display.left() equals native_display.right()
|
||||
@ -728,6 +739,20 @@ std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int
|
||||
return std::move(sws);
|
||||
}
|
||||
|
||||
int sws_t::blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height) {
|
||||
auto f = [&]() {
|
||||
std::swap(offsetX, this->offsetX);
|
||||
std::swap(offsetY, this->offsetY);
|
||||
std::swap(width, this->out_width);
|
||||
std::swap(height, this->out_height);
|
||||
};
|
||||
|
||||
f();
|
||||
auto fg = util::fail_guard(f);
|
||||
|
||||
return convert(fb);
|
||||
}
|
||||
|
||||
std::optional<sws_t> sws_t::make(int in_width, int in_height, int out_width, int out_heigth) {
|
||||
auto tex = gl::tex_t::make(2);
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]);
|
||||
@ -803,7 +828,7 @@ void sws_t::load_vram(img_descriptor_t &img, int offset_x, int offset_y, int tex
|
||||
}
|
||||
}
|
||||
|
||||
int sws_t::convert(nv12_t &nv12) {
|
||||
int sws_t::convert(gl::frame_buf_t &fb) {
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, loaded_texture);
|
||||
|
||||
GLenum attachments[] {
|
||||
@ -812,7 +837,7 @@ int sws_t::convert(nv12_t &nv12) {
|
||||
};
|
||||
|
||||
for(int x = 0; x < sizeof(attachments) / sizeof(decltype(attachments[0])); ++x) {
|
||||
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]);
|
||||
gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, fb[x]);
|
||||
gl::ctx.DrawBuffers(1, &attachments[x]);
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
@ -19,6 +19,9 @@
|
||||
|
||||
extern "C" int close(int __fd);
|
||||
|
||||
// X11 Display
|
||||
extern "C" struct _XDisplay;
|
||||
|
||||
struct AVFrame;
|
||||
void free_frame(AVFrame *frame);
|
||||
|
||||
@ -227,7 +230,7 @@ struct surface_descriptor_t {
|
||||
std::uint32_t offsets[4];
|
||||
};
|
||||
|
||||
display_t make_display(util::Either<gbm::gbm_t::pointer, wl_display *> native_display);
|
||||
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
|
||||
std::optional<ctx_t> make_ctx(display_t::pointer display);
|
||||
|
||||
std::optional<rgb_t> import_source(
|
||||
@ -276,7 +279,11 @@ public:
|
||||
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_heigth, gl::tex_t &&tex);
|
||||
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_heigth);
|
||||
|
||||
int convert(nv12_t &nv12);
|
||||
// Convert the loaded image into the first two framebuffers
|
||||
int convert(gl::frame_buf_t &fb);
|
||||
|
||||
// Make an area of the image black
|
||||
int blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
void load_ram(platf::img_t &img);
|
||||
void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
|
||||
|
@ -696,7 +696,13 @@ public:
|
||||
};
|
||||
|
||||
inline void rumbleIterate(std::vector<effect_t> &effects, std::vector<pollfd_t> &polls, std::chrono::milliseconds to) {
|
||||
auto res = poll(&polls.data()->el, polls.size(), to.count());
|
||||
std::vector<pollfd> polls_tmp;
|
||||
polls_tmp.reserve(polls.size());
|
||||
for(auto &poll : polls) {
|
||||
polls_tmp.emplace_back(poll.el);
|
||||
}
|
||||
|
||||
auto res = poll(polls_tmp.data(), polls.size(), to.count());
|
||||
|
||||
// If timed out
|
||||
if(!res) {
|
||||
@ -871,7 +877,7 @@ void broadcastRumble(safe::queue_t<mail_evdev_t> &rumble_queue_queue) {
|
||||
}
|
||||
|
||||
if(polls.empty()) {
|
||||
std::this_thread::sleep_for(50ms);
|
||||
std::this_thread::sleep_for(250ms);
|
||||
}
|
||||
else {
|
||||
rumbleIterate(effects, polls, 100ms);
|
||||
|
@ -140,7 +140,11 @@ std::string get_mac_address(const std::string_view &address) {
|
||||
return "00:00:00:00:00:00"s;
|
||||
}
|
||||
|
||||
enum class source_e {
|
||||
namespace source {
|
||||
enum source_e : std::size_t {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
NVFBC,
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
WAYLAND,
|
||||
#endif
|
||||
@ -150,8 +154,20 @@ enum class source_e {
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
X11,
|
||||
#endif
|
||||
MAX_FLAGS
|
||||
};
|
||||
static source_e source;
|
||||
} // namespace source
|
||||
|
||||
static std::bitset<source::MAX_FLAGS> sources;
|
||||
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
std::vector<std::string> nvfbc_display_names();
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
|
||||
|
||||
bool verify_nvfbc() {
|
||||
return !nvfbc_display_names().empty();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
std::vector<std::string> wl_display_names();
|
||||
@ -180,40 +196,48 @@ bool verify_x11() {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<std::string> display_names() {
|
||||
switch(source) {
|
||||
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
// display using NvFBC only supports mem_type_e::cuda
|
||||
if(sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) return nvfbc_display_names();
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
case source_e::WAYLAND:
|
||||
return wl_display_names();
|
||||
if(sources[source::WAYLAND]) return wl_display_names();
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
case source_e::KMS:
|
||||
return kms_display_names();
|
||||
if(sources[source::KMS]) return kms_display_names();
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
case source_e::X11:
|
||||
return x11_display_names();
|
||||
if(sources[source::X11]) return x11_display_names();
|
||||
#endif
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
switch(source) {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
if(sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) {
|
||||
BOOST_LOG(info) << "Screencasting with NvFBC"sv;
|
||||
return nvfbc_display(hwdevice_type, display_name, framerate);
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
case source_e::WAYLAND:
|
||||
if(sources[source::WAYLAND]) {
|
||||
BOOST_LOG(info) << "Screencasting with Wayland's protocol"sv;
|
||||
return wl_display(hwdevice_type, display_name, framerate);
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
case source_e::KMS:
|
||||
if(sources[source::KMS]) {
|
||||
BOOST_LOG(info) << "Screencasting with KMS"sv;
|
||||
return kms_display(hwdevice_type, display_name, framerate);
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
case source_e::X11:
|
||||
if(sources[source::X11]) {
|
||||
BOOST_LOG(info) << "Screencasting with X11"sv;
|
||||
return x11_display(hwdevice_type, display_name, framerate);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@ -229,7 +253,7 @@ std::unique_ptr<deinit_t> init() {
|
||||
window_system = window_system_e::WAYLAND;
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
#if defined(SUNSHINE_BUILD_X11) || defined(SUNSHINE_BUILD_CUDA)
|
||||
if(std::getenv("DISPLAY") && window_system != window_system_e::WAYLAND) {
|
||||
if(std::getenv("WAYLAND_DISPLAY")) {
|
||||
BOOST_LOG(warning) << "Wayland detected, yet sunshine will use X11 for screencasting, screencasting will only work on XWayland applications"sv;
|
||||
@ -238,11 +262,14 @@ std::unique_ptr<deinit_t> init() {
|
||||
window_system = window_system_e::X11;
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
if(verify_nvfbc()) {
|
||||
sources[source::NVFBC] = true;
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
if(verify_wl()) {
|
||||
BOOST_LOG(info) << "Using Wayland for screencasting"sv;
|
||||
source = source_e::WAYLAND;
|
||||
goto found_source;
|
||||
sources[source::WAYLAND] = true;
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
@ -253,24 +280,19 @@ std::unique_ptr<deinit_t> init() {
|
||||
display_cursor = false;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Using KMS for screencasting"sv;
|
||||
source = source_e::KMS;
|
||||
goto found_source;
|
||||
sources[source::KMS] = true;
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
if(verify_x11()) {
|
||||
BOOST_LOG(info) << "Using X11 for screencasting"sv;
|
||||
source = source_e::X11;
|
||||
goto found_source;
|
||||
sources[source::X11] = true;
|
||||
}
|
||||
#endif
|
||||
// Did not find a source
|
||||
return nullptr;
|
||||
|
||||
// Normally, I would simply use if-else statements to achieve this result,
|
||||
// but due to the macro's, (*spits on ground*), it would be too messy
|
||||
found_source:
|
||||
if(sources.none()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!gladLoaderLoadEGL(EGL_NO_DISPLAY) || !eglGetPlatformDisplay) {
|
||||
BOOST_LOG(warning) << "Couldn't load EGL library"sv;
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <glad/egl.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
@ -404,7 +402,7 @@ public:
|
||||
int convert(platf::img_t &img) override {
|
||||
sws.load_ram(img);
|
||||
|
||||
sws.convert(nv12);
|
||||
sws.convert(nv12->buf);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@ -430,7 +428,7 @@ public:
|
||||
|
||||
sws.load_vram(descriptor, offset_x, offset_y, rgb->tex[0]);
|
||||
|
||||
sws.convert(nv12);
|
||||
sws.convert(nv12->buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "sunshine/main.h"
|
||||
#include "sunshine/task_pool.h"
|
||||
|
||||
#include "cuda.h"
|
||||
#include "graphics.h"
|
||||
#include "misc.h"
|
||||
#include "vaapi.h"
|
||||
@ -259,9 +260,8 @@ void freeX(XFixesCursorImage *);
|
||||
using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>;
|
||||
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
|
||||
|
||||
using xdisplay_t = util::dyn_safe_ptr_v2<Display, int, &x11::CloseDisplay>;
|
||||
using ximg_t = util::safe_ptr<XImage, freeImage>;
|
||||
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
|
||||
using ximg_t = util::safe_ptr<XImage, freeImage>;
|
||||
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
|
||||
|
||||
using crtc_info_t = util::dyn_safe_ptr<_XRRCrtcInfo, &x11::rr::FreeCrtcInfo>;
|
||||
using output_info_t = util::dyn_safe_ptr<_XRROutputInfo, &x11::rr::FreeOutputInfo>;
|
||||
@ -366,7 +366,7 @@ static void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY)
|
||||
struct x11_attr_t : public display_t {
|
||||
std::chrono::nanoseconds delay;
|
||||
|
||||
xdisplay_t xdisplay;
|
||||
x11::xdisplay_t xdisplay;
|
||||
Window xwindow;
|
||||
XWindowAttributes xattr;
|
||||
|
||||
@ -465,6 +465,7 @@ struct x11_attr_t : public display_t {
|
||||
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
|
||||
}
|
||||
while(next_frame > now) {
|
||||
std::this_thread::sleep_for(1ns);
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
next_frame = now + delay;
|
||||
@ -523,6 +524,12 @@ struct x11_attr_t : public display_t {
|
||||
return va::make_hwdevice(width, height, false);
|
||||
}
|
||||
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
if(mem_type == mem_type_e::cuda) {
|
||||
return cuda::make_hwdevice(width, height, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
return std::make_shared<hwdevice_t>();
|
||||
}
|
||||
|
||||
@ -533,7 +540,7 @@ struct x11_attr_t : public display_t {
|
||||
};
|
||||
|
||||
struct shm_attr_t : public x11_attr_t {
|
||||
xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
|
||||
x11::xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
|
||||
xcb_connect_t xcb;
|
||||
xcb_screen_t *display;
|
||||
std::uint32_t seg;
|
||||
@ -569,6 +576,7 @@ struct shm_attr_t : public x11_attr_t {
|
||||
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
|
||||
}
|
||||
while(next_frame > now) {
|
||||
std::this_thread::sleep_for(1ns);
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
next_frame = now + delay;
|
||||
@ -679,7 +687,7 @@ struct shm_attr_t : public x11_attr_t {
|
||||
|
||||
std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
||||
BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -720,7 +728,7 @@ std::vector<std::string> x11_display_names() {
|
||||
|
||||
BOOST_LOG(info) << "Detecting connected monitors"sv;
|
||||
|
||||
xdisplay_t xdisplay { x11::OpenDisplay(nullptr) };
|
||||
x11::xdisplay_t xdisplay { x11::OpenDisplay(nullptr) };
|
||||
if(!xdisplay) {
|
||||
return {};
|
||||
}
|
||||
@ -814,8 +822,16 @@ void cursor_t::blend(img_t &img, int offsetX, int offsetY) {
|
||||
blend_cursor((xdisplay_t::pointer)ctx.get(), img, offsetX, offsetY);
|
||||
}
|
||||
|
||||
xdisplay_t make_display() {
|
||||
return OpenDisplay(nullptr);
|
||||
}
|
||||
|
||||
void freeDisplay(_XDisplay *xdisplay) {
|
||||
CloseDisplay(xdisplay);
|
||||
}
|
||||
|
||||
void freeCursorCtx(cursor_ctx_t::pointer ctx) {
|
||||
x11::CloseDisplay((xdisplay_t::pointer)ctx);
|
||||
CloseDisplay((xdisplay_t::pointer)ctx);
|
||||
}
|
||||
} // namespace x11
|
||||
} // namespace platf
|
||||
|
@ -6,6 +6,9 @@
|
||||
#include "sunshine/platform/common.h"
|
||||
#include "sunshine/utility.h"
|
||||
|
||||
// X11 Display
|
||||
extern "C" struct _XDisplay;
|
||||
|
||||
namespace egl {
|
||||
class cursor_t;
|
||||
}
|
||||
@ -15,8 +18,10 @@ namespace platf::x11 {
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
struct cursor_ctx_raw_t;
|
||||
void freeCursorCtx(cursor_ctx_raw_t *ctx);
|
||||
void freeDisplay(_XDisplay *xdisplay);
|
||||
|
||||
using cursor_ctx_t = util::safe_ptr<cursor_ctx_raw_t, freeCursorCtx>;
|
||||
using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>;
|
||||
|
||||
class cursor_t {
|
||||
public:
|
||||
@ -34,7 +39,12 @@ public:
|
||||
|
||||
cursor_ctx_t ctx;
|
||||
};
|
||||
|
||||
xdisplay_t make_display();
|
||||
#else
|
||||
// It's never something different from nullptr
|
||||
util::safe_ptr<_XDisplay, std::default_delete<_XDisplay>>;
|
||||
|
||||
class cursor_t {
|
||||
public:
|
||||
static std::optional<cursor_t> make() { return std::nullopt; }
|
||||
@ -42,6 +52,8 @@ public:
|
||||
void capture(egl::cursor_t &) {}
|
||||
void blend(img_t &, int, int) {}
|
||||
};
|
||||
|
||||
xdisplay_t make_display() { return nullptr; }
|
||||
#endif
|
||||
} // namespace platf::x11
|
||||
|
||||
|
@ -452,7 +452,7 @@ std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> display_names() {
|
||||
std::vector<std::string> display_names(mem_type_e) {
|
||||
std::vector<std::string> display_names;
|
||||
|
||||
HRESULT status;
|
||||
|
@ -64,8 +64,7 @@ struct argument_type<T(U)> { typedef U type; };
|
||||
|
||||
#define KITTY_DEFAULT_CONSTR_MOVE(x) \
|
||||
x(x &&) noexcept = default; \
|
||||
x &operator=(x &&) noexcept = default; \
|
||||
x() = default;
|
||||
x &operator=(x &&) noexcept = default;
|
||||
|
||||
#define KITTY_DEFAULT_CONSTR_MOVE_THROW(x) \
|
||||
x(x &&) = default; \
|
||||
@ -415,9 +414,9 @@ inline std::int64_t from_view(const std::string_view &number) {
|
||||
}
|
||||
|
||||
template<class X, class Y>
|
||||
class Either : public std::variant<X, Y> {
|
||||
class Either : public std::variant<std::monostate, X, Y> {
|
||||
public:
|
||||
using std::variant<X, Y>::variant;
|
||||
using std::variant<std::monostate, X, Y>::variant;
|
||||
|
||||
constexpr bool has_left() const {
|
||||
return std::holds_alternative<X>(*this);
|
||||
|
@ -324,7 +324,7 @@ struct encoder_t {
|
||||
class session_t {
|
||||
public:
|
||||
session_t() = default;
|
||||
session_t(ctx_t &&ctx, util::wrap_ptr<platf::hwdevice_t> &&device, int inject) : ctx { std::move(ctx) }, device { std::move(device) }, inject { inject } {}
|
||||
session_t(ctx_t &&ctx, std::shared_ptr<platf::hwdevice_t> &&device, int inject) : ctx { std::move(ctx) }, device { std::move(device) }, inject { inject } {}
|
||||
|
||||
session_t(session_t &&other) noexcept = default;
|
||||
|
||||
@ -342,7 +342,7 @@ public:
|
||||
}
|
||||
|
||||
ctx_t ctx;
|
||||
util::wrap_ptr<platf::hwdevice_t> device;
|
||||
std::shared_ptr<platf::hwdevice_t> device;
|
||||
|
||||
std::vector<packet_raw_t::replace_t> replacements;
|
||||
|
||||
@ -369,7 +369,6 @@ struct sync_session_t {
|
||||
sync_session_ctx_t *ctx;
|
||||
|
||||
platf::img_t *img_tmp;
|
||||
std::shared_ptr<platf::hwdevice_t> hwdevice;
|
||||
session_t session;
|
||||
};
|
||||
|
||||
@ -409,13 +408,11 @@ static encoder_t nvenc {
|
||||
#ifdef _WIN32
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_PIX_FMT_D3D11,
|
||||
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
||||
#else
|
||||
AV_HWDEVICE_TYPE_CUDA,
|
||||
AV_PIX_FMT_CUDA,
|
||||
// Fully planar YUV formats are more efficient for sws_scale()
|
||||
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
|
||||
#endif
|
||||
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
||||
{
|
||||
{
|
||||
{ "forced-idr"s, 1 },
|
||||
@ -459,19 +456,19 @@ static encoder_t amdvce {
|
||||
{ "gops_per_idr"s, 30 },
|
||||
{ "usage"s, "ultralowlatency"s },
|
||||
{ "quality"s, &config::video.amd.quality },
|
||||
{ "rc"s, &config::video.amd.rc },
|
||||
{ "rc"s, &config::video.amd.rc_hevc },
|
||||
},
|
||||
std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
||||
std::make_optional<encoder_t::option_t>({ "qp_p"s, &config::video.qp }),
|
||||
"hevc_amf"s,
|
||||
},
|
||||
{
|
||||
{
|
||||
{ "usage"s, "ultralowlatency"s },
|
||||
{ "quality"s, &config::video.amd.quality },
|
||||
{ "rc"s, &config::video.amd.rc },
|
||||
{ "rc"s, &config::video.amd.rc_h264 },
|
||||
{ "log_to_dbg"s, "1"s },
|
||||
},
|
||||
std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
||||
std::make_optional<encoder_t::option_t>({ "qp_p"s, &config::video.qp }),
|
||||
"h264_amf"s,
|
||||
},
|
||||
DEFAULT,
|
||||
@ -588,7 +585,7 @@ void captureThread(
|
||||
|
||||
// Get all the monitor names now, rather than at boot, to
|
||||
// get the most up-to-date list available monitors
|
||||
auto display_names = platf::display_names();
|
||||
auto display_names = platf::display_names(map_dev_type(encoder.dev_type));
|
||||
int display_p = 0;
|
||||
|
||||
if(display_names.empty()) {
|
||||
@ -781,7 +778,7 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, safe::m
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<session_t> make_session(const encoder_t &encoder, const config_t &config, int width, int height, platf::hwdevice_t *hwdevice) {
|
||||
std::optional<session_t> make_session(const encoder_t &encoder, const config_t &config, int width, int height, std::shared_ptr<platf::hwdevice_t> &&hwdevice) {
|
||||
bool hardware = encoder.dev_type != AV_HWDEVICE_TYPE_NONE;
|
||||
|
||||
auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc;
|
||||
@ -888,7 +885,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
if(hardware) {
|
||||
ctx->pix_fmt = encoder.dev_pix_fmt;
|
||||
|
||||
auto buf_or_error = encoder.make_hwdevice_ctx(hwdevice);
|
||||
auto buf_or_error = encoder.make_hwdevice_ctx(hwdevice.get());
|
||||
if(buf_or_error.has_right()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -967,7 +964,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frames_ctx);
|
||||
}
|
||||
|
||||
util::wrap_ptr<platf::hwdevice_t> device;
|
||||
std::shared_ptr<platf::hwdevice_t> device;
|
||||
|
||||
if(!hwdevice->data) {
|
||||
auto device_tmp = std::make_unique<swdevice_t>();
|
||||
@ -979,7 +976,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
device = std::move(device_tmp);
|
||||
}
|
||||
else {
|
||||
device = hwdevice;
|
||||
device = std::move(hwdevice);
|
||||
}
|
||||
|
||||
if(device->set_frame(frame.release())) {
|
||||
@ -1011,12 +1008,12 @@ void encode_run(
|
||||
img_event_t images,
|
||||
config_t config,
|
||||
int width, int height,
|
||||
platf::hwdevice_t *hwdevice,
|
||||
std::shared_ptr<platf::hwdevice_t> &&hwdevice,
|
||||
safe::signal_t &reinit_event,
|
||||
const encoder_t &encoder,
|
||||
void *channel_data) {
|
||||
|
||||
auto session = make_session(encoder, config, width, height, hwdevice);
|
||||
auto session = make_session(encoder, config, width, height, std::move(hwdevice));
|
||||
if(!session) {
|
||||
return;
|
||||
}
|
||||
@ -1103,23 +1100,35 @@ std::optional<sync_session_t> make_synced_session(platf::display_t *disp, const
|
||||
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||
ctx.touch_port_events->raise(make_port(disp, ctx.config));
|
||||
|
||||
auto session = make_session(encoder, ctx.config, img.width, img.height, hwdevice.get());
|
||||
auto session = make_session(encoder, ctx.config, img.width, img.height, std::move(hwdevice));
|
||||
if(!session) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
encode_session.hwdevice = std::move(hwdevice);
|
||||
encode_session.session = std::move(*session);
|
||||
encode_session.session = std::move(*session);
|
||||
|
||||
return std::move(encode_session);
|
||||
}
|
||||
|
||||
encode_e encode_run_sync(
|
||||
std::vector<std::unique_ptr<sync_session_ctx_t>> &synced_session_ctxs,
|
||||
encode_session_ctx_queue_t &encode_session_ctx_queue,
|
||||
int &display_p, const std::vector<std::string> &display_names) {
|
||||
encode_session_ctx_queue_t &encode_session_ctx_queue) {
|
||||
|
||||
const auto &encoder = encoders.front();
|
||||
auto display_names = platf::display_names(map_dev_type(encoder.dev_type));
|
||||
int display_p = 0;
|
||||
|
||||
if(display_names.empty()) {
|
||||
display_names.emplace_back(config::video.output_name);
|
||||
}
|
||||
|
||||
for(int x = 0; x < display_names.size(); ++x) {
|
||||
if(display_names[x] == config::video.output_name) {
|
||||
display_p = x;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::display_t> disp;
|
||||
|
||||
@ -1210,7 +1219,7 @@ encode_e encode_run_sync(
|
||||
ctx->idr_events->pop();
|
||||
}
|
||||
|
||||
if(pos->hwdevice->convert(*img)) {
|
||||
if(pos->session.device->convert(*img)) {
|
||||
BOOST_LOG(error) << "Could not convert image"sv;
|
||||
ctx->shutdown_event->raise(true);
|
||||
|
||||
@ -1273,22 +1282,7 @@ void captureThreadSync() {
|
||||
}
|
||||
});
|
||||
|
||||
auto display_names = platf::display_names();
|
||||
int display_p = 0;
|
||||
|
||||
if(display_names.empty()) {
|
||||
display_names.emplace_back(config::video.output_name);
|
||||
}
|
||||
|
||||
for(int x = 0; x < display_names.size(); ++x) {
|
||||
if(display_names[x] == config::video.output_name) {
|
||||
display_p = x;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while(encode_run_sync(synced_session_ctxs, ctx, display_p, display_names) == encode_e::reinit) {}
|
||||
while(encode_run_sync(synced_session_ctxs, ctx) == encode_e::reinit) {}
|
||||
}
|
||||
|
||||
void capture_async(
|
||||
@ -1358,7 +1352,7 @@ void capture_async(
|
||||
frame_nr,
|
||||
mail, images,
|
||||
config, display->width, display->height,
|
||||
hwdevice.get(),
|
||||
std::move(hwdevice),
|
||||
ref->reinit_event, *ref->encoder_p,
|
||||
channel_data);
|
||||
}
|
||||
@ -1411,7 +1405,7 @@ int validate_config(std::shared_ptr<platf::display_t> &disp, const encoder_t &en
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto session = make_session(encoder, config, disp->width, disp->height, hwdevice.get());
|
||||
auto session = make_session(encoder, config, disp->width, disp->height, std::move(hwdevice));
|
||||
if(!session) {
|
||||
return -1;
|
||||
}
|
||||
@ -1703,7 +1697,7 @@ util::Either<buffer_t, int> vaapi_make_hwdevice_ctx(platf::hwdevice_t *base) {
|
||||
util::Either<buffer_t, int> cuda_make_hwdevice_ctx(platf::hwdevice_t *base) {
|
||||
buffer_t hw_device_buf;
|
||||
|
||||
auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_CUDA, nullptr, nullptr, 0);
|
||||
auto status = av_hwdevice_ctx_create(&hw_device_buf, AV_HWDEVICE_TYPE_CUDA, nullptr, nullptr, 1 /* AV_CUDA_USE_PRIMARY_CONTEXT */);
|
||||
if(status < 0) {
|
||||
char string[AV_ERROR_MAX_STRING_SIZE];
|
||||
BOOST_LOG(error) << "Failed to create a CUDA device: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status);
|
||||
|
1
third-party/nv-codec-headers
vendored
Submodule
1
third-party/nv-codec-headers
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit b641a195edbe3ac9788e681e22c2e2fad8aacddb
|
2006
third-party/nvfbc/NvFBC.h
vendored
Normal file
2006
third-party/nvfbc/NvFBC.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1469
third-party/nvfbc/helper_math.h
vendored
Normal file
1469
third-party/nvfbc/helper_math.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user