Merge branch 'master' into crct

This commit is contained in:
loki-47-6F-64 2021-10-03 19:21:02 +02:00
commit 9e93bb2dd8
32 changed files with 5387 additions and 162 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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>)

View File

@ -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:

View File

@ -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

View File

@ -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 !!

View File

@ -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
View 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
View 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
View 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
View File

@ -0,0 +1,53 @@
# Introduction
Sunshine is a Gamestream host for Moonlight
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/cgrtw2g3fq9b0b70/branch/master?svg=true)](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master)
[![Downloads](https://img.shields.io/github/downloads/Loki-47-6F-64/sunshine/total)](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
View 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
View 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
View 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

View File

@ -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);

View File

@ -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;

View File

@ -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);

View 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, &params)) {
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, &params)) {
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, &params);
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, &params);
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, &params)) {
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, &params)) {
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

View 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

View 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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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

@ -0,0 +1 @@
Subproject commit b641a195edbe3ac9788e681e22c2e2fad8aacddb

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

File diff suppressed because it is too large Load Diff