mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
@@ -1 +0,0 @@
|
||||
linux/amd64,linux/arm64/v8
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
# ignore repo directories and files
|
||||
docs/
|
||||
packaging/
|
||||
scripts/
|
||||
tools/
|
||||
crowdin.yml
|
||||
@@ -14,3 +13,6 @@ crowdin.yml
|
||||
# ignore dev directories
|
||||
build/
|
||||
venv/
|
||||
|
||||
# ignore artifacts
|
||||
artifacts/
|
||||
|
||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -9,7 +9,7 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
time: "08:00"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
@@ -17,7 +17,7 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
time: "08:30"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
@@ -25,7 +25,7 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
time: "09:00"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
@@ -33,7 +33,7 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
time: "09:30"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
@@ -41,6 +41,6 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
time: "10:00"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
36
.github/workflows/CI.yml
vendored
36
.github/workflows/CI.yml
vendored
@@ -163,9 +163,10 @@ jobs:
|
||||
cmake
|
||||
|
||||
- name: Configure PKGBUILD files
|
||||
id: prepare
|
||||
run: |
|
||||
# variables for manifest
|
||||
echo "aur_publish=false" >> $GITHUB_ENV
|
||||
aur_publish=false
|
||||
aur_pkg=sunshine-dev
|
||||
sub_version=""
|
||||
conflicts="'sunshine'"
|
||||
@@ -174,22 +175,20 @@ jobs:
|
||||
branch=${GITHUB_HEAD_REF}
|
||||
|
||||
# check the branch variable
|
||||
if [ -z "$branch" ]
|
||||
then
|
||||
if [ -z "$branch" ]; then
|
||||
echo "This is a PUSH event"
|
||||
commit=${{ github.sha }}
|
||||
clone_url=${{ github.event.repository.clone_url }}
|
||||
|
||||
if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then
|
||||
if [[ ${{ github.ref == 'refs/heads/master' }} == true ]]; then
|
||||
echo "This is a main release event"
|
||||
aur_publish=true
|
||||
aur_pkg=sunshine
|
||||
conflicts=""
|
||||
provides=""
|
||||
|
||||
echo "aur_publish=true" >> $GITHUB_ENV
|
||||
elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then
|
||||
elif [[ ${{ github.ref == 'refs/heads/nightly' }} == true ]]; then
|
||||
echo "This is a nightly release event"
|
||||
sub_version=".r${commit}"
|
||||
|
||||
echo "aur_publish=false" >> $GITHUB_ENV
|
||||
fi
|
||||
else
|
||||
echo "This is a PR event"
|
||||
@@ -201,7 +200,8 @@ jobs:
|
||||
echo "Commit: ${commit}"
|
||||
echo "Clone URL: ${clone_url}"
|
||||
|
||||
echo "aur_pkg=${aur_pkg}" >> $GITHUB_ENV
|
||||
echo "aur_publish=${aur_publish}" >> $GITHUB_OUTPUT
|
||||
echo "aur_pkg=${aur_pkg}" >> $GITHUB_OUTPUT
|
||||
|
||||
mkdir -p artifacts
|
||||
mkdir -p build
|
||||
@@ -237,10 +237,10 @@ jobs:
|
||||
path: artifacts/
|
||||
|
||||
- name: Publish AUR package
|
||||
if: ${{ env.aur_publish == 'true' }}
|
||||
if: ${{ steps.prepare.outputs.aur_publish == 'true' }}
|
||||
uses: KSXGitHub/github-actions-deploy-aur@v2.6.0
|
||||
with:
|
||||
pkgname: ${{ env.aur_pkg }}
|
||||
pkgname: ${{ steps.prepare.outputs.aur_pkg }}
|
||||
pkgbuild: ./artifacts/PKGBUILD
|
||||
assets: |
|
||||
./artifacts/*
|
||||
@@ -363,12 +363,6 @@ jobs:
|
||||
fail-fast: false # false to test all, true to fail entire job if any fail
|
||||
matrix:
|
||||
include: # package these differently
|
||||
- type: cpack
|
||||
EXTRA_ARGS: ''
|
||||
dist: 20.04
|
||||
- type: cpack
|
||||
EXTRA_ARGS: ''
|
||||
dist: 22.04
|
||||
- type: appimage
|
||||
EXTRA_ARGS: '-DSUNSHINE_CONFIGURE_APPIMAGE=ON'
|
||||
dist: 20.04
|
||||
@@ -430,6 +424,7 @@ jobs:
|
||||
libcurl4-openssl-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libmfx-dev \
|
||||
libnuma-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
@@ -459,9 +454,9 @@ jobs:
|
||||
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \
|
||||
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10
|
||||
|
||||
# Install CuDA
|
||||
# Install CUDA
|
||||
sudo wget \
|
||||
https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run \
|
||||
https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run \
|
||||
--progress=bar:force:noscroll -q --show-progress -O /root/cuda.run
|
||||
sudo chmod a+x /root/cuda.run
|
||||
sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm
|
||||
@@ -886,6 +881,7 @@ jobs:
|
||||
mingw-w64-x86_64-boost
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-curl
|
||||
mingw-w64-x86_64-libmfx
|
||||
mingw-w64-x86_64-nsis
|
||||
mingw-w64-x86_64-openssl
|
||||
mingw-w64-x86_64-opus
|
||||
|
||||
27
.github/workflows/autoupdate.yml
vendored
27
.github/workflows/autoupdate.yml
vendored
@@ -9,7 +9,7 @@
|
||||
|
||||
# It uses an action that auto-updates pull requests branches, when changes are pushed to their destination branch.
|
||||
# Auto-updating to the latest destination branch works only in the context of upstream repo and not forks.
|
||||
# Dependabot PRs are updated by an action that comments `@depdenabot rebase` on dependabot PRs.
|
||||
# Dependabot PRs are updated by an action that comments `@depdenabot rebase` on dependabot PRs. (disabled)
|
||||
|
||||
name: autoupdate
|
||||
|
||||
@@ -34,13 +34,18 @@ jobs:
|
||||
PR_READY_STATE: "all"
|
||||
MERGE_CONFLICT_ACTION: "fail"
|
||||
|
||||
dependabot-rebase:
|
||||
name: Dependabot Rebase
|
||||
if: >-
|
||||
startsWith(github.repository, 'LizardByte/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: rebase
|
||||
uses: "bbeesley/gha-auto-dependabot-rebase@v1.2.0"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
# Disabled due to:
|
||||
# - no major version tag, resulting in constant nagging to update this action
|
||||
# - additionally, the code is sketchy, 16k+ lines of code?
|
||||
# https://github.com/bbeesley/gha-auto-dependabot-rebase/blob/main/dist/main.cjs
|
||||
#
|
||||
# dependabot-rebase:
|
||||
# name: Dependabot Rebase
|
||||
# if: >-
|
||||
# startsWith(github.repository, 'LizardByte/')
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: rebase
|
||||
# uses: "bbeesley/gha-auto-dependabot-rebase@v1.3.18"
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
297
.github/workflows/ci-docker.yml
vendored
297
.github/workflows/ci-docker.yml
vendored
@@ -3,10 +3,21 @@
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
# This workflow is intended to work with all our organization Docker projects. Docker platforms/architectures should be
|
||||
# listed in a file named `.docker_platforms`, comma separated list with no spaces. A readme named `DOCKER_README.md`
|
||||
# This workflow is intended to work with all our organization Docker projects. A readme named `DOCKER_README.md`
|
||||
# will be used to update the description on Docker hub.
|
||||
|
||||
# custom comments in dockerfiles:
|
||||
|
||||
# `# platforms: `
|
||||
# Comma separated list of platforms, i.e. `# platforms: linux/386,linux/amd64`. Docker platforms can alternatively
|
||||
# be listed in a file named `.docker_platforms`.
|
||||
# `# platforms_pr: `
|
||||
# Comma separated list of platforms to run for PR events, i.e. `# platforms_pr: linux/amd64`. This will take
|
||||
# precedence over the `# platforms: ` directive.
|
||||
# `# artifacts: `
|
||||
# `true` to build in two steps, stopping at `artifacts` build stage and extracting the image from there to the
|
||||
# GitHub runner.
|
||||
|
||||
name: CI Docker
|
||||
|
||||
on:
|
||||
@@ -22,56 +33,55 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check_dockerfile:
|
||||
name: Check Dockerfile
|
||||
check_dockerfiles:
|
||||
name: Check Dockerfiles
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check
|
||||
id: check
|
||||
- name: Find dockerfiles
|
||||
id: find
|
||||
run: |
|
||||
if [ -f "./Dockerfile" ]
|
||||
then
|
||||
FOUND=true
|
||||
else
|
||||
FOUND=false
|
||||
fi
|
||||
dockerfiles=$(find . -type f -iname "Dockerfile" -o -iname "*.dockerfile")
|
||||
|
||||
echo "dockerfile=${FOUND}" >> $GITHUB_OUTPUT
|
||||
echo "found dockerfiles: ${dockerfiles}"
|
||||
|
||||
# do not quote to keep this as a single line
|
||||
echo dockerfiles=${dockerfiles} >> $GITHUB_OUTPUT
|
||||
|
||||
MATRIX_COMBINATIONS=""
|
||||
for FILE in ${dockerfiles}; do
|
||||
# extract tag from file name
|
||||
tag=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*\/(Dockerfile)/None/gm')
|
||||
if [[ $tag == "None" ]]; then
|
||||
MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS {\"dockerfile\": \"$FILE\"},"
|
||||
else
|
||||
tag=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*\/(.+)(\.dockerfile)/-\2/gm')
|
||||
MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS {\"dockerfile\": \"$FILE\", \"tag\": \"$tag\"},"
|
||||
fi
|
||||
done
|
||||
|
||||
# removes the last character (i.e. comma)
|
||||
MATRIX_COMBINATIONS=${MATRIX_COMBINATIONS::-1}
|
||||
|
||||
# setup matrix for later jobs
|
||||
matrix=$((
|
||||
echo "{ \"include\": [$MATRIX_COMBINATIONS] }"
|
||||
) | jq -c .)
|
||||
|
||||
echo $matrix
|
||||
echo $matrix | jq .
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
outputs:
|
||||
dockerfile: ${{ steps.check.outputs.dockerfile }}
|
||||
|
||||
lint_dockerfile:
|
||||
name: Lint Dockerfile
|
||||
needs: [check_dockerfile]
|
||||
if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Hadolint
|
||||
id: hadolint
|
||||
uses: hadolint/hadolint-action@v3.0.0
|
||||
with:
|
||||
dockerfile: ./Dockerfile
|
||||
ignore: DL3008,DL3013,DL3016,DL3018,DL3028,DL3059
|
||||
output-file: ./hadolint.log
|
||||
verbose: true
|
||||
|
||||
- name: Log
|
||||
if: failure()
|
||||
run: |
|
||||
echo "Hadolint outcome: ${{ steps.hadolint.outcome }}" >> $GITHUB_STEP_SUMMARY
|
||||
cat "./hadolint.log" >> $GITHUB_STEP_SUMMARY
|
||||
dockerfiles: ${{ steps.find.outputs.dockerfiles }}
|
||||
matrix: ${{ steps.find.outputs.matrix }}
|
||||
|
||||
check_changelog:
|
||||
name: Check Changelog
|
||||
needs: [check_dockerfile]
|
||||
if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }}
|
||||
needs: [check_dockerfiles]
|
||||
if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -87,15 +97,99 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
outputs:
|
||||
next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }}
|
||||
next_version_bare: ${{ steps.verify_changelog.outputs.changelog_parser_version_bare }}
|
||||
last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }}
|
||||
release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }}
|
||||
|
||||
setup_release:
|
||||
name: Setup Release
|
||||
needs: check_changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set release details
|
||||
id: release_details
|
||||
env:
|
||||
RELEASE_BODY: ${{ needs.check_changelog.outputs.release_body }}
|
||||
run: |
|
||||
# determine to create a release or not
|
||||
if [[ $GITHUB_EVENT_NAME == "push" ]]; then
|
||||
RELEASE=true
|
||||
else
|
||||
RELEASE=false
|
||||
fi
|
||||
|
||||
# set the release tag
|
||||
COMMIT=${{ github.sha }}
|
||||
if [[ $GITHUB_REF == refs/heads/master ]]; then
|
||||
TAG="${{ needs.check_changelog.outputs.next_version }}"
|
||||
RELEASE_NAME="${{ needs.check_changelog.outputs.next_version }}"
|
||||
RELEASE_BODY="$RELEASE_BODY"
|
||||
PRE_RELEASE="false"
|
||||
elif [[ $GITHUB_REF == refs/heads/nightly ]]; then
|
||||
TAG="nightly-dev"
|
||||
RELEASE_NAME="nightly"
|
||||
RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ') - ${COMMIT}"
|
||||
PRE_RELEASE="true"
|
||||
fi
|
||||
|
||||
echo "create_release=${RELEASE}" >> $GITHUB_OUTPUT
|
||||
echo "release_tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "release_commit=${COMMIT}" >> $GITHUB_OUTPUT
|
||||
echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "pre_release=${PRE_RELEASE}" >> $GITHUB_OUTPUT
|
||||
|
||||
# this is stupid but works for multiline strings
|
||||
echo "RELEASE_BODY<<EOF" >> $GITHUB_ENV
|
||||
echo "$RELEASE_BODY" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
outputs:
|
||||
create_release: ${{ steps.release_details.outputs.create_release }}
|
||||
release_tag: ${{ steps.release_details.outputs.release_tag }}
|
||||
release_commit: ${{ steps.release_details.outputs.release_commit }}
|
||||
release_name: ${{ steps.release_details.outputs.release_name }}
|
||||
release_body: ${{ env.RELEASE_BODY }}
|
||||
pre_release: ${{ steps.release_details.outputs.pre_release }}
|
||||
|
||||
lint_dockerfile:
|
||||
needs: [check_dockerfiles]
|
||||
if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.check_dockerfiles.outputs.matrix) }}
|
||||
name: Lint Dockerfile${{ matrix.tag }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Hadolint
|
||||
id: hadolint
|
||||
uses: hadolint/hadolint-action@v3.1.0
|
||||
with:
|
||||
dockerfile: ${{ matrix.dockerfile }}
|
||||
ignore: DL3008,DL3013,DL3016,DL3018,DL3028,DL3059
|
||||
output-file: ./hadolint.log
|
||||
verbose: true
|
||||
|
||||
- name: Log
|
||||
if: failure()
|
||||
run: |
|
||||
echo "Hadolint outcome: ${{ steps.hadolint.outcome }}" >> $GITHUB_STEP_SUMMARY
|
||||
cat "./hadolint.log" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
docker:
|
||||
name: Docker
|
||||
needs: [check_dockerfile, check_changelog]
|
||||
if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }}
|
||||
needs: [check_dockerfiles, check_changelog, setup_release]
|
||||
if: ${{ needs.check_dockerfiles.outputs.dockerfiles }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.check_dockerfiles.outputs.matrix) }}
|
||||
name: Docker${{ matrix.tag }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -106,13 +200,12 @@ jobs:
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
env:
|
||||
NEXT_VERSION: ${{ needs.check_changelog.outputs.next_version }}
|
||||
NV: ${{ needs.check_changelog.outputs.next_version }}
|
||||
run: |
|
||||
# get branch name
|
||||
BRANCH=${GITHUB_HEAD_REF}
|
||||
|
||||
if [ -z "$BRANCH" ]
|
||||
then
|
||||
if [ -z "$BRANCH" ]; then
|
||||
echo "This is a PUSH event"
|
||||
BRANCH=${{ github.ref_name }}
|
||||
fi
|
||||
@@ -129,27 +222,62 @@ jobs:
|
||||
BASE_TAG=$(echo $REPOSITORY | tr '[:upper:]' '[:lower:]')
|
||||
COMMIT=${{ github.sha }}
|
||||
|
||||
TAGS="${BASE_TAG}:${COMMIT:0:7},ghcr.io/${BASE_TAG}:${COMMIT:0:7}"
|
||||
TAGS="${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }},ghcr.io/${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }}"
|
||||
|
||||
if [[ $GITHUB_REF == refs/heads/master ]]; then
|
||||
TAGS="${TAGS},${BASE_TAG}:latest,ghcr.io/${BASE_TAG}:latest"
|
||||
TAGS="${TAGS},${BASE_TAG}:master,ghcr.io/${BASE_TAG}:master"
|
||||
TAGS="${TAGS},${BASE_TAG}:latest${{ matrix.tag }},ghcr.io/${BASE_TAG}:latest${{ matrix.tag }}"
|
||||
TAGS="${TAGS},${BASE_TAG}:master${{ matrix.tag }},ghcr.io/${BASE_TAG}:master${{ matrix.tag }}"
|
||||
elif [[ $GITHUB_REF == refs/heads/nightly ]]; then
|
||||
TAGS="${TAGS},${BASE_TAG}:nightly,ghcr.io/${BASE_TAG}:nightly"
|
||||
TAGS="${TAGS},${BASE_TAG}:nightly${{ matrix.tag }},ghcr.io/${BASE_TAG}:nightly${{ matrix.tag }}"
|
||||
else
|
||||
TAGS="${TAGS},${BASE_TAG}:test,ghcr.io/${BASE_TAG}:test"
|
||||
TAGS="${TAGS},${BASE_TAG}:test${{ matrix.tag }},ghcr.io/${BASE_TAG}:test${{ matrix.tag }}"
|
||||
fi
|
||||
|
||||
if [[ ${NEXT_VERSION} != "" ]]; then
|
||||
TAGS="${TAGS},${BASE_TAG}:${NEXT_VERSION},ghcr.io/${BASE_TAG}:${NEXT_VERSION}"
|
||||
if [[ ${NV} != "" ]]; then
|
||||
TAGS="${TAGS},${BASE_TAG}:${NV}${{ matrix.tag }},ghcr.io/${BASE_TAG}:${NV}${{ matrix.tag }}"
|
||||
fi
|
||||
|
||||
# read the platforms from `.docker_platforms`
|
||||
PLATFORMS=$(<.docker_platforms)
|
||||
# parse custom directives out of dockerfile
|
||||
# try to get the platforms from the dockerfile custom directive, i.e. `# platforms: xxx,yyy`
|
||||
# directives for PR event, i.e. not push event
|
||||
if [[ ${PUSH} == "false" ]]; then
|
||||
while read -r line; do
|
||||
if [[ $line == "# platforms_pr: "* && $PLATFORMS == "" ]]; then
|
||||
# echo the line and use `sed` to remove the custom directive
|
||||
PLATFORMS=$(echo -e "$line" | sed 's/# platforms_pr: //')
|
||||
elif [[ $PLATFORMS != "" ]]; then
|
||||
# break while loop once all custom "PR" event directives are found
|
||||
break
|
||||
fi
|
||||
done <"${{ matrix.dockerfile }}"
|
||||
fi
|
||||
# directives for all events... above directives will not be parsed if they were already found
|
||||
while read -r line; do
|
||||
if [[ $line == "# platforms: "* && $PLATFORMS == "" ]]; then
|
||||
# echo the line and use `sed` to remove the custom directive
|
||||
PLATFORMS=$(echo -e "$line" | sed 's/# platforms: //')
|
||||
elif [[ $line == "# artifacts: "* && $ARTIFACTS == "" ]]; then
|
||||
# echo the line and use `sed` to remove the custom directive
|
||||
ARTIFACTS=$(echo -e "$line" | sed 's/# artifacts: //')
|
||||
elif [[ $PLATFORMS != "" && $ARTIFACTS != "" ]]; then
|
||||
# break while loop once all custom directives are found
|
||||
break
|
||||
fi
|
||||
done <"${{ matrix.dockerfile }}"
|
||||
# if PLATFORMS is blank, fall back to the legacy method of reading from the `.docker_platforms` file
|
||||
if [[ $PLATFORMS == "" ]]; then
|
||||
# read the platforms from `.docker_platforms`
|
||||
PLATFORMS=$(<.docker_platforms)
|
||||
fi
|
||||
# if PLATFORMS is still blank, fall back to `linux/amd64`
|
||||
if [[ $PLATFORMS == "" ]]; then
|
||||
PLATFORMS="linux/amd64"
|
||||
fi
|
||||
|
||||
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
|
||||
echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
echo "commit=${COMMIT}" >> $GITHUB_OUTPUT
|
||||
echo "artifacts=${ARTIFACTS}" >> $GITHUB_OUTPUT
|
||||
echo "platforms=${PLATFORMS}" >> $GITHUB_OUTPUT
|
||||
echo "push=${PUSH}" >> $GITHUB_OUTPUT
|
||||
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||
@@ -165,9 +293,9 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
key: Docker-buildx${{ matrix.tag }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
Docker-buildx${{ matrix.tag }}-
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets
|
||||
@@ -184,11 +312,32 @@ jobs:
|
||||
username: ${{ secrets.GH_BOT_NAME }}
|
||||
password: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
- name: Build artifacts
|
||||
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
|
||||
id: build_artifacts
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./
|
||||
file: ./Dockerfile
|
||||
file: ${{ matrix.dockerfile }}
|
||||
target: artifacts
|
||||
outputs: type=local,dest=artifacts
|
||||
push: false
|
||||
platforms: ${{ steps.prepare.outputs.platforms }}
|
||||
build-args: |
|
||||
BRANCH=${{ steps.prepare.outputs.branch }}
|
||||
BUILD_DATE=${{ steps.prepare.outputs.build_date }}
|
||||
BUILD_VERSION=${{ needs.check_changelog.outputs.next_version }}
|
||||
COMMIT=${{ steps.prepare.outputs.commit }}
|
||||
tags: ${{ steps.prepare.outputs.tags }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Build and push
|
||||
id: build
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./
|
||||
file: ${{ matrix.dockerfile }}
|
||||
push: ${{ steps.prepare.outputs.push }}
|
||||
platforms: ${{ steps.prepare.outputs.platforms }}
|
||||
build-args: |
|
||||
@@ -200,6 +349,36 @@ jobs:
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Arrange Artifacts
|
||||
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
# artifacts will be in sub directories named after the docker target platform, e.g. `linux_amd64`
|
||||
# so move files to the artifacts directory
|
||||
# https://unix.stackexchange.com/a/52816
|
||||
find ./ -type f -exec mv -t ./ -n '{}' +
|
||||
|
||||
- name: Upload Artifacts
|
||||
if: ${{ steps.prepare.outputs.artifacts == 'true' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sunshine${{ matrix.tag }}
|
||||
path: artifacts/
|
||||
|
||||
- name: Create/Update GitHub Release
|
||||
if: ${{ needs.setup_release.outputs.create_release == 'true' && steps.prepare.outputs.artifacts == 'true' }}
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ needs.setup_release.outputs.release_name }}
|
||||
tag: ${{ needs.setup_release.outputs.release_tag }}
|
||||
commit: ${{ needs.setup_release.outputs.release_commit }}
|
||||
artifacts: "*artifacts/*"
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
allowUpdates: true
|
||||
body: ${{ needs.setup_release.outputs.release_body }}
|
||||
discussionCategory: announcements
|
||||
prerelease: ${{ needs.setup_release.outputs.pre_release }}
|
||||
|
||||
- name: Update Docker Hub Description
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
|
||||
2
.github/workflows/issues-stale.yml
vendored
2
.github/workflows/issues-stale.yml
vendored
@@ -9,7 +9,7 @@ name: Stale Issues / PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '00 00 * * *'
|
||||
- cron: '00 10 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -42,3 +42,7 @@
|
||||
path = third-party/ffmpeg-macos-aarch64
|
||||
url = https://github.com/LizardByte/build-deps
|
||||
branch = ffmpeg-macos-aarch64
|
||||
[submodule "third-party/nanors"]
|
||||
path = third-party/nanors
|
||||
url = https://github.com/sleepybishop/nanors.git
|
||||
branch = master
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## [0.18.0] - 2023-01-29
|
||||
Attention, this release contains critical security fixes. Please update as soon as possible. Additionally, we are
|
||||
encouraging users to change your Sunshine password, especially if you expose the web UI (i.e. port 47790 by default)
|
||||
to the internet, or have ever uploaded your logs with verbose output to a public resource.
|
||||
|
||||
### Added
|
||||
- (Windows) Add support for Intel QuickSync
|
||||
- (Linux) Added aarch64 deb and rpm packages
|
||||
- (Windows) Add support for hybrid graphics systems, such as laptops with both integrated and discrete GPUs
|
||||
- (Linux) Add support for streaming from Steam Deck Gaming Mode
|
||||
- (Windows) Add HDR support, see https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/usage.html#hdr-support
|
||||
### Fixed
|
||||
- (Network) Refactor code for UPnP port forwarding
|
||||
- (Video) Enforce 10 FPS encoding frame rate minimum to improve static image quality
|
||||
- (Linux) deb and rpm packages are now specific to destination distro and version
|
||||
- (Docs) Add nvidia/nvenc preset migration guide
|
||||
- (Network) Performance optimizations
|
||||
- (Video/Windows) Fix streaming to multiple clients from hardware encoder
|
||||
- (Linux) Fix child process spawning
|
||||
- (Security) Fix security vulnerability in implementation of SimpleWebServer
|
||||
- (Misc) Rename "Steam BigPicture" to "Steam Big Picture" in default apps.json
|
||||
- (Security) Scrub basic authorization header from logs
|
||||
- (Linux) The systemd service will now restart in the event of a crash
|
||||
- (Video/KMS/Linux) Fixed error: `couldn't import RGB Image: 00003002 and 00003004`
|
||||
- (Video/Windows) Fix stream freezing triggered by the resolution changed
|
||||
- (Installer/Windows) Fixes silent installation and other miscellaneous improvements
|
||||
- (CPU) Significantly improved CPU usage
|
||||
|
||||
## [0.17.0] - 2023-01-08
|
||||
If you are running Sunshine as a service on Windows, we are strongly urging you to update to v0.17.0 as soon as
|
||||
possible. Older Windows versions of Sunshine had a security flaw in which the binary was located in a user-writable
|
||||
@@ -272,3 +300,4 @@ settings. In v0.17.0, games now run under your user account without elevated pri
|
||||
[0.15.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.15.0
|
||||
[0.16.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.16.0
|
||||
[0.17.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.17.0
|
||||
[0.18.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.0
|
||||
|
||||
131
CMakeLists.txt
131
CMakeLists.txt
@@ -1,6 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
# `CMAKE_CUDA_ARCHITECTURES` requires 3.18
|
||||
|
||||
project(Sunshine VERSION 0.17.0
|
||||
project(Sunshine VERSION 0.18.0
|
||||
DESCRIPTION "Sunshine is a self-hosted game stream host for Moonlight."
|
||||
HOMEPAGE_URL "https://app.lizardbyte.dev")
|
||||
|
||||
@@ -16,6 +17,11 @@ option(SUNSHINE_CONFIGURE_FLATPAK "Configuration specific for Flatpak." OFF)
|
||||
option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile." OFF)
|
||||
option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(STATUS "Setting build type to 'Release' as none was specified.")
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
|
||||
endif()
|
||||
|
||||
if(${SUNSHINE_CONFIGURE_APPIMAGE})
|
||||
configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY)
|
||||
elseif(${SUNSHINE_CONFIGURE_AUR})
|
||||
@@ -64,7 +70,7 @@ include_directories(third-party/miniupnp/miniupnpc/include)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules (CURL REQUIRED libcurl)
|
||||
pkg_check_modules(CURL REQUIRED libcurl)
|
||||
|
||||
if(NOT APPLE)
|
||||
set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103
|
||||
@@ -197,12 +203,70 @@ else()
|
||||
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)
|
||||
|
||||
message(STATUS "CUDA Compiler Version: ${CMAKE_CUDA_COMPILER_VERSION}")
|
||||
set(CMAKE_CUDA_ARCHITECTURES "")
|
||||
|
||||
# https://tech.amikelive.com/node-930/cuda-compatibility-of-nvidia-display-gpu-drivers/
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 6.5)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 10)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_10,code=sm_10")
|
||||
elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 6.5)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 50 52)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_50,code=sm_50")
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_52,code=sm_52")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 7.0)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 11)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_11,code=sm_11")
|
||||
elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER 7.6)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 60 61 62)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_60,code=sm_60")
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_61,code=sm_61")
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_62,code=sm_62")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 9.0)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 20)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_20,code=sm_20")
|
||||
elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 9.0)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 70)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_70,code=sm_70")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 75)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_75,code=sm_75")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 11.0)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 30)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_30,code=sm_30")
|
||||
elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 80)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_80,code=sm_80")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.1)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 86)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_86,code=sm_86")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.8)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 90)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_90,code=sm_90")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12.0)
|
||||
list(APPEND CMAKE_CUDA_ARCHITECTURES 35)
|
||||
# set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_35,code=sm_35")
|
||||
endif()
|
||||
|
||||
# message(STATUS "CUDA NVCC Flags: ${CUDA_NVCC_FLAGS}")
|
||||
message(STATUS "CUDA Architectures: ${CMAKE_CUDA_ARCHITECTURES}")
|
||||
endif()
|
||||
endif()
|
||||
if(${SUNSHINE_ENABLE_DRM})
|
||||
@@ -337,8 +401,8 @@ configure_file(version.h.in version.h @ONLY)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(SUNSHINE_TARGET_FILES
|
||||
third-party/moonlight-common-c/reedsolomon/rs.c
|
||||
third-party/moonlight-common-c/reedsolomon/rs.h
|
||||
third-party/nanors/rs.c
|
||||
third-party/nanors/rs.h
|
||||
third-party/moonlight-common-c/src/Input.h
|
||||
third-party/moonlight-common-c/src/Rtsp.h
|
||||
third-party/moonlight-common-c/src/RtspParser.c
|
||||
@@ -385,10 +449,13 @@ set(SUNSHINE_TARGET_FILES
|
||||
|
||||
set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic)
|
||||
|
||||
set_source_files_properties(third-party/nanors/rs.c
|
||||
PROPERTIES COMPILE_FLAGS "-include deps/obl/autoshim.h -ftree-vectorize")
|
||||
|
||||
# Pre-compiled binaries
|
||||
if(WIN32)
|
||||
set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-windows-x86_64")
|
||||
set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid)
|
||||
set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid mfx)
|
||||
elseif(APPLE)
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
|
||||
set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-macos-aarch64")
|
||||
@@ -396,12 +463,13 @@ elseif(APPLE)
|
||||
set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-macos-x86_64")
|
||||
endif()
|
||||
else()
|
||||
set(FFMPEG_PLATFORM_LIBRARIES va va-drm va-x11 vdpau X11)
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
|
||||
set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-linux-aarch64")
|
||||
else()
|
||||
set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-linux-x86_64")
|
||||
list(APPEND FFMPEG_PLATFORM_LIBRARIES mfx)
|
||||
endif()
|
||||
set(FFMPEG_PLATFORM_LIBRARIES va va-drm va-x11 vdpau X11)
|
||||
endif()
|
||||
set(FFMPEG_INCLUDE_DIRS
|
||||
${FFMPEG_PREPARED_BINARIES}/include)
|
||||
@@ -424,20 +492,19 @@ include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/enet/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/reedsolomon
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/nanors
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/nanors/deps/obl
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
${PLATFORM_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE)
|
||||
if("${BUILD_TYPE}" STREQUAL "XDEBUG")
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -ggdb3)
|
||||
if(WIN32)
|
||||
set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2)
|
||||
endif()
|
||||
else()
|
||||
add_definitions(-DNDEBUG)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O3)
|
||||
endif()
|
||||
|
||||
# setup assets directory
|
||||
@@ -475,6 +542,8 @@ add_executable(sunshine ${SUNSHINE_TARGET_FILES})
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
|
||||
find_library(ZLIB ZLIB1)
|
||||
endif()
|
||||
|
||||
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS})
|
||||
@@ -521,11 +590,17 @@ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules"
|
||||
if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.html
|
||||
install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application)
|
||||
|
||||
# Hardening: include zlib1.dll (loaded via LoadLibrary() in openssl's libcrypto.a)
|
||||
install(FILES "${ZLIB}" DESTINATION "." COMPONENT application)
|
||||
|
||||
# Adding tools
|
||||
install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi)
|
||||
install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
|
||||
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc)
|
||||
|
||||
# Mandatory tools
|
||||
install(TARGETS ddprobe RUNTIME DESTINATION "tools" COMPONENT application)
|
||||
|
||||
# scripts
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/"
|
||||
DESTINATION "scripts"
|
||||
@@ -558,15 +633,17 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
# Install service
|
||||
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
|
||||
"${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
|
||||
ExecWait '\\\"$SYSDIR\\\\cmd.exe\\\" /c \\\"start https://sunshinestream.readthedocs.io/\\\"'
|
||||
ExecWait 'icacls \\\"$INSTDIR\\\" /reset'
|
||||
ExecWait '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
|
||||
ExecWait 'icacls \\\"$INSTDIR\\\\config\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)'
|
||||
ExecWait '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
|
||||
ExecWait '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION 'Do you want to add/update ViGEmBus (virtual controller support)?' \
|
||||
IDNO NoController
|
||||
ExecWait '\\\"$SYSDIR\\\\cmd.exe\\\" /c \\\"start https://github.com/ViGEm/ViGEmBus/releases/latest\\\"'; \
|
||||
IfSilent +2 0
|
||||
ExecShell 'open' 'https://sunshinestream.readthedocs.io/'
|
||||
nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
|
||||
nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\\config\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION \
|
||||
'Do you want to install ViGEmBus? This is REQUIRED for gamepad support while streaming.' \
|
||||
/SD IDNO IDNO NoController
|
||||
ExecShell 'open' 'https://github.com/ViGEm/ViGEmBus/releases/latest'; \
|
||||
skipped if no
|
||||
NoController:
|
||||
")
|
||||
@@ -575,8 +652,8 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
# Uninstall service
|
||||
set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS
|
||||
"${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}
|
||||
ExecWait '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
|
||||
ExecWait '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
|
||||
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION \
|
||||
'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \
|
||||
/SD IDNO IDNO NoDelete
|
||||
@@ -592,12 +669,12 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
|
||||
set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe")
|
||||
set(CPACK_NSIS_CREATE_ICONS_EXTRA
|
||||
"${CPACK_NSIS_CREATE_ICONS_EXTRA}
|
||||
CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' \
|
||||
CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk' \
|
||||
'\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe'
|
||||
")
|
||||
set(CPACK_NSIS_DELETE_ICONS_EXTRA
|
||||
"${CPACK_NSIS_DELETE_ICONS_EXTRA}
|
||||
Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME}.lnk'
|
||||
Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk'
|
||||
")
|
||||
|
||||
# Checking for previous installed versions
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
# Docker
|
||||
|
||||
## Important note
|
||||
Starting with v0.18.0, tag names have changed. You may no longer use `latest`, `master`, `vX.X.X`.
|
||||
|
||||
## Build your own containers
|
||||
This image provides a method for you to easily use the latest Sunshine release in your own docker projects. It is not
|
||||
intended to use as a standalone container at this point, and should be considered experimental.
|
||||
|
||||
```dockerfile
|
||||
FROM lizardbyte/sunshine
|
||||
ARG SUNSHINE_VERSION=latest
|
||||
ARG SUNSHINE_OS=ubuntu-22.04
|
||||
FROM lizardbyte/sunshine:${SUNSHINE_VERSION}-${SUNSHINE_OS}
|
||||
|
||||
# install Steam, Wayland, etc.
|
||||
|
||||
ENTRYPOINT steam && sunshine
|
||||
```
|
||||
|
||||
### SUNSHINE_VERSION
|
||||
- `latest`, `master`, `vX.X.X`
|
||||
- `nightly`
|
||||
- commit hash
|
||||
|
||||
### SUNSHINE_OS
|
||||
Sunshine images are available, based on the following base images.
|
||||
|
||||
- `debian-bullseye`
|
||||
- `fedora-36`
|
||||
- `fedora-37`
|
||||
- `ubuntu-20.04`
|
||||
- `ubuntu-22.04`
|
||||
|
||||
### Tags
|
||||
You must combine the `SUNSHINE_VERSION` and `SUNSHINE_OS` to determine the tag to pull. The format should be
|
||||
`<SUNSHINE_VERSION>-<SUNSHINE_OS>`. For example, `latest-ubuntu-22.04`.
|
||||
|
||||
See all our available tags on [docker hub](https://hub.docker.com/r/lizardbyte/sunshine/tags) or
|
||||
[ghcr](https://github.com/LizardByte/Sunshine/pkgs/container/sunshine/versions) for more info.
|
||||
|
||||
## Where used
|
||||
This is a list of docker projects using Sunshine. Something missing? Let us know about it!
|
||||
|
||||
@@ -97,12 +123,12 @@ If you want to change the PUID or PGID after the image has been built, it will r
|
||||
|
||||
## Supported Architectures
|
||||
|
||||
Specifying `lizardbyte/sunshine:latest` or `ghcr.io/lizardbyte/sunshine:latest` should retrieve the correct
|
||||
image for your architecture.
|
||||
Specifying `lizardbyte/sunshine:latest-<SUNSHINE_OS>` or `ghcr.io/lizardbyte/sunshine:latest-<SUNSHINE_OS>` should
|
||||
retrieve the correct image for your architecture.
|
||||
|
||||
The architectures supported by this image are:
|
||||
The architectures supported by these images are:
|
||||
|
||||
| Architecture | Available |
|
||||
|:------------:|:---------:|
|
||||
| x86-64 | ✅ |
|
||||
| arm64 | ✅ |
|
||||
| Architecture | Available |
|
||||
|:---------------:|:---------:|
|
||||
| amd64 / x86_64 | ✅ |
|
||||
| arm64 / aarch64 | ✅ |
|
||||
|
||||
102
Dockerfile
102
Dockerfile
@@ -1,102 +0,0 @@
|
||||
FROM ubuntu:22.04 AS sunshine-base
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential=12.9* \
|
||||
cmake=3.22.1* \
|
||||
libavdevice-dev=7:4.4.* \
|
||||
libboost-filesystem-dev=1.74.0* \
|
||||
libboost-log-dev=1.74.0* \
|
||||
libboost-thread-dev=1.74.0* \
|
||||
libboost-program-options-dev=1.74.0* \
|
||||
libcap-dev=1:2.44* \
|
||||
libcurl4-openssl-dev=7.81.0* \
|
||||
libdrm-dev=2.4.110* \
|
||||
libevdev-dev=1.12.1* \
|
||||
libnuma-dev=2.0.14* \
|
||||
libopus-dev=1.3.1* \
|
||||
libpulse-dev=1:15.99.1* \
|
||||
libssl-dev=3.0.2* \
|
||||
libva-dev=2.14.0* \
|
||||
libvdpau-dev=1.4* \
|
||||
libwayland-dev=1.20.0* \
|
||||
libx11-dev=2:1.7.5* \
|
||||
libxcb-shm0-dev=1.14* \
|
||||
libxcb-xfixes0-dev=1.14* \
|
||||
libxcb1-dev=1.14* \
|
||||
libxfixes-dev=1:6.0.0* \
|
||||
libxrandr-dev=2:1.5.2* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
nodejs=12.22.9* \
|
||||
npm=8.5.1* \
|
||||
nvidia-cuda-dev=11.5.1* \
|
||||
nvidia-cuda-toolkit=11.5.1* \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# copy repository
|
||||
WORKDIR /root/sunshine-build/
|
||||
COPY . .
|
||||
|
||||
# setup npm and dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /root/sunshine-build/build
|
||||
|
||||
# cmake and cpack
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
/root/sunshine-build \
|
||||
&& make -j "$(nproc)" \
|
||||
&& cpack -G DEB
|
||||
|
||||
FROM sunshine-base as sunshine
|
||||
|
||||
# copy deb from builder
|
||||
COPY --from=sunshine-build /root/sunshine-build/build/cpack_artifacts/Sunshine.deb /sunshine.deb
|
||||
|
||||
# install sunshine
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y --no-install-recommends /sunshine.deb \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# network setup
|
||||
EXPOSE 47984-47990/tcp
|
||||
EXPOSE 48010
|
||||
EXPOSE 47998-48000/udp
|
||||
|
||||
# setup user
|
||||
ARG PGID=1000
|
||||
ENV PGID=${PGID}
|
||||
ARG PUID=1000
|
||||
ENV PUID=${PUID}
|
||||
ENV TZ="UTC"
|
||||
ARG UNAME=lizard
|
||||
ENV UNAME=${UNAME}
|
||||
|
||||
ENV HOME=/home/$UNAME
|
||||
|
||||
RUN groupadd -f -g "${PGID}" "${UNAME}" && \
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" && \
|
||||
mkdir -p ${HOME}/.config/sunshine && \
|
||||
ln -s ${HOME}/.config/sunshine /config && \
|
||||
chown -R ${UNAME} ${HOME}
|
||||
|
||||
USER ${UNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
# entrypoint
|
||||
ENTRYPOINT ["/usr/bin/sunshine"]
|
||||
156
docker/debian-bullseye.dockerfile
Normal file
156
docker/debian-bullseye.dockerfile
Normal file
@@ -0,0 +1,156 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# artifacts: true
|
||||
# platforms: linux/amd64,linux/arm64/v8
|
||||
# platforms_pr: linux/amd64
|
||||
ARG BASE=debian
|
||||
ARG TAG=bullseye
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN echo "target_platform: ${TARGETPLATFORM}"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# install dependencies
|
||||
RUN <<_DEPS
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential=12.9* \
|
||||
cmake=3.18.4* \
|
||||
libavdevice-dev=7:4.3.* \
|
||||
libboost-filesystem-dev=1.74.0* \
|
||||
libboost-log-dev=1.74.0* \
|
||||
libboost-program-options-dev=1.74.0* \
|
||||
libboost-thread-dev=1.74.0* \
|
||||
libcap-dev=1:2.44* \
|
||||
libcurl4-openssl-dev=7.74.0* \
|
||||
libdrm-dev=2.4.104* \
|
||||
libevdev-dev=1.11.0* \
|
||||
libnuma-dev=2.0.12* \
|
||||
libopus-dev=1.3.1* \
|
||||
libpulse-dev=14.2* \
|
||||
libssl-dev=1.1.1* \
|
||||
libva-dev=2.10.0* \
|
||||
libvdpau-dev=1.4* \
|
||||
libwayland-dev=1.18.0* \
|
||||
libx11-dev=2:1.7.2* \
|
||||
libxcb-shm0-dev=1.14* \
|
||||
libxcb-xfixes0-dev=1.14* \
|
||||
libxcb1-dev=1.14* \
|
||||
libxfixes-dev=1:5.0.3* \
|
||||
libxrandr-dev=2:1.5.1* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
nodejs=12.22* \
|
||||
npm=7.5.2* \
|
||||
wget=1.21*
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmfx-dev=21.1.0*
|
||||
fi
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_DEPS
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
ENV CUDA_VERSION="11.8.0"
|
||||
ENV CUDA_BUILD="520.61.05"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CUDA
|
||||
#!/bin/bash
|
||||
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
cuda_suffix=""
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cuda_suffix="_sbsa"
|
||||
fi
|
||||
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
_INSTALL_CUDA
|
||||
|
||||
# copy repository
|
||||
WORKDIR /build/sunshine/
|
||||
COPY .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
/build/sunshine
|
||||
make -j "$(nproc)"
|
||||
cpack -G DEB
|
||||
_MAKE
|
||||
|
||||
FROM scratch AS artifacts
|
||||
ARG BASE
|
||||
ARG TAG
|
||||
ARG TARGETARCH
|
||||
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
|
||||
|
||||
FROM sunshine-base as sunshine
|
||||
|
||||
# copy deb from builder
|
||||
COPY --from=artifacts /sunshine*.deb /sunshine.deb
|
||||
|
||||
# install sunshine
|
||||
RUN <<_INSTALL_SUNSHINE
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends /sunshine.deb
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_INSTALL_SUNSHINE
|
||||
|
||||
# network setup
|
||||
EXPOSE 47984-47990/tcp
|
||||
EXPOSE 48010
|
||||
EXPOSE 47998-48000/udp
|
||||
|
||||
# setup user
|
||||
ARG PGID=1000
|
||||
ENV PGID=${PGID}
|
||||
ARG PUID=1000
|
||||
ENV PUID=${PUID}
|
||||
ENV TZ="UTC"
|
||||
ARG UNAME=lizard
|
||||
ENV UNAME=${UNAME}
|
||||
|
||||
ENV HOME=/home/$UNAME
|
||||
|
||||
# setup user
|
||||
RUN <<_SETUP_USER
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
_SETUP_USER
|
||||
|
||||
USER ${UNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
# entrypoint
|
||||
ENTRYPOINT ["/usr/bin/sunshine"]
|
||||
155
docker/fedora-36.dockerfile
Normal file
155
docker/fedora-36.dockerfile
Normal file
@@ -0,0 +1,155 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# artifacts: true
|
||||
# platforms: linux/amd64,linux/arm64/v8
|
||||
# platforms_pr: linux/amd64
|
||||
ARG BASE=fedora
|
||||
ARG TAG=36
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN echo "target_platform: ${TARGETPLATFORM}"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# install dependencies
|
||||
# hadolint ignore=DL3041
|
||||
RUN <<_DEPS
|
||||
#!/bin/bash
|
||||
dnf -y update
|
||||
dnf -y group install "Development Tools"
|
||||
dnf -y install \
|
||||
boost-devel-1.76.0* \
|
||||
boost-static-1.76.0* \
|
||||
cmake-3.22.2* \
|
||||
gcc-12.0.1* \
|
||||
gcc-c++-12.0.1* \
|
||||
libcap-devel-2.48* \
|
||||
libcurl-devel-7.82.0* \
|
||||
libdrm-devel-2.4.110* \
|
||||
libevdev-devel-1.12.0* \
|
||||
libva-devel-2.14.0* \
|
||||
libvdpau-devel-1.5* \
|
||||
libX11-devel-1.7.3* \
|
||||
libxcb-devel-1.13.1* \
|
||||
libXcursor-devel-1.2.0* \
|
||||
libXfixes-devel-6.0.0* \
|
||||
libXi-devel-1.8* \
|
||||
libXinerama-devel-1.1.4* \
|
||||
libXrandr-devel-1.5.2* \
|
||||
libXtst-devel-1.2.3* \
|
||||
mesa-libGL-devel-22.0.1* \
|
||||
npm-8.3.1* \
|
||||
numactl-devel-2.0.14* \
|
||||
openssl-devel-3.0.2* \
|
||||
opus-devel-1.3.1* \
|
||||
pulseaudio-libs-devel-15.0* \
|
||||
rpm-build-4.17.0* \
|
||||
wget-1.21.3* \
|
||||
which-2.21*
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
dnf -y install intel-mediasdk-devel-22.3.0*
|
||||
fi
|
||||
dnf clean all
|
||||
rm -rf /var/cache/yum
|
||||
_DEPS
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
ENV CUDA_VERSION="12.0.0"
|
||||
ENV CUDA_BUILD="525.60.13"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CUDA
|
||||
#!/bin/bash
|
||||
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
cuda_suffix=""
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cuda_suffix="_sbsa"
|
||||
fi
|
||||
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
_INSTALL_CUDA
|
||||
|
||||
# copy repository
|
||||
WORKDIR /build/sunshine/
|
||||
COPY .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
/build/sunshine
|
||||
make -j "$(nproc)"
|
||||
cpack -G RPM
|
||||
_MAKE
|
||||
|
||||
FROM scratch AS artifacts
|
||||
ARG BASE
|
||||
ARG TAG
|
||||
ARG TARGETARCH
|
||||
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm
|
||||
|
||||
FROM sunshine-base as sunshine
|
||||
|
||||
# copy deb from builder
|
||||
COPY --from=artifacts /sunshine*.rpm /sunshine.rpm
|
||||
|
||||
# install sunshine
|
||||
RUN <<_INSTALL_SUNSHINE
|
||||
#!/bin/bash
|
||||
dnf -y update
|
||||
dnf -y install /sunshine.rpm
|
||||
dnf clean all
|
||||
rm -rf /var/cache/yum
|
||||
_INSTALL_SUNSHINE
|
||||
|
||||
# network setup
|
||||
EXPOSE 47984-47990/tcp
|
||||
EXPOSE 48010
|
||||
EXPOSE 47998-48000/udp
|
||||
|
||||
# setup user
|
||||
ARG PGID=1000
|
||||
ENV PGID=${PGID}
|
||||
ARG PUID=1000
|
||||
ENV PUID=${PUID}
|
||||
ENV TZ="UTC"
|
||||
ARG UNAME=lizard
|
||||
ENV UNAME=${UNAME}
|
||||
|
||||
ENV HOME=/home/$UNAME
|
||||
|
||||
# setup user
|
||||
RUN <<_SETUP_USER
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
_SETUP_USER
|
||||
|
||||
USER ${UNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
# entrypoint
|
||||
ENTRYPOINT ["/usr/bin/sunshine"]
|
||||
155
docker/fedora-37.dockerfile
Normal file
155
docker/fedora-37.dockerfile
Normal file
@@ -0,0 +1,155 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# artifacts: true
|
||||
# platforms: linux/amd64,linux/arm64/v8
|
||||
# platforms_pr: linux/amd64
|
||||
ARG BASE=fedora
|
||||
ARG TAG=37
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN echo "target_platform: ${TARGETPLATFORM}"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# install dependencies
|
||||
# hadolint ignore=DL3041
|
||||
RUN <<_DEPS
|
||||
#!/bin/bash
|
||||
dnf -y update
|
||||
dnf -y group install "Development Tools"
|
||||
dnf -y install \
|
||||
boost-devel-1.78.0* \
|
||||
boost-static-1.78.0* \
|
||||
cmake-3.24.1* \
|
||||
gcc-12.2.1* \
|
||||
gcc-c++-12.2.1* \
|
||||
libcap-devel-2.48* \
|
||||
libcurl-devel-7.85.0* \
|
||||
libdrm-devel-2.4.112* \
|
||||
libevdev-devel-1.13.0* \
|
||||
libva-devel-2.15.0* \
|
||||
libvdpau-devel-1.5* \
|
||||
libX11-devel-1.8.1* \
|
||||
libxcb-devel-1.13.1* \
|
||||
libXcursor-devel-1.2.1* \
|
||||
libXfixes-devel-6.0.0* \
|
||||
libXi-devel-1.8* \
|
||||
libXinerama-devel-1.1.4* \
|
||||
libXrandr-devel-1.5.2* \
|
||||
libXtst-devel-1.2.3* \
|
||||
mesa-libGL-devel-22.2.2* \
|
||||
npm-8.15.0* \
|
||||
numactl-devel-2.0.14* \
|
||||
openssl-devel-3.0.5* \
|
||||
opus-devel-1.3.1* \
|
||||
pulseaudio-libs-devel-16.1* \
|
||||
rpm-build-4.18.0* \
|
||||
wget-1.21.3* \
|
||||
which-2.21*
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
dnf -y install intel-mediasdk-devel-22.4.4*
|
||||
fi
|
||||
dnf clean all
|
||||
rm -rf /var/cache/yum
|
||||
_DEPS
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
ENV CUDA_VERSION="12.0.0"
|
||||
ENV CUDA_BUILD="525.60.13"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CUDA
|
||||
#!/bin/bash
|
||||
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
cuda_suffix=""
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cuda_suffix="_sbsa"
|
||||
fi
|
||||
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
_INSTALL_CUDA
|
||||
|
||||
# copy repository
|
||||
WORKDIR /build/sunshine/
|
||||
COPY .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
/build/sunshine
|
||||
make -j "$(nproc)"
|
||||
cpack -G RPM
|
||||
_MAKE
|
||||
|
||||
FROM scratch AS artifacts
|
||||
ARG BASE
|
||||
ARG TAG
|
||||
ARG TARGETARCH
|
||||
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm
|
||||
|
||||
FROM sunshine-base as sunshine
|
||||
|
||||
# copy deb from builder
|
||||
COPY --from=artifacts /sunshine*.rpm /sunshine.rpm
|
||||
|
||||
# install sunshine
|
||||
RUN <<_INSTALL_SUNSHINE
|
||||
#!/bin/bash
|
||||
dnf -y update
|
||||
dnf -y install /sunshine.rpm
|
||||
dnf clean all
|
||||
rm -rf /var/cache/yum
|
||||
_INSTALL_SUNSHINE
|
||||
|
||||
# network setup
|
||||
EXPOSE 47984-47990/tcp
|
||||
EXPOSE 48010
|
||||
EXPOSE 47998-48000/udp
|
||||
|
||||
# setup user
|
||||
ARG PGID=1000
|
||||
ENV PGID=${PGID}
|
||||
ARG PUID=1000
|
||||
ENV PUID=${PUID}
|
||||
ENV TZ="UTC"
|
||||
ARG UNAME=lizard
|
||||
ENV UNAME=${UNAME}
|
||||
|
||||
ENV HOME=/home/$UNAME
|
||||
|
||||
# setup user
|
||||
RUN <<_SETUP_USER
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
_SETUP_USER
|
||||
|
||||
USER ${UNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
# entrypoint
|
||||
ENTRYPOINT ["/usr/bin/sunshine"]
|
||||
210
docker/ubuntu-18.04.dockerfile-todo
Normal file
210
docker/ubuntu-18.04.dockerfile-todo
Normal file
@@ -0,0 +1,210 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# artifacts: true
|
||||
# platforms: linux/amd64,linux/arm64/v8
|
||||
# platforms_pr: linux/amd64
|
||||
ARG BASE=ubuntu
|
||||
ARG TAG=18.04
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN echo "target_platform: ${TARGETPLATFORM}"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# install dependencies
|
||||
RUN <<_DEPS
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
software-properties-common=0.96.24.32.18
|
||||
add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
apt-get install -y --no-install-recommends \
|
||||
bison=2:3.0.4* \
|
||||
build-essential=12.4* \
|
||||
gcc-10=10.3.0* \
|
||||
g++-10=10.3.0* \
|
||||
libavdevice-dev=7:3.4.* \
|
||||
libcap-dev=1:2.25* \
|
||||
libcurl-openssl1.0-dev=7.58.0* \
|
||||
libdrm-dev=2.4.101* \
|
||||
libevdev-dev=1.5.8* \
|
||||
libnuma-dev=2.0.11* \
|
||||
libopus-dev=1.1.2* \
|
||||
libpulse-dev=1:11.1* \
|
||||
libssl1.0-dev=1.0.2* \
|
||||
libva-dev=2.1.0* \
|
||||
libvdpau-dev=1.1.1* \
|
||||
libwayland-dev=1.16.0* \
|
||||
libx11-dev=2:1.6.4* \
|
||||
libxcb-shm0-dev=1.13* \
|
||||
libxcb-xfixes0-dev=1.13* \
|
||||
libxcb1-dev=1.13* \
|
||||
libxfixes-dev=1:5.0.3* \
|
||||
libxrandr-dev=2:1.5.1* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
npm=3.5.2* \
|
||||
node-gyp=3.6.2* \
|
||||
nodejs-dev=8.10.0* \
|
||||
wget=1.19.4*
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_DEPS
|
||||
|
||||
# Update gcc alias
|
||||
# https://stackoverflow.com/a/70653945/11214013
|
||||
RUN <<_GCC_ALIAS
|
||||
#!/bin/bash
|
||||
update-alternatives --install \
|
||||
/usr/bin/gcc gcc /usr/bin/gcc-10 100 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-10 \
|
||||
--slave /usr/bin/gcov gcov /usr/bin/gcov-10 \
|
||||
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \
|
||||
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10
|
||||
_GCC_ALIAS
|
||||
|
||||
# install boost
|
||||
# cannot install boost for aarch64 using ppa:savoury1/boost-defaults-1.71
|
||||
# otherwise add the repository and the following packages
|
||||
# libboost-filesystem1.71-dev=1.71.0* \
|
||||
# libboost-log1.71-dev=1.71.0* \
|
||||
# libboost-program-options1.71-dev=1.71.0* \
|
||||
# libboost-regex1.71-dev=1.71.0* \
|
||||
# libboost-thread1.71-dev=1.71.0* \
|
||||
WORKDIR /build/tmp
|
||||
RUN <<_INSTALL_BOOST
|
||||
url="https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.bz2"
|
||||
wget "${url}" --progress=bar:force:noscroll -q --show-progress -O ./boost.tar.bz2
|
||||
tar --bzip2 -xf boost.tar.bz2 --directory /build
|
||||
mv /build/boost_*/ /build/boost
|
||||
ls -a /build/boost
|
||||
cd /build/boost
|
||||
./bootstrap.sh --with-libraries=system,thread,log,program_options && \
|
||||
./b2 install variant=release link=static,shared runtime-link=shared -j "$(nproc)"
|
||||
_INSTALL_BOOST
|
||||
|
||||
# install cmake
|
||||
# sunshine requires cmake >= 3.18
|
||||
WORKDIR /build/cmake
|
||||
# https://cmake.org/download/
|
||||
ENV CMAKE_VERSION="3.25.1"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CMAKE
|
||||
#!/bin/bash
|
||||
cmake_prefix="https://github.com/Kitware/CMake/releases/download/v"
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
cmake_arch="x86_64"
|
||||
elif [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cmake_arch="aarch64"
|
||||
fi
|
||||
url="${cmake_prefix}${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.sh"
|
||||
echo "cmake url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cmake.sh
|
||||
sh ./cmake.sh --prefix=/usr/local --skip-license
|
||||
cmake --version
|
||||
_INSTALL_CMAKE
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
ENV CUDA_VERSION="11.8.0"
|
||||
ENV CUDA_BUILD="520.61.05"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CUDA
|
||||
#!/bin/bash
|
||||
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
cuda_suffix=""
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cuda_suffix="_sbsa"
|
||||
fi
|
||||
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
_INSTALL_CUDA
|
||||
|
||||
# todo - install libmfx
|
||||
# https://github.com/Intel-Media-SDK/MediaSDK
|
||||
|
||||
# copy repository
|
||||
WORKDIR /build/sunshine/
|
||||
COPY .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
/build/sunshine
|
||||
make -j "$(nproc)"
|
||||
cpack -G DEB
|
||||
_MAKE
|
||||
|
||||
FROM scratch AS artifacts
|
||||
ARG BASE
|
||||
ARG TAG
|
||||
ARG TARGETARCH
|
||||
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
|
||||
|
||||
FROM sunshine-base as sunshine
|
||||
|
||||
# copy deb from builder
|
||||
COPY --from=artifacts /sunshine*.deb /sunshine.deb
|
||||
|
||||
# install sunshine
|
||||
RUN <<_INSTALL_SUNSHINE
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends /sunshine.deb
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_INSTALL_SUNSHINE
|
||||
|
||||
# network setup
|
||||
EXPOSE 47984-47990/tcp
|
||||
EXPOSE 48010
|
||||
EXPOSE 47998-48000/udp
|
||||
|
||||
# setup user
|
||||
ARG PGID=1000
|
||||
ENV PGID=${PGID}
|
||||
ARG PUID=1000
|
||||
ENV PUID=${PUID}
|
||||
ENV TZ="UTC"
|
||||
ARG UNAME=lizard
|
||||
ENV UNAME=${UNAME}
|
||||
|
||||
ENV HOME=/home/$UNAME
|
||||
|
||||
# setup user
|
||||
RUN <<_SETUP_USER
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
_SETUP_USER
|
||||
|
||||
USER ${UNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
# entrypoint
|
||||
ENTRYPOINT ["/usr/bin/sunshine"]
|
||||
190
docker/ubuntu-20.04.dockerfile
Normal file
190
docker/ubuntu-20.04.dockerfile
Normal file
@@ -0,0 +1,190 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# artifacts: true
|
||||
# platforms: linux/amd64,linux/arm64/v8
|
||||
# platforms_pr: linux/amd64
|
||||
ARG BASE=ubuntu
|
||||
ARG TAG=20.04
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN echo "target_platform: ${TARGETPLATFORM}"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# install dependencies
|
||||
RUN <<_DEPS
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential=12.8* \
|
||||
gcc-10=10.3.0* \
|
||||
g++-10=10.3.0* \
|
||||
libavdevice-dev=7:4.2.* \
|
||||
libboost-filesystem-dev=1.71.0* \
|
||||
libboost-log-dev=1.71.0* \
|
||||
libboost-program-options-dev=1.71.0* \
|
||||
libboost-thread-dev=1.71.0* \
|
||||
libcap-dev=1:2.32* \
|
||||
libcurl4-openssl-dev=7.68.0* \
|
||||
libdrm-dev=2.4.107* \
|
||||
libevdev-dev=1.9.0* \
|
||||
libnuma-dev=2.0.12* \
|
||||
libopus-dev=1.3.1* \
|
||||
libpulse-dev=1:13.99.1* \
|
||||
libssl-dev=1.1.1* \
|
||||
libva-dev=2.7.0* \
|
||||
libvdpau-dev=1.3* \
|
||||
libwayland-dev=1.18.0* \
|
||||
libx11-dev=2:1.6.9* \
|
||||
libxcb-shm0-dev=1.14* \
|
||||
libxcb-xfixes0-dev=1.14* \
|
||||
libxcb1-dev=1.14* \
|
||||
libxfixes-dev=1:5.0.3* \
|
||||
libxrandr-dev=2:1.5.2* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
nodejs=10.19.0* \
|
||||
npm=6.14.4* \
|
||||
wget=1.20.3*
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmfx-dev=20.1.0*
|
||||
fi
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_DEPS
|
||||
|
||||
# Update gcc alias
|
||||
# https://stackoverflow.com/a/70653945/11214013
|
||||
RUN <<_GCC_ALIAS
|
||||
#!/bin/bash
|
||||
update-alternatives --install \
|
||||
/usr/bin/gcc gcc /usr/bin/gcc-10 100 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-10 \
|
||||
--slave /usr/bin/gcov gcov /usr/bin/gcov-10 \
|
||||
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \
|
||||
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10
|
||||
_GCC_ALIAS
|
||||
|
||||
# install cmake
|
||||
# sunshine requires cmake >= 3.18
|
||||
WORKDIR /build/cmake
|
||||
# https://cmake.org/download/
|
||||
ENV CMAKE_VERSION="3.25.1"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CMAKE
|
||||
#!/bin/bash
|
||||
cmake_prefix="https://github.com/Kitware/CMake/releases/download/v"
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
cmake_arch="x86_64"
|
||||
elif [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cmake_arch="aarch64"
|
||||
fi
|
||||
url="${cmake_prefix}${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.sh"
|
||||
echo "cmake url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cmake.sh
|
||||
sh ./cmake.sh --prefix=/usr/local --skip-license
|
||||
cmake --version
|
||||
_INSTALL_CMAKE
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
ENV CUDA_VERSION="11.8.0"
|
||||
ENV CUDA_BUILD="520.61.05"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CUDA
|
||||
#!/bin/bash
|
||||
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
cuda_suffix=""
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cuda_suffix="_sbsa"
|
||||
fi
|
||||
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
_INSTALL_CUDA
|
||||
|
||||
# copy repository
|
||||
WORKDIR /build/sunshine/
|
||||
COPY .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
/build/sunshine
|
||||
make -j "$(nproc)"
|
||||
cpack -G DEB
|
||||
_MAKE
|
||||
|
||||
FROM scratch AS artifacts
|
||||
ARG BASE
|
||||
ARG TAG
|
||||
ARG TARGETARCH
|
||||
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
|
||||
|
||||
FROM sunshine-base as sunshine
|
||||
|
||||
# copy deb from builder
|
||||
COPY --from=artifacts /sunshine*.deb /sunshine.deb
|
||||
|
||||
# install sunshine
|
||||
RUN <<_INSTALL_SUNSHINE
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends /sunshine.deb
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_INSTALL_SUNSHINE
|
||||
|
||||
# network setup
|
||||
EXPOSE 47984-47990/tcp
|
||||
EXPOSE 48010
|
||||
EXPOSE 47998-48000/udp
|
||||
|
||||
# setup user
|
||||
ARG PGID=1000
|
||||
ENV PGID=${PGID}
|
||||
ARG PUID=1000
|
||||
ENV PUID=${PUID}
|
||||
ENV TZ="UTC"
|
||||
ARG UNAME=lizard
|
||||
ENV UNAME=${UNAME}
|
||||
|
||||
ENV HOME=/home/$UNAME
|
||||
|
||||
# setup user
|
||||
RUN <<_SETUP_USER
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
_SETUP_USER
|
||||
|
||||
USER ${UNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
# entrypoint
|
||||
ENTRYPOINT ["/usr/bin/sunshine"]
|
||||
156
docker/ubuntu-22.04.dockerfile
Normal file
156
docker/ubuntu-22.04.dockerfile
Normal file
@@ -0,0 +1,156 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# artifacts: true
|
||||
# platforms: linux/amd64,linux/arm64/v8
|
||||
# platforms_pr: linux/amd64
|
||||
ARG BASE=ubuntu
|
||||
ARG TAG=22.04
|
||||
FROM ${BASE}:${TAG} AS sunshine-base
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
FROM sunshine-base as sunshine-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN echo "target_platform: ${TARGETPLATFORM}"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# install dependencies
|
||||
RUN <<_DEPS
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential=12.9* \
|
||||
cmake=3.22.1* \
|
||||
libavdevice-dev=7:4.4.* \
|
||||
libboost-filesystem-dev=1.74.0* \
|
||||
libboost-log-dev=1.74.0* \
|
||||
libboost-program-options-dev=1.74.0* \
|
||||
libboost-thread-dev=1.74.0* \
|
||||
libcap-dev=1:2.44* \
|
||||
libcurl4-openssl-dev=7.81.0* \
|
||||
libdrm-dev=2.4.110* \
|
||||
libevdev-dev=1.12.1* \
|
||||
libnuma-dev=2.0.14* \
|
||||
libopus-dev=1.3.1* \
|
||||
libpulse-dev=1:15.99.1* \
|
||||
libssl-dev=3.0.2* \
|
||||
libva-dev=2.14.0* \
|
||||
libvdpau-dev=1.4* \
|
||||
libwayland-dev=1.20.0* \
|
||||
libx11-dev=2:1.7.5* \
|
||||
libxcb-shm0-dev=1.14* \
|
||||
libxcb-xfixes0-dev=1.14* \
|
||||
libxcb1-dev=1.14* \
|
||||
libxfixes-dev=1:6.0.0* \
|
||||
libxrandr-dev=2:1.5.2* \
|
||||
libxtst-dev=2:1.2.3* \
|
||||
nodejs=12.22.9* \
|
||||
npm=8.5.1* \
|
||||
wget=1.21.2*
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmfx-dev=22.3.0*
|
||||
fi
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_DEPS
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
ENV CUDA_VERSION="11.8.0"
|
||||
ENV CUDA_BUILD="520.61.05"
|
||||
# hadolint ignore=SC3010
|
||||
RUN <<_INSTALL_CUDA
|
||||
#!/bin/bash
|
||||
cuda_prefix="https://developer.download.nvidia.com/compute/cuda/"
|
||||
cuda_suffix=""
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then
|
||||
cuda_suffix="_sbsa"
|
||||
fi
|
||||
url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run"
|
||||
echo "cuda url: ${url}"
|
||||
wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
_INSTALL_CUDA
|
||||
|
||||
# copy repository
|
||||
WORKDIR /build/sunshine/
|
||||
COPY .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=share/sunshine \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
/build/sunshine
|
||||
make -j "$(nproc)"
|
||||
cpack -G DEB
|
||||
_MAKE
|
||||
|
||||
FROM scratch AS artifacts
|
||||
ARG BASE
|
||||
ARG TAG
|
||||
ARG TARGETARCH
|
||||
COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb
|
||||
|
||||
FROM sunshine-base as sunshine
|
||||
|
||||
# copy deb from builder
|
||||
COPY --from=artifacts /sunshine*.deb /sunshine.deb
|
||||
|
||||
# install sunshine
|
||||
RUN <<_INSTALL_SUNSHINE
|
||||
#!/bin/bash
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends /sunshine.deb
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_INSTALL_SUNSHINE
|
||||
|
||||
# network setup
|
||||
EXPOSE 47984-47990/tcp
|
||||
EXPOSE 48010
|
||||
EXPOSE 47998-48000/udp
|
||||
|
||||
# setup user
|
||||
ARG PGID=1000
|
||||
ENV PGID=${PGID}
|
||||
ARG PUID=1000
|
||||
ENV PUID=${PUID}
|
||||
ENV TZ="UTC"
|
||||
ARG UNAME=lizard
|
||||
ENV UNAME=${UNAME}
|
||||
|
||||
ENV HOME=/home/$UNAME
|
||||
|
||||
# setup user
|
||||
RUN <<_SETUP_USER
|
||||
groupadd -f -g "${PGID}" "${UNAME}"
|
||||
useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}"
|
||||
mkdir -p ${HOME}/.config/sunshine
|
||||
ln -s ${HOME}/.config/sunshine /config
|
||||
chown -R ${UNAME} ${HOME}
|
||||
_SETUP_USER
|
||||
|
||||
USER ${UNAME}
|
||||
WORKDIR ${HOME}
|
||||
|
||||
# entrypoint
|
||||
ENTRYPOINT ["/usr/bin/sunshine"]
|
||||
@@ -1,4 +1,4 @@
|
||||
furo==2022.12.7
|
||||
m2r2==0.3.3
|
||||
Sphinx==6.1.1
|
||||
Sphinx==6.1.3
|
||||
sphinx-copybutton==0.5.1
|
||||
|
||||
@@ -739,13 +739,14 @@ encoder
|
||||
.. table::
|
||||
:widths: auto
|
||||
|
||||
======== ===========
|
||||
Value Description
|
||||
======== ===========
|
||||
nvenc For Nvidia graphics cards
|
||||
amdvce For AMD graphics cards
|
||||
software Encoding occurs on the CPU
|
||||
======== ===========
|
||||
========= ===========
|
||||
Value Description
|
||||
========= ===========
|
||||
nvenc For NVIDIA graphics cards
|
||||
quicksync For Intel graphics cards
|
||||
amdvce For AMD graphics cards
|
||||
software Encoding occurs on the CPU
|
||||
========= ===========
|
||||
|
||||
**Default**
|
||||
Sunshine will use the first encoder that is available.
|
||||
@@ -843,7 +844,8 @@ nv_preset
|
||||
**Description**
|
||||
The encoder preset to use.
|
||||
|
||||
.. Note:: This option only applies when using nvenc `encoder`_.
|
||||
.. Note:: This option only applies when using nvenc `encoder`_. For more information on the presets, see
|
||||
`nvenc preset migration guide <https://docs.nvidia.com/video-technologies/video-codec-sdk/nvenc-preset-migration-guide/>`_.
|
||||
|
||||
**Choices**
|
||||
|
||||
@@ -958,6 +960,68 @@ nv_coder
|
||||
|
||||
nv_coder = auto
|
||||
|
||||
qsv_preset
|
||||
^^^^^^^^^^
|
||||
|
||||
**Description**
|
||||
The encoder preset to use.
|
||||
|
||||
.. Note:: This option only applies when using quicksync `encoder`_.
|
||||
|
||||
**Choices**
|
||||
|
||||
.. table::
|
||||
:widths: auto
|
||||
|
||||
========== ===========
|
||||
Value Description
|
||||
========== ===========
|
||||
veryfast fastest (lowest quality)
|
||||
faster faster (lower quality)
|
||||
fast fast (low quality)
|
||||
medium medium (default)
|
||||
slow slow (good quality)
|
||||
slower slower (better quality)
|
||||
veryslow slowest (best quality)
|
||||
========== ===========
|
||||
|
||||
**Default**
|
||||
``medium``
|
||||
|
||||
**Example**
|
||||
.. code-block:: text
|
||||
|
||||
qsv_preset = medium
|
||||
|
||||
qsv_coder
|
||||
^^^^^^^^^
|
||||
|
||||
**Description**
|
||||
The entropy encoding to use.
|
||||
|
||||
.. Note:: This option only applies when using H264 with quicksync `encoder`_.
|
||||
|
||||
**Choices**
|
||||
|
||||
.. table::
|
||||
:widths: auto
|
||||
|
||||
========== ===========
|
||||
Value Description
|
||||
========== ===========
|
||||
auto let ffmpeg decide
|
||||
cabac context adaptive binary arithmetic coding - higher quality
|
||||
cavlc context adaptive variable-length coding - faster decode
|
||||
========== ===========
|
||||
|
||||
**Default**
|
||||
``auto``
|
||||
|
||||
**Example**
|
||||
.. code-block:: text
|
||||
|
||||
qsv_coder = auto
|
||||
|
||||
amd_quality
|
||||
^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -23,6 +23,28 @@ Linux
|
||||
-----
|
||||
Follow the instructions for your preferred package type below.
|
||||
|
||||
**CUDA Compatibility**
|
||||
|
||||
CUDA is used for NVFBC capture.
|
||||
|
||||
.. Tip:: See `CUDA GPUS <https://developer.nvidia.com/cuda-gpus>`_ to cross reference Compute Capability to your GPU.
|
||||
|
||||
.. table::
|
||||
:widths: auto
|
||||
|
||||
=========================================== ============== ============== ================================
|
||||
Package CUDA Version Min Driver CUDA Compute Capabilities
|
||||
=========================================== ============== ============== ================================
|
||||
https://aur.archlinux.org/packages/sunshine User dependent User dependent User dependent
|
||||
sunshine.AppImage 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
sunshine_{arch}.flatpak 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
sunshine-debian-bullseye-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
sunshine-fedora-36-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
|
||||
sunshine-fedora-37-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90
|
||||
sunshine-ubuntu-20.04-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
sunshine-ubuntu-22.04-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35
|
||||
=========================================== ============== ============== ================================
|
||||
|
||||
AppImage
|
||||
^^^^^^^^
|
||||
According to AppImageLint the supported distro matrix of the AppImage is below.
|
||||
|
||||
@@ -87,9 +87,13 @@ Sunshine needs access to `uinput` to create mouse and gamepad events.
|
||||
|
||||
[Unit]
|
||||
Description=Sunshine self-hosted game stream host for Moonlight.
|
||||
StartLimitIntervalSec=500
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
ExecStart=<see table>
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
#Flatpak Only
|
||||
#ExecStop=flatpak kill dev.lizardbyte.sunshine
|
||||
|
||||
@@ -190,7 +194,7 @@ Shortcuts
|
||||
All shortcuts start with ``CTRL + ALT + SHIFT``, just like Moonlight
|
||||
|
||||
- ``CTRL + ALT + SHIFT + N`` - Hide/Unhide the cursor (This may be useful for Remote Desktop Mode for Moonlight)
|
||||
- ``CTRL + ALT + SHIFT + F1/F13`` - Switch to different monitor for Streaming
|
||||
- ``CTRL + ALT + SHIFT + F1/F12`` - Switch to different monitor for Streaming
|
||||
|
||||
Application List
|
||||
----------------
|
||||
@@ -257,6 +261,18 @@ Considerations
|
||||
instead it simply starts a stream.
|
||||
- For the Linux flatpak you must prepend commands with ``flatpak-spawn --host``.
|
||||
|
||||
HDR Support
|
||||
-----------
|
||||
Streaming HDR content is supported for Windows hosts with NVIDIA, AMD, or Intel GPUs that support encoding HEVC Main 10.
|
||||
You must have an HDR-capable display or EDID emulator dongle connected to your host PC to activate HDR in Windows.
|
||||
|
||||
- Ensure you enable the HDR option in your Moonlight client settings, otherwise the stream will be SDR.
|
||||
- A good HDR experience relies on proper HDR display calibration both in Windows and in game. HDR calibration can differ significantly between client and host displays.
|
||||
- We recommend calibrating the display by streaming the Windows HDR Calibration app to your client device and saving an HDR calibration profile to use while streaming.
|
||||
- You may also need to tune the brightness slider or HDR calibration options in game to the different HDR brightness capabilities of your client's display.
|
||||
- Older games that use NVIDIA-specific NVAPI HDR rather than native Windows 10 OS HDR support may not display in HDR.
|
||||
- Some GPUs can produce lower image quality or encoding performance when streaming in HDR compared to SDR.
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
Tutorial videos are available `here <https://www.youtube.com/playlist?list=PLMYr5_xSeuXAbhxYHz86hA1eCDugoxXY0>`_.
|
||||
|
||||
@@ -17,11 +17,13 @@ Install Requirements
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libboost-program-options-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \ # KMS
|
||||
libcurl4-openssl-dev \
|
||||
libdrm-dev \ # KMS
|
||||
libevdev-dev \
|
||||
libmfx-dev \ # x86_64 only
|
||||
libnuma-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
@@ -41,25 +43,24 @@ Install Requirements
|
||||
nvidia-cuda-dev \ # Cuda, NvFBC
|
||||
nvidia-cuda-toolkit # Cuda, NvFBC
|
||||
|
||||
Fedora 36
|
||||
^^^^^^^^^
|
||||
Fedora 36, 37
|
||||
^^^^^^^^^^^^^
|
||||
End of Life: TBD
|
||||
|
||||
Install Repositories
|
||||
.. code-block:: bash
|
||||
|
||||
sudo dnf update && \
|
||||
sudo dnf group install "Development Tools" && \
|
||||
sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
|
||||
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
sudo dnf update && \
|
||||
sudo dnf group install "Development Tools" && \
|
||||
sudo dnf install \
|
||||
boost-devel \
|
||||
boost-static.x86_64 \
|
||||
boost-static \
|
||||
cmake \
|
||||
gcc \
|
||||
gcc-c++ \
|
||||
libcap-devel \
|
||||
libcurl-devel \
|
||||
libdrm-devel \
|
||||
libevdev-devel \
|
||||
libva-devel \
|
||||
libvdpau-devel \
|
||||
@@ -67,18 +68,21 @@ Install Requirements
|
||||
libxcb-devel \ # X11
|
||||
libXcursor-devel \ # X11
|
||||
libXfixes-devel \ # X11
|
||||
libXinerama-devel \ # X11
|
||||
libXi-devel \ # X11
|
||||
libXinerama-devel \ # X11
|
||||
libXrandr-devel \ # X11
|
||||
libXtst-devel \ # X11
|
||||
mesa-libGL-devel \
|
||||
nodejs \
|
||||
npm \
|
||||
numactl-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
pulseaudio-libs-devel \
|
||||
rpm-build # if you want to build an RPM binary package
|
||||
rpm-build \ # if you want to build an RPM binary package
|
||||
wget \ # necessary for cuda install with `run` file
|
||||
which \ # necessary for cuda install with `run` file
|
||||
# libmfx-devel is not listed for fedora, this is for x86_64 only
|
||||
https://kojipkgs.fedoraproject.org//packages/libmfx/1.25/4.el8/x86_64/libmfx-devel-1.25-4.el8.x86_64.rpm
|
||||
|
||||
Ubuntu 20.04
|
||||
^^^^^^^^^^^^
|
||||
@@ -99,6 +103,7 @@ Install Requirements
|
||||
libcap-dev \ # KMS
|
||||
libdrm-dev \ # KMS
|
||||
libevdev-dev \
|
||||
libmfx-dev \ # x86_64 only
|
||||
libnuma-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
@@ -115,18 +120,17 @@ Install Requirements
|
||||
libxtst-dev \ # X11
|
||||
nodejs \
|
||||
npm \
|
||||
wget
|
||||
wget # necessary for cuda install with `run` file
|
||||
|
||||
Update gcc alias
|
||||
.. code-block:: bash
|
||||
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10
|
||||
|
||||
Install CuDA
|
||||
.. code-block:: bash
|
||||
|
||||
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 ./cuda.run && chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run
|
||||
update-alternatives --install \
|
||||
/usr/bin/gcc gcc /usr/bin/gcc-10 100 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-10 \
|
||||
--slave /usr/bin/gcov gcov /usr/bin/gcov-10 \
|
||||
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \
|
||||
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10
|
||||
|
||||
Ubuntu 22.04
|
||||
^^^^^^^^^^^^
|
||||
@@ -146,6 +150,7 @@ Install Requirements
|
||||
libcap-dev \ # KMS
|
||||
libdrm-dev \ # KMS
|
||||
libevdev-dev \
|
||||
libmfx-dev \ # x86_64 only
|
||||
libnuma-dev \
|
||||
libopus-dev \
|
||||
libpulse-dev \
|
||||
@@ -160,8 +165,26 @@ Install Requirements
|
||||
libxtst-dev \ # X11
|
||||
nodejs \
|
||||
npm \
|
||||
nvidia-cuda-dev \ # Cuda, NvFBC
|
||||
nvidia-cuda-toolkit # Cuda, NvFBC
|
||||
nvidia-cuda-dev \ # CUDA, NvFBC
|
||||
nvidia-cuda-toolkit # CUDA, NvFBC
|
||||
|
||||
CUDA
|
||||
----
|
||||
If the version of CUDA available from your distro is not adequate, manually install CUDA.
|
||||
|
||||
.. Tip:: The version of CUDA you use will determine compatibility with various GPU generations.
|
||||
See `CUDA compatibility <https://docs.nvidia.com/deploy/cuda-compatibility/index.html>`_ for more info.
|
||||
|
||||
Select the appropriate run file based on your desired CUDA version and architecture according to
|
||||
`CUDA Toolkit Archive <https://developer.nvidia.com/cuda-toolkit-archive>`_.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
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 ./cuda.run
|
||||
chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
|
||||
npm dependencies
|
||||
----------------
|
||||
|
||||
@@ -16,7 +16,8 @@ Install dependencies:
|
||||
|
||||
pacman -S base-devel cmake diffutils gcc git make mingw-w64-x86_64-binutils \
|
||||
mingw-w64-x86_64-boost mingw-w64-x86_64-cmake mingw-w64-x86_64-curl \
|
||||
mingw-w64-x86_64-openssl mingw-w64-x86_64-opus mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-libmfx mingw-w64-x86_64-openssl mingw-w64-x86_64-opus \
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
npm dependencies
|
||||
----------------
|
||||
|
||||
@@ -16,6 +16,5 @@ Limitations
|
||||
-----------
|
||||
Sunshine does have some limitations, as compared to Nvidia GameStream.
|
||||
|
||||
- HDR support is limited and currently HDR is converted to SDR.
|
||||
- Automatic game/application list.
|
||||
- Changing game settings automatically, to optimize streaming.
|
||||
|
||||
@@ -9,9 +9,11 @@ arch=('x86_64' 'i686')
|
||||
url=@PROJECT_HOMEPAGE_URL@
|
||||
license=('GPL3')
|
||||
|
||||
depends=('avahi' 'boost-libs' 'curl' 'libevdev' 'libpulse' 'libva' 'libvdpau' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'numactl' 'openssl' 'opus' 'udev')
|
||||
depends=('avahi' 'boost-libs' 'curl' 'libevdev' 'libmfx' 'libpulse' 'libva' 'libvdpau' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'numactl' 'openssl' 'opus' 'udev')
|
||||
makedepends=('boost' 'cmake' 'git' 'make' 'nodejs' 'npm')
|
||||
optdepends=('cuda' 'libcap' 'libdrm')
|
||||
optdepends=('cuda: NvFBC capture support'
|
||||
'libcap'
|
||||
'libdrm')
|
||||
|
||||
provides=(@SUNSHINE_AUR_PROVIDES@)
|
||||
conflicts=(@SUNSHINE_AUR_CONFLICTS@)
|
||||
|
||||
@@ -40,6 +40,12 @@ modules:
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/b/boost1.74/boost1.74_1.74.0.orig.tar.xz
|
||||
sha256: 2467be4af625b5ae4b3c93fc7af196a09eba39c11a7338cd9e8b356fa44d2f45
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/b/boost1.74/boost1.74_1.74.0-17ubuntu1.debian.tar.xz
|
||||
sha256: 22e623d98c84eb3fec57e19ea371157a5bc8225ba4c5907f7e5155072317a31d
|
||||
- type: shell
|
||||
commands:
|
||||
- for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done
|
||||
|
||||
- name: avahi
|
||||
disabled: false
|
||||
@@ -74,6 +80,13 @@ modules:
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8.orig.tar.gz
|
||||
sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8-6ubuntu1.debian.tar.xz
|
||||
sha256: ebf1dfe5e853b6bc6843e3bd784cb6af632041f305abd0e5415114f80c1dcea4
|
||||
- type: shell
|
||||
commands:
|
||||
- for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done
|
||||
- autoreconf -ivf
|
||||
|
||||
- name: libevdev
|
||||
disabled: false
|
||||
@@ -87,34 +100,56 @@ modules:
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.13.0+dfsg.orig.tar.xz
|
||||
sha256: a882e13ef1dd6bd227318080cabf60fe5af3c06471259d3acfc9dbfb202351a7
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.13.0+dfsg-1.debian.tar.xz
|
||||
sha256: d33c56acbbfff2dc540e45c57a38d92210b5e7fd0947ac47fbe48183468aad74
|
||||
- type: shell
|
||||
commands:
|
||||
- for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done
|
||||
|
||||
- name: cuda
|
||||
- name: intel-mediasdk
|
||||
disabled: false
|
||||
buildsystem: simple
|
||||
buildsystem: cmake
|
||||
config-opts:
|
||||
- -DENABLE_OPENCL=ON
|
||||
- -DENABLE_X11_DRI3=ON
|
||||
- -DENABLE_WAYLAND=ON
|
||||
- -DENABLE_ITT=OFF
|
||||
- -DENABLE_TEXTLOG=OFF
|
||||
- -DENABLE_STAT=OFF
|
||||
- -DBUILD_ALL=OFF
|
||||
- -DBUILD_RUNTIME=ON
|
||||
- -DBUILD_SAMPLES=OFF
|
||||
- -DBUILD_TESTS=OFF
|
||||
- -DBUILD_TOOLS=OFF
|
||||
- -DUSE_SYSTEM_GTEST=OFF
|
||||
- -DMFX_ENABLE_KERNELS=ON
|
||||
only-arches:
|
||||
- x86_64
|
||||
- aarch64
|
||||
cleanup:
|
||||
- '*'
|
||||
build-commands:
|
||||
- chmod u+x ./cuda.run
|
||||
- ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
--tmpdir=$FLATPAK_BUILDER_BUILDDIR
|
||||
- rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2
|
||||
- rm ./cuda.run
|
||||
sources:
|
||||
- type: file
|
||||
only-arches:
|
||||
- x86_64
|
||||
url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run
|
||||
sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a
|
||||
dest-filename: cuda.run
|
||||
- type: file
|
||||
only-arches:
|
||||
- aarch64
|
||||
url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run # yamllint disable-line rule:line-length
|
||||
sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8
|
||||
dest-filename: cuda.run
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/universe/i/intel-mediasdk/intel-mediasdk_22.3.0.orig.tar.gz
|
||||
sha256: e1e74229f409e969b70c2b35b1955068de3d40db85ecc42bd6ff501468bc76d7
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/universe/i/intel-mediasdk/intel-mediasdk_22.3.0-1.debian.tar.xz
|
||||
sha256: 024d98d2f63443d2765a90cfe997d104e7b897694889f199ca8fb4d9ffdcf1dc
|
||||
- type: shell
|
||||
commands:
|
||||
- for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done
|
||||
modules:
|
||||
- name: libdrm
|
||||
disabled: false
|
||||
buildsystem: meson
|
||||
sources:
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110.orig.tar.xz
|
||||
sha256: eecee4c4b47ed6d6ce1a9be3d6d92102548ea35e442282216d47d05293cf9737
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110-1ubuntu1.debian.tar.xz
|
||||
sha256: 464b9553861f39beddfaee6b8924734b02a0febfae3968e4ca1360f2972bba8b
|
||||
- type: shell
|
||||
commands:
|
||||
- for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done
|
||||
|
||||
- name: numactl
|
||||
buildsystem: autotools
|
||||
@@ -124,9 +159,44 @@ modules:
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14.orig.tar.gz
|
||||
sha256: 1ee27abd07ff6ba140aaf9bc6379b37825e54496e01d6f7343330cf1a4487035
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14-3ubuntu2.debian.tar.xz
|
||||
sha256: 49089e5be5367f6367f8b0389d1d523944432607783b53f0605705792e1015ee
|
||||
- type: shell
|
||||
commands:
|
||||
- for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done
|
||||
cleanup:
|
||||
- "/bin"
|
||||
|
||||
# Caching is configured until here, not including CUDA
|
||||
- name: cuda
|
||||
disabled: false
|
||||
buildsystem: simple
|
||||
only-arches:
|
||||
- x86_64
|
||||
- aarch64
|
||||
cleanup:
|
||||
- "*"
|
||||
build-commands:
|
||||
- chmod u+x ./cuda.run
|
||||
- ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm
|
||||
--tmpdir=$FLATPAK_BUILDER_BUILDDIR
|
||||
- rm -r $FLATPAK_DEST/cuda/nsight-systems-*
|
||||
- rm ./cuda.run
|
||||
sources:
|
||||
- type: file
|
||||
only-arches:
|
||||
- x86_64
|
||||
url: https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run
|
||||
sha256: 9223c4af3aebe4a7bbed9abd9b163b03a1b34b855fbc2b4a0d1b706ac09a5a16
|
||||
dest-filename: cuda.run
|
||||
- type: file
|
||||
only-arches:
|
||||
- aarch64
|
||||
url: https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux_sbsa.run # yamllint disable-line rule:line-length
|
||||
sha256: e6e9a8d31163c9776b5e313fd7590877c5684e1ecddee741154f95704d4ed27c
|
||||
dest-filename: cuda.run
|
||||
|
||||
- name: sunshine
|
||||
disabled: false
|
||||
buildsystem: cmake
|
||||
@@ -156,9 +226,9 @@ modules:
|
||||
- -DSUNSHINE_CONFIGURE_FLATPAK=ON
|
||||
sources:
|
||||
- type: git
|
||||
url: '@GITHUB_CLONE_URL@'
|
||||
branch: '@GITHUB_BRANCH@'
|
||||
commit: '@GITHUB_COMMIT@'
|
||||
url: "@GITHUB_CLONE_URL@"
|
||||
branch: "@GITHUB_BRANCH@"
|
||||
commit: "@GITHUB_COMMIT@"
|
||||
post-install:
|
||||
# use `sed` to update apps.json with prefixes required for flatpak
|
||||
# -r (regex)
|
||||
|
||||
@@ -200,6 +200,46 @@ int coder_from_view(const std::string_view &coder) {
|
||||
}
|
||||
} // namespace amd
|
||||
|
||||
namespace qsv {
|
||||
enum preset_e : int {
|
||||
veryslow = 1,
|
||||
slower = 2,
|
||||
slow = 3,
|
||||
medium = 4,
|
||||
fast = 5,
|
||||
faster = 6,
|
||||
veryfast = 7
|
||||
};
|
||||
|
||||
enum cavlc_e : int {
|
||||
_auto = false,
|
||||
enabled = true,
|
||||
disabled = false
|
||||
};
|
||||
|
||||
std::optional<int> preset_from_view(const std::string_view &preset) {
|
||||
#define _CONVERT_(x) \
|
||||
if(preset == #x##sv) return x
|
||||
_CONVERT_(veryslow);
|
||||
_CONVERT_(slower);
|
||||
_CONVERT_(slow);
|
||||
_CONVERT_(medium);
|
||||
_CONVERT_(fast);
|
||||
_CONVERT_(faster);
|
||||
_CONVERT_(veryfast);
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> coder_from_view(const std::string_view &coder) {
|
||||
if(coder == "auto"sv) return _auto;
|
||||
if(coder == "cabac"sv || coder == "ac"sv) return disabled;
|
||||
if(coder == "cavlc"sv || coder == "vlc"sv) return enabled;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace qsv
|
||||
|
||||
namespace vt {
|
||||
|
||||
enum coder_e : int {
|
||||
@@ -254,6 +294,11 @@ video_t video {
|
||||
nv::_auto // coder
|
||||
}, // nv
|
||||
|
||||
{
|
||||
qsv::medium, // preset
|
||||
qsv::_auto, // cavlc
|
||||
}, // qsv
|
||||
|
||||
{
|
||||
(int)amd::quality_h264_e::balanced, // quality (h264)
|
||||
(int)amd::quality_hevc_e::balanced, // quality (hevc)
|
||||
@@ -261,11 +306,13 @@ video_t video {
|
||||
(int)amd::rc_hevc_e::vbr_latency, // rate control (hevc)
|
||||
(int)amd::coder_e::_auto, // coder
|
||||
}, // amd
|
||||
|
||||
{
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
-1 }, // vt
|
||||
-1,
|
||||
}, // vt
|
||||
|
||||
{}, // encoder
|
||||
{}, // adapter_name
|
||||
@@ -761,6 +808,9 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
int_f(vars, "nv_rc", video.nv.rc, nv::rc_from_view);
|
||||
int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view);
|
||||
|
||||
int_f(vars, "qsv_preset", video.qsv.preset, qsv::preset_from_view);
|
||||
int_f(vars, "qsv_coder", video.qsv.cavlc, qsv::coder_from_view);
|
||||
|
||||
std::string quality;
|
||||
string_f(vars, "amd_quality", quality);
|
||||
if(!quality.empty()) {
|
||||
|
||||
@@ -28,6 +28,11 @@ struct video_t {
|
||||
int coder;
|
||||
} nv;
|
||||
|
||||
struct {
|
||||
std::optional<int> preset;
|
||||
std::optional<int> cavlc;
|
||||
} qsv;
|
||||
|
||||
struct {
|
||||
std::optional<int> quality_h264;
|
||||
std::optional<int> quality_hevc;
|
||||
|
||||
@@ -56,7 +56,7 @@ void print_req(const req_https_t &request) {
|
||||
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
|
||||
|
||||
for(auto &[name, val] : request->header) {
|
||||
BOOST_LOG(debug) << name << " -- " << val;
|
||||
BOOST_LOG(debug) << name << " -- " << (name == "Authorization" ? "CREDENTIALS REDACTED" : val);
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << " [--] "sv;
|
||||
@@ -246,23 +246,40 @@ void getSunshineLogoImage(resp_https_t response, req_https_t request) {
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
bool isChildPath(fs::path const &base, fs::path const &query) {
|
||||
auto relPath = fs::relative(base, query);
|
||||
return *(relPath.begin()) != fs::path("..");
|
||||
}
|
||||
|
||||
void getNodeModules(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
fs::path webDirPath(WEB_DIR);
|
||||
fs::path nodeModulesPath(webDirPath / "node_modules");
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
if(boost::algorithm::iends_with(request->path, ".ttf") == 1) {
|
||||
std::ifstream in((WEB_DIR + request->path).c_str(), std::ios::binary);
|
||||
headers.emplace("Content-Type", "font/ttf");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
// .relative_path is needed to shed any leading slash that might exist in the request path
|
||||
auto filePath = fs::weakly_canonical(webDirPath / fs::path(request->path).relative_path());
|
||||
|
||||
// Don't do anything if file does not exist or is outside the node_modules directory
|
||||
if(!isChildPath(filePath, nodeModulesPath)) {
|
||||
BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the node_modules folder";
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Bad Request");
|
||||
}
|
||||
else if(boost::algorithm::iends_with(request->path, ".woff2") == 1) {
|
||||
std::ifstream in((WEB_DIR + request->path).c_str(), std::ios::binary);
|
||||
headers.emplace("Content-Type", "font/woff2");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
else if(!fs::exists(filePath)) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_not_found);
|
||||
}
|
||||
else {
|
||||
std::string content = read_file((WEB_DIR + request->path).c_str());
|
||||
response->write(content);
|
||||
auto relPath = fs::relative(filePath, webDirPath);
|
||||
if(relPath.extension() == ".ttf" or relPath.extension() == ".woff2") {
|
||||
// Fonts are read differntly
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
std::ifstream in((filePath).c_str(), std::ios::binary);
|
||||
headers.emplace("Content-Type", "font/" + filePath.extension().string().substr(1));
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
else {
|
||||
std::string content = read_file((filePath.string()).c_str());
|
||||
response->write(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -188,12 +188,20 @@ void print(PNV_SCROLL_PACKET packet) {
|
||||
<< "--end mouse scroll packet--"sv;
|
||||
}
|
||||
|
||||
void print(PSS_HSCROLL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse hscroll packet--"sv << std::endl
|
||||
<< "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl
|
||||
<< "--end mouse hscroll packet--"sv;
|
||||
}
|
||||
|
||||
void print(PNV_KEYBOARD_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin keyboard packet--"sv << std::endl
|
||||
<< "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl
|
||||
<< "keyCode ["sv << util::hex(packet->keyCode).to_string_view() << ']' << std::endl
|
||||
<< "modifiers ["sv << util::hex(packet->modifiers).to_string_view() << ']' << std::endl
|
||||
<< "flags ["sv << util::hex(packet->flags).to_string_view() << ']' << std::endl
|
||||
<< "--end keyboard packet--"sv;
|
||||
}
|
||||
|
||||
@@ -238,6 +246,9 @@ void print(void *payload) {
|
||||
case SCROLL_MAGIC_GEN5:
|
||||
print((PNV_SCROLL_PACKET)payload);
|
||||
break;
|
||||
case SS_HSCROLL_MAGIC:
|
||||
print((PSS_HSCROLL_PACKET)payload);
|
||||
break;
|
||||
case KEY_DOWN_EVENT_MAGIC:
|
||||
case KEY_UP_EVENT_MAGIC:
|
||||
print((PNV_KEYBOARD_PACKET)payload);
|
||||
@@ -459,6 +470,10 @@ void passthrough(PNV_SCROLL_PACKET packet) {
|
||||
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
|
||||
}
|
||||
|
||||
void passthrough(PSS_HSCROLL_PACKET packet) {
|
||||
platf::hscroll(platf_input, util::endian::big(packet->scrollAmount));
|
||||
}
|
||||
|
||||
void passthrough(PNV_UNICODE_PACKET packet) {
|
||||
auto size = util::endian::big(packet->header.size) - sizeof(packet->header.magic);
|
||||
platf::unicode(platf_input, packet->text, size);
|
||||
@@ -621,6 +636,9 @@ void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t
|
||||
case SCROLL_MAGIC_GEN5:
|
||||
passthrough((PNV_SCROLL_PACKET)payload);
|
||||
break;
|
||||
case SS_HSCROLL_MAGIC:
|
||||
passthrough((PSS_HSCROLL_PACKET)payload);
|
||||
break;
|
||||
case KEY_DOWN_EVENT_MAGIC:
|
||||
case KEY_UP_EVENT_MAGIC:
|
||||
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
|
||||
|
||||
@@ -51,6 +51,7 @@ MAIL(switch_display);
|
||||
MAIL(touch_port);
|
||||
MAIL(idr);
|
||||
MAIL(rumble);
|
||||
MAIL(hdr);
|
||||
#undef MAIL
|
||||
} // namespace mail
|
||||
#endif // SUNSHINE_MAIN_H
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
using namespace std::literals;
|
||||
namespace nvhttp {
|
||||
|
||||
constexpr auto VERSION = "7.1.431.0";
|
||||
// The negative 4th version number tells Moonlight that this is Sunshine
|
||||
constexpr auto VERSION = "7.1.431.-1";
|
||||
constexpr auto GFE_VERSION = "3.23.0.74";
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -15,22 +15,37 @@
|
||||
#include "src/thread_safe.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
extern "C" {
|
||||
#include <moonlight-common-c/src/Limelight.h>
|
||||
}
|
||||
|
||||
struct sockaddr;
|
||||
struct AVFrame;
|
||||
struct AVBufferRef;
|
||||
struct AVHWFramesContext;
|
||||
|
||||
// Forward declarations of boost classes to avoid having to include boost headers
|
||||
// here, which results in issues with Windows.h and WinSock2.h include order.
|
||||
namespace boost {
|
||||
namespace asio {
|
||||
namespace ip {
|
||||
class address;
|
||||
} // namespace ip
|
||||
} // namespace asio
|
||||
namespace filesystem {
|
||||
class path;
|
||||
}
|
||||
namespace process {
|
||||
class child;
|
||||
class group;
|
||||
template<typename Char>
|
||||
class basic_environment;
|
||||
typedef basic_environment<char> environment;
|
||||
} // namespace process
|
||||
} // namespace boost
|
||||
namespace video {
|
||||
struct config_t;
|
||||
}
|
||||
|
||||
namespace platf {
|
||||
constexpr auto MAX_GAMEPADS = 32;
|
||||
@@ -196,13 +211,25 @@ struct hwdevice_t {
|
||||
/**
|
||||
* implementations must take ownership of 'frame'
|
||||
*/
|
||||
virtual int set_frame(AVFrame *frame) {
|
||||
virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?";
|
||||
return -1;
|
||||
};
|
||||
|
||||
virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {};
|
||||
|
||||
/**
|
||||
* Implementations may set parameters during initialization of the hwframes context
|
||||
*/
|
||||
virtual void init_hwframes(AVHWFramesContext *frames) {};
|
||||
|
||||
/**
|
||||
* Implementations may make modifications required before context derivation
|
||||
*/
|
||||
virtual int prepare_to_derive_context(int hw_device_type) {
|
||||
return 0;
|
||||
};
|
||||
|
||||
virtual ~hwdevice_t() = default;
|
||||
};
|
||||
|
||||
@@ -250,6 +277,15 @@ public:
|
||||
return std::make_shared<hwdevice_t>();
|
||||
}
|
||||
|
||||
virtual bool is_hdr() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
std::memset(&metadata, 0, sizeof(metadata));
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual ~display_t() = default;
|
||||
|
||||
// Offsets for when streaming a specific monitor. By default, they are 0.
|
||||
@@ -295,16 +331,16 @@ std::unique_ptr<audio_control_t> audio_control();
|
||||
* If display_name is empty --> Use the first monitor that's compatible you can find
|
||||
* If you require to use this parameter in a seperate thread --> make a copy of it.
|
||||
*
|
||||
* framerate --> The peak number of images per second
|
||||
* config --> Stream configuration
|
||||
*
|
||||
* Returns display_t based on hwdevice_type
|
||||
*/
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
// 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);
|
||||
|
||||
boost::process::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec);
|
||||
boost::process::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group);
|
||||
|
||||
enum class thread_priority_e : int {
|
||||
low,
|
||||
@@ -321,11 +357,29 @@ void streaming_will_stop();
|
||||
bool restart_supported();
|
||||
bool restart();
|
||||
|
||||
struct batched_send_info_t {
|
||||
const char *buffer;
|
||||
size_t block_size;
|
||||
size_t block_count;
|
||||
|
||||
std::uintptr_t native_socket;
|
||||
boost::asio::ip::address &target_address;
|
||||
uint16_t target_port;
|
||||
};
|
||||
bool send_batch(batched_send_info_t &send_info);
|
||||
|
||||
enum class qos_data_type_e : int {
|
||||
audio,
|
||||
video
|
||||
};
|
||||
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type);
|
||||
|
||||
input_t input();
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY);
|
||||
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
|
||||
void button_mouse(input_t &input, int button, bool release);
|
||||
void scroll(input_t &input, int distance);
|
||||
void hscroll(input_t &input, int distance);
|
||||
void keyboard(input_t &input, uint16_t modcode, bool release);
|
||||
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state);
|
||||
void unicode(input_t &input, char *utf8, int size);
|
||||
|
||||
@@ -13,6 +13,7 @@ extern "C" {
|
||||
#include "graphics.h"
|
||||
#include "src/main.h"
|
||||
#include "src/utility.h"
|
||||
#include "src/video.h"
|
||||
#include "wayland.h"
|
||||
|
||||
#define SUNSHINE_STRINGVIEW_HELPER(x) x##sv
|
||||
@@ -94,20 +95,21 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame) override {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
auto hwframe_ctx = (AVHWFramesContext *)frame->hw_frames_ctx->data;
|
||||
auto hwframe_ctx = (AVHWFramesContext *)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;
|
||||
if(!frame->buf[0]) {
|
||||
if(av_hwframe_get_buffer(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;
|
||||
@@ -180,8 +182,8 @@ public:
|
||||
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)) {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
if(cuda_t::set_frame(frame, hw_frames_ctx)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -413,7 +415,7 @@ public:
|
||||
|
||||
class display_t : public platf::display_t {
|
||||
public:
|
||||
int init(const std::string_view &display_name, int framerate) {
|
||||
int init(const std::string_view &display_name, const ::video::config_t &config) {
|
||||
auto handle = handle_t::make();
|
||||
if(!handle) {
|
||||
return -1;
|
||||
@@ -443,14 +445,14 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
delay = std::chrono::nanoseconds { 1s } / framerate;
|
||||
delay = std::chrono::nanoseconds { 1s } / config.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;
|
||||
capture_params.dwSamplingRateMs = 1000 /* ms */ / config.framerate;
|
||||
|
||||
if(streamedMonitor != -1) {
|
||||
auto &output = status_params->outputs[streamedMonitor];
|
||||
@@ -662,7 +664,7 @@ public:
|
||||
} // namespace cuda
|
||||
|
||||
namespace platf {
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if(hwdevice_type != mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
|
||||
return nullptr;
|
||||
@@ -670,7 +672,7 @@ std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::st
|
||||
|
||||
auto display = std::make_shared<cuda::nvfbc::display_t>();
|
||||
|
||||
if(display->init(display_name, framerate)) {
|
||||
if(display->init(display_name, config)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -982,6 +982,19 @@ void scroll(input_t &input, int high_res_distance) {
|
||||
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
void hscroll(input_t &input, int high_res_distance) {
|
||||
int distance = high_res_distance / 120;
|
||||
|
||||
auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
|
||||
if(!mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
libevdev_uinput_write_event(mouse, EV_REL, REL_HWHEEL, distance);
|
||||
libevdev_uinput_write_event(mouse, EV_REL, REL_HWHEEL_HI_RES, high_res_distance);
|
||||
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
static keycode_t keysym(std::uint16_t modcode) {
|
||||
if(modcode <= keycodes.size()) {
|
||||
return keycodes[modcode];
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "src/platform/common.h"
|
||||
#include "src/round_robin.h"
|
||||
#include "src/utility.h"
|
||||
#include "src/video.h"
|
||||
|
||||
// Cursor rendering support through x11
|
||||
#include "graphics.h"
|
||||
@@ -293,6 +294,18 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t get_panel_orientation(std::uint32_t plane_id) {
|
||||
auto props = plane_props(plane_id);
|
||||
for(auto &[prop, val] : props) {
|
||||
if(prop->name == "rotation"sv) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "Failed to determine panel orientation, defaulting to landscape.";
|
||||
return DRM_MODE_ROTATE_0;
|
||||
}
|
||||
|
||||
connector_interal_t connector(std::uint32_t id) {
|
||||
return drmModeGetConnector(fd.el, id);
|
||||
}
|
||||
@@ -444,8 +457,8 @@ class display_t : public platf::display_t {
|
||||
public:
|
||||
display_t(mem_type_e mem_type) : platf::display_t(), mem_type { mem_type } {}
|
||||
|
||||
int init(const std::string &display_name, int framerate) {
|
||||
delay = std::chrono::nanoseconds { 1s } / framerate;
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
|
||||
int monitor_index = util::from_view(display_name);
|
||||
int monitor = 0;
|
||||
@@ -530,11 +543,25 @@ public:
|
||||
if(monitor != std::end(pos->crtc_to_monitor)) {
|
||||
auto &viewport = monitor->second.viewport;
|
||||
|
||||
width = viewport.width;
|
||||
height = viewport.height;
|
||||
width = viewport.width;
|
||||
height = viewport.height;
|
||||
|
||||
switch(card.get_panel_orientation(plane->plane_id)) {
|
||||
case DRM_MODE_ROTATE_270:
|
||||
BOOST_LOG(debug) << "Detected panel orientation at 90, swapping width and height.";
|
||||
width = viewport.height;
|
||||
height = viewport.width;
|
||||
break;
|
||||
case DRM_MODE_ROTATE_90:
|
||||
case DRM_MODE_ROTATE_180:
|
||||
BOOST_LOG(warning) << "Panel orientation is unsupported, screen capture may not work correctly.";
|
||||
break;
|
||||
}
|
||||
|
||||
offset_x = viewport.offset_x;
|
||||
offset_y = viewport.offset_y;
|
||||
}
|
||||
|
||||
// This code path shouldn't happend, but it's there just in case.
|
||||
// crtc_to_monitor is part of the guesswork after all.
|
||||
else {
|
||||
@@ -583,9 +610,12 @@ public:
|
||||
|
||||
for(int y = 0; y < 4; ++y) {
|
||||
if(!fb->handles[y]) {
|
||||
// It's not clear wheter there could still be valid handles left.
|
||||
// setting sd->fds[y] to a negative value indicates that sd->offsets[y] and sd->pitches[y]
|
||||
// are uninitialized and contain invalid values.
|
||||
sd->fds[y] = -1;
|
||||
// It's not clear whether there could still be valid handles left.
|
||||
// So, continue anyway.
|
||||
// TODO: Is this redundent?
|
||||
// TODO: Is this redundant?
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -632,13 +662,13 @@ class display_ram_t : public display_t {
|
||||
public:
|
||||
display_ram_t(mem_type_e mem_type) : display_t(mem_type) {}
|
||||
|
||||
int init(const std::string &display_name, int framerate) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if(!gbm::create_device) {
|
||||
BOOST_LOG(warning) << "libgbm not initialized"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(display_t::init(display_name, framerate)) {
|
||||
if(display_t::init(display_name, config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -852,8 +882,8 @@ public:
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int init(const std::string &display_name, int framerate) {
|
||||
if(display_t::init(display_name, framerate)) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if(display_t::init(display_name, config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -872,11 +902,11 @@ public:
|
||||
|
||||
} // namespace kms
|
||||
|
||||
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if(hwdevice_type == mem_type_e::vaapi) {
|
||||
auto disp = std::make_shared<kms::display_vram_t>(hwdevice_type);
|
||||
|
||||
if(!disp->init(display_name, framerate)) {
|
||||
if(!disp->init(display_name, config)) {
|
||||
return disp;
|
||||
}
|
||||
|
||||
@@ -885,7 +915,7 @@ std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::stri
|
||||
|
||||
auto disp = std::make_shared<kms::display_ram_t>(hwdevice_type);
|
||||
|
||||
if(disp->init(display_name, framerate)) {
|
||||
if(disp->init(display_name, config)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -14,6 +15,7 @@
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/process.hpp>
|
||||
|
||||
#ifdef __GNUC__
|
||||
@@ -143,13 +145,23 @@ std::string get_mac_address(const std::string_view &address) {
|
||||
return "00:00:00:00:00:00"s;
|
||||
}
|
||||
|
||||
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) {
|
||||
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
|
||||
if(!file) {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||
if(!group) {
|
||||
if(!file) {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||
}
|
||||
else {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
|
||||
if(!file) {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec, *group);
|
||||
}
|
||||
else {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec, *group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +187,215 @@ bool restart() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool send_batch(batched_send_info_t &send_info) {
|
||||
auto sockfd = (int)send_info.native_socket;
|
||||
|
||||
// Convert the target address into a sockaddr
|
||||
struct sockaddr_in saddr_v4 = {};
|
||||
struct sockaddr_in6 saddr_v6 = {};
|
||||
struct sockaddr *addr;
|
||||
socklen_t addr_len;
|
||||
if(send_info.target_address.is_v6()) {
|
||||
auto address_v6 = send_info.target_address.to_v6();
|
||||
|
||||
saddr_v6.sin6_family = AF_INET6;
|
||||
saddr_v6.sin6_port = htons(send_info.target_port);
|
||||
saddr_v6.sin6_scope_id = address_v6.scope_id();
|
||||
|
||||
auto addr_bytes = address_v6.to_bytes();
|
||||
memcpy(&saddr_v6.sin6_addr, addr_bytes.data(), sizeof(saddr_v6.sin6_addr));
|
||||
|
||||
addr = (struct sockaddr *)&saddr_v6;
|
||||
addr_len = sizeof(saddr_v6);
|
||||
}
|
||||
else {
|
||||
auto address_v4 = send_info.target_address.to_v4();
|
||||
|
||||
saddr_v4.sin_family = AF_INET;
|
||||
saddr_v4.sin_port = htons(send_info.target_port);
|
||||
|
||||
auto addr_bytes = address_v4.to_bytes();
|
||||
memcpy(&saddr_v4.sin_addr, addr_bytes.data(), sizeof(saddr_v4.sin_addr));
|
||||
|
||||
addr = (struct sockaddr *)&saddr_v4;
|
||||
addr_len = sizeof(saddr_v4);
|
||||
}
|
||||
|
||||
#ifdef UDP_SEGMENT
|
||||
{
|
||||
struct msghdr msg = {};
|
||||
struct iovec iov = {};
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(uint16_t))];
|
||||
struct cmsghdr alignment;
|
||||
} cmbuf;
|
||||
|
||||
// UDP GSO on Linux currently only supports sending 64K or 64 segments at a time
|
||||
size_t seg_index = 0;
|
||||
const size_t seg_max = 65536 / 1500;
|
||||
while(seg_index < send_info.block_count) {
|
||||
iov.iov_base = (void *)&send_info.buffer[seg_index * send_info.block_size];
|
||||
iov.iov_len = send_info.block_size * std::min(send_info.block_count - seg_index, seg_max);
|
||||
|
||||
msg.msg_name = addr;
|
||||
msg.msg_namelen = addr_len;
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
// We should not use GSO if the data is <= one full block size
|
||||
if(iov.iov_len > send_info.block_size) {
|
||||
msg.msg_control = cmbuf.buf;
|
||||
msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t));
|
||||
|
||||
// Enable GSO to perform segmentation of our buffer for us
|
||||
auto cm = CMSG_FIRSTHDR(&msg);
|
||||
cm->cmsg_level = SOL_UDP;
|
||||
cm->cmsg_type = UDP_SEGMENT;
|
||||
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
|
||||
*((uint16_t *)CMSG_DATA(cm)) = send_info.block_size;
|
||||
}
|
||||
else {
|
||||
msg.msg_control = nullptr;
|
||||
msg.msg_controllen = 0;
|
||||
}
|
||||
|
||||
// This will fail if GSO is not available, so we will fall back to non-GSO if
|
||||
// it's the first sendmsg() call. On subsequent calls, we will treat errors as
|
||||
// actual failures and return to the caller.
|
||||
auto bytes_sent = sendmsg(sockfd, &msg, 0);
|
||||
if(bytes_sent < 0) {
|
||||
// If there's no send buffer space, wait for some to be available
|
||||
if(errno == EAGAIN) {
|
||||
struct pollfd pfd;
|
||||
|
||||
pfd.fd = sockfd;
|
||||
pfd.events = POLLOUT;
|
||||
|
||||
if(poll(&pfd, 1, -1) != 1) {
|
||||
BOOST_LOG(warning) << "poll() failed: "sv << errno;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to send again
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
seg_index += bytes_sent / send_info.block_size;
|
||||
}
|
||||
|
||||
// If we sent something, return the status and don't fall back to the non-GSO path.
|
||||
if(seg_index != 0) {
|
||||
return seg_index >= send_info.block_count;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
// If GSO is not supported, use sendmmsg() instead.
|
||||
struct mmsghdr msgs[send_info.block_count];
|
||||
struct iovec iovs[send_info.block_count];
|
||||
for(size_t i = 0; i < send_info.block_count; i++) {
|
||||
iovs[i] = {};
|
||||
iovs[i].iov_base = (void *)&send_info.buffer[i * send_info.block_size];
|
||||
iovs[i].iov_len = send_info.block_size;
|
||||
|
||||
msgs[i] = {};
|
||||
msgs[i].msg_hdr.msg_name = addr;
|
||||
msgs[i].msg_hdr.msg_namelen = addr_len;
|
||||
msgs[i].msg_hdr.msg_iov = &iovs[i];
|
||||
msgs[i].msg_hdr.msg_iovlen = 1;
|
||||
}
|
||||
|
||||
// Call sendmmsg() until all messages are sent
|
||||
size_t blocks_sent = 0;
|
||||
while(blocks_sent < send_info.block_count) {
|
||||
int msgs_sent = sendmmsg(sockfd, &msgs[blocks_sent], send_info.block_count - blocks_sent, 0);
|
||||
if(msgs_sent < 0) {
|
||||
// If there's no send buffer space, wait for some to be available
|
||||
if(errno == EAGAIN) {
|
||||
struct pollfd pfd;
|
||||
|
||||
pfd.fd = sockfd;
|
||||
pfd.events = POLLOUT;
|
||||
|
||||
if(poll(&pfd, 1, -1) != 1) {
|
||||
BOOST_LOG(warning) << "poll() failed: "sv << errno;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to send again
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOST_LOG(warning) << "sendmmsg() failed: "sv << errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
blocks_sent += msgs_sent;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class qos_t : public deinit_t {
|
||||
public:
|
||||
qos_t(int sockfd, int level, int option) : sockfd(sockfd), level(level), option(option) {}
|
||||
|
||||
virtual ~qos_t() {
|
||||
int reset_val = -1;
|
||||
if(setsockopt(sockfd, level, option, &reset_val, sizeof(reset_val)) < 0) {
|
||||
BOOST_LOG(warning) << "Failed to reset IP TOS: "sv << errno;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int sockfd;
|
||||
int level;
|
||||
int option;
|
||||
};
|
||||
|
||||
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) {
|
||||
int sockfd = (int)native_socket;
|
||||
|
||||
int level;
|
||||
int option;
|
||||
if(address.is_v6()) {
|
||||
level = SOL_IPV6;
|
||||
option = IPV6_TCLASS;
|
||||
}
|
||||
else {
|
||||
level = SOL_IP;
|
||||
option = IP_TOS;
|
||||
}
|
||||
|
||||
// The specific DSCP values here are chosen to be consistent with Windows
|
||||
int dscp;
|
||||
switch(data_type) {
|
||||
case qos_data_type_e::video:
|
||||
dscp = 40;
|
||||
break;
|
||||
case qos_data_type_e::audio:
|
||||
dscp = 56;
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unknown traffic type: "sv << (int)data_type;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Shift to put the DSCP value in the correct position in the TOS field
|
||||
dscp <<= 2;
|
||||
|
||||
if(setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<qos_t>(sockfd, level, option);
|
||||
}
|
||||
|
||||
namespace source {
|
||||
enum source_e : std::size_t {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
@@ -197,7 +418,7 @@ 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);
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool verify_nvfbc() {
|
||||
return !nvfbc_display_names().empty();
|
||||
@@ -206,7 +427,7 @@ bool verify_nvfbc() {
|
||||
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
std::vector<std::string> wl_display_names();
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool verify_wl() {
|
||||
return window_system == window_system_e::WAYLAND && !wl_display_names().empty();
|
||||
@@ -215,7 +436,7 @@ bool verify_wl() {
|
||||
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
std::vector<std::string> kms_display_names();
|
||||
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
|
||||
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool verify_kms() {
|
||||
return !kms_display_names().empty();
|
||||
@@ -224,7 +445,7 @@ bool verify_kms() {
|
||||
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
std::vector<std::string> x11_display_names();
|
||||
std::shared_ptr<display_t> x11_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
|
||||
std::shared_ptr<display_t> x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
bool verify_x11() {
|
||||
return window_system == window_system_e::X11 && !x11_display_names().empty();
|
||||
@@ -248,29 +469,29 @@ std::vector<std::string> display_names(mem_type_e hwdevice_type) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
#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);
|
||||
return nvfbc_display(hwdevice_type, display_name, config);
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
if(sources[source::WAYLAND]) {
|
||||
BOOST_LOG(info) << "Screencasting with Wayland's protocol"sv;
|
||||
return wl_display(hwdevice_type, display_name, framerate);
|
||||
return wl_display(hwdevice_type, display_name, config);
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
if(sources[source::KMS]) {
|
||||
BOOST_LOG(info) << "Screencasting with KMS"sv;
|
||||
return kms_display(hwdevice_type, display_name, framerate);
|
||||
return kms_display(hwdevice_type, display_name, config);
|
||||
}
|
||||
#endif
|
||||
#ifdef SUNSHINE_BUILD_X11
|
||||
if(sources[source::X11]) {
|
||||
BOOST_LOG(info) << "Screencasting with X11"sv;
|
||||
return x11_display(hwdevice_type, display_name, framerate);
|
||||
return x11_display(hwdevice_type, display_name, config);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -313,14 +313,15 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame) override {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) {
|
||||
BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv;
|
||||
|
||||
return -1;
|
||||
if(!frame->buf[0]) {
|
||||
if(av_hwframe_get_buffer(hw_frames_ctx, frame, 0)) {
|
||||
BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
va::DRMPRIMESurfaceDescriptor prime;
|
||||
@@ -385,12 +386,14 @@ public:
|
||||
va::display_t::pointer va_display;
|
||||
file_t file;
|
||||
|
||||
frame_t hwframe;
|
||||
|
||||
gbm::gbm_t gbm;
|
||||
egl::display_t display;
|
||||
egl::ctx_t ctx;
|
||||
|
||||
// This must be destroyed before display_t to ensure the GPU
|
||||
// driver is still loaded when vaDestroySurfaces() is called.
|
||||
frame_t hwframe;
|
||||
|
||||
egl::sws_t sws;
|
||||
egl::nv12_t nv12;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "src/platform/common.h"
|
||||
|
||||
#include "src/main.h"
|
||||
#include "src/video.h"
|
||||
#include "vaapi.h"
|
||||
#include "wayland.h"
|
||||
|
||||
@@ -18,8 +19,8 @@ struct img_t : public platf::img_t {
|
||||
|
||||
class wlr_t : public platf::display_t {
|
||||
public:
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
delay = std::chrono::nanoseconds { 1s } / framerate;
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
mem_type = hwdevice_type;
|
||||
|
||||
if(display.init()) {
|
||||
@@ -167,7 +168,7 @@ public:
|
||||
int w, h;
|
||||
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
|
||||
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &h);
|
||||
BOOST_LOG(debug) << "width and height: w "sv << w << ' h ' << h;
|
||||
BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
|
||||
|
||||
gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data);
|
||||
gl::ctx.BindTexture(GL_TEXTURE_2D, 0);
|
||||
@@ -175,8 +176,8 @@ public:
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
if(wlr_t::init(hwdevice_type, display_name, framerate)) {
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if(wlr_t::init(hwdevice_type, display_name, config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -307,7 +308,7 @@ public:
|
||||
} // namespace wl
|
||||
|
||||
namespace platf {
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
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;
|
||||
return nullptr;
|
||||
@@ -315,7 +316,7 @@ std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::strin
|
||||
|
||||
if(hwdevice_type == platf::mem_type_e::vaapi) {
|
||||
auto wlr = std::make_shared<wl::wlr_vram_t>();
|
||||
if(wlr->init(hwdevice_type, display_name, framerate)) {
|
||||
if(wlr->init(hwdevice_type, display_name, config)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -323,7 +324,7 @@ std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::strin
|
||||
}
|
||||
|
||||
auto wlr = std::make_shared<wl::wlr_ram_t>();
|
||||
if(wlr->init(hwdevice_type, display_name, framerate)) {
|
||||
if(wlr->init(hwdevice_type, display_name, config)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/task_pool.h"
|
||||
#include "src/video.h"
|
||||
|
||||
#include "cuda.h"
|
||||
#include "graphics.h"
|
||||
@@ -382,13 +383,13 @@ struct x11_attr_t : public display_t {
|
||||
x11::InitThreads();
|
||||
}
|
||||
|
||||
int init(const std::string &display_name, int framerate) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if(!xdisplay) {
|
||||
BOOST_LOG(error) << "Could not open X11 display"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
delay = std::chrono::nanoseconds { 1s } / framerate;
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
|
||||
xwindow = DefaultRootWindow(xdisplay.get());
|
||||
|
||||
@@ -641,8 +642,8 @@ struct shm_attr_t : public x11_attr_t {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init(const std::string &display_name, int framerate) {
|
||||
if(x11_attr_t::init(display_name, framerate)) {
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if(x11_attr_t::init(display_name, config)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -685,7 +686,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) {
|
||||
std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
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 x11 display with the given hw device type"sv;
|
||||
return nullptr;
|
||||
@@ -700,7 +701,7 @@ std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const st
|
||||
// Attempt to use shared memory X11 to avoid copying the frame
|
||||
auto shm_disp = std::make_shared<shm_attr_t>(hwdevice_type);
|
||||
|
||||
auto status = shm_disp->init(display_name, framerate);
|
||||
auto status = shm_disp->init(display_name, config);
|
||||
if(status > 0) {
|
||||
// x11_attr_t::init() failed, don't bother trying again.
|
||||
return nullptr;
|
||||
@@ -712,7 +713,7 @@ std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const st
|
||||
|
||||
// Fallback
|
||||
auto x11_disp = std::make_shared<x11_attr_t>(hwdevice_type);
|
||||
if(x11_disp->init(display_name, framerate)) {
|
||||
if(x11_disp->init(display_name, config)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
|
||||
// Avoid conflict between AVFoundation and libavutil both defining AVMediaType
|
||||
#define AVMediaType AVMediaType_FFmpeg
|
||||
#include "src/video.h"
|
||||
#undef AVMediaType
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace platf {
|
||||
@@ -147,7 +152,7 @@ struct av_display_t : public display_t {
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if(hwdevice_type != platf::mem_type_e::system) {
|
||||
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
|
||||
return nullptr;
|
||||
@@ -168,7 +173,7 @@ std::shared_ptr<display_t> display(platf::mem_type_e hwdevice_type, const std::s
|
||||
}
|
||||
}
|
||||
|
||||
display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:framerate];
|
||||
display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate];
|
||||
|
||||
if(!display->av_capture) {
|
||||
BOOST_LOG(error) << "Video setup failed."sv;
|
||||
|
||||
@@ -417,6 +417,10 @@ void scroll(input_t &input, int high_res_distance) {
|
||||
CFRelease(upEvent);
|
||||
}
|
||||
|
||||
void hscroll(input_t &input, int high_res_distance) {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
input_t input() {
|
||||
input_t result { new macos_input_t() };
|
||||
|
||||
|
||||
@@ -121,13 +121,23 @@ std::string get_mac_address(const std::string_view &address) {
|
||||
return "00:00:00:00:00:00"s;
|
||||
}
|
||||
|
||||
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) {
|
||||
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
|
||||
if(!file) {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||
if(!group) {
|
||||
if(!file) {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||
}
|
||||
else {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
|
||||
if(!file) {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec, *group);
|
||||
}
|
||||
else {
|
||||
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec, *group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +163,18 @@ bool restart() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool send_batch(batched_send_info_t &send_info) {
|
||||
// Fall back to unbatched send calls
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) {
|
||||
// Unimplemented
|
||||
//
|
||||
// NB: When implementing, remember to consider that some routes can drop DSCP-tagged packets completely!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace platf
|
||||
|
||||
namespace dyn {
|
||||
|
||||
@@ -53,7 +53,7 @@ int nv12_zero_device::convert(platf::img_t &img) {
|
||||
return result > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
int nv12_zero_device::set_frame(AVFrame *frame) {
|
||||
int nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
this->frame = frame;
|
||||
|
||||
av_frame.reset(frame);
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
int init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn);
|
||||
|
||||
int convert(img_t &img);
|
||||
int set_frame(AVFrame *frame);
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx);
|
||||
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <d3dcommon.h>
|
||||
#include <dwmapi.h>
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
@@ -37,6 +37,7 @@ using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter
|
||||
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
|
||||
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
|
||||
using output5_t = util::safe_ptr<IDXGIOutput5, Release<IDXGIOutput5>>;
|
||||
using output6_t = util::safe_ptr<IDXGIOutput6, Release<IDXGIOutput6>>;
|
||||
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
|
||||
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
|
||||
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
|
||||
@@ -115,7 +116,8 @@ public:
|
||||
|
||||
class display_base_t : public display_t {
|
||||
public:
|
||||
int init(int framerate, const std::string &display_name);
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override;
|
||||
|
||||
std::chrono::nanoseconds delay;
|
||||
|
||||
@@ -140,29 +142,34 @@ public:
|
||||
|
||||
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
|
||||
|
||||
virtual bool is_hdr() override;
|
||||
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
|
||||
|
||||
protected:
|
||||
int get_pixel_pitch() {
|
||||
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
|
||||
}
|
||||
|
||||
const char *dxgi_format_to_string(DXGI_FORMAT format);
|
||||
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
|
||||
|
||||
virtual int complete_img(img_t *img, bool dummy) = 0;
|
||||
virtual std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() = 0;
|
||||
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
|
||||
virtual int complete_img(img_t *img, bool dummy) = 0;
|
||||
virtual std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() = 0;
|
||||
virtual std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() = 0;
|
||||
};
|
||||
|
||||
class display_ram_t : public display_base_t {
|
||||
public:
|
||||
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override;
|
||||
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible);
|
||||
|
||||
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
int dummy_img(img_t *img) override;
|
||||
int complete_img(img_t *img, bool dummy) override;
|
||||
std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() override;
|
||||
std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() override;
|
||||
|
||||
int init(int framerate, const std::string &display_name);
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
|
||||
cursor_t cursor;
|
||||
D3D11_MAPPED_SUBRESOURCE img_info;
|
||||
@@ -171,15 +178,15 @@ public:
|
||||
|
||||
class display_vram_t : public display_base_t, public std::enable_shared_from_this<display_vram_t> {
|
||||
public:
|
||||
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override;
|
||||
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible);
|
||||
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
int dummy_img(img_t *img_base) override;
|
||||
int complete_img(img_t *img_base, bool dummy) override;
|
||||
std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() override;
|
||||
std::vector<DXGI_FORMAT> get_supported_hdr_capture_formats() override;
|
||||
|
||||
int init(int framerate, const std::string &display_name);
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
|
||||
std::shared_ptr<platf::hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override;
|
||||
|
||||
@@ -196,6 +203,8 @@ public:
|
||||
gpu_cursor_t cursor_xor;
|
||||
|
||||
texture2d_t last_frame_copy;
|
||||
|
||||
std::atomic<uint32_t> next_image_id;
|
||||
};
|
||||
} // namespace platf::dxgi
|
||||
|
||||
|
||||
@@ -6,16 +6,25 @@
|
||||
#include <codecvt>
|
||||
#include <initguid.h>
|
||||
|
||||
#include <boost/process.hpp>
|
||||
|
||||
// We have to include boost/process.hpp before display.h due to WinSock.h,
|
||||
// but that prevents the definition of NTSTATUS so we must define it ourself.
|
||||
typedef long NTSTATUS;
|
||||
|
||||
#include "display.h"
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/video.h"
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
namespace platf::dxgi {
|
||||
namespace bp = boost::process;
|
||||
|
||||
capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
|
||||
auto capture_status = release_frame();
|
||||
if(capture_status != capture_e::ok) {
|
||||
@@ -79,7 +88,202 @@ duplication_t::~duplication_t() {
|
||||
release_frame();
|
||||
}
|
||||
|
||||
int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
capture_e display_base_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
// Use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION if supported (Windows 10 1809+)
|
||||
HANDLE timer = CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
if(!timer) {
|
||||
timer = CreateWaitableTimerEx(nullptr, nullptr, 0, TIMER_ALL_ACCESS);
|
||||
if(!timer) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(error) << "Failed to create timer: "sv << winerr;
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
auto close_timer = util::fail_guard([timer]() {
|
||||
CloseHandle(timer);
|
||||
});
|
||||
|
||||
while(img) {
|
||||
// This will return false if the HDR state changes or for any number of other
|
||||
// display or GPU changes. We should reinit to examine the updated state of
|
||||
// the display subsystem. It is recommended to call this once per frame.
|
||||
if(!factory->IsCurrent()) {
|
||||
return platf::capture_e::reinit;
|
||||
}
|
||||
|
||||
// If the wait time is between 1 us and 1 second, wait the specified time
|
||||
// and offset the next frame time from the exact current frame time target.
|
||||
auto wait_time_us = std::chrono::duration_cast<std::chrono::microseconds>(next_frame - std::chrono::steady_clock::now()).count();
|
||||
if(wait_time_us > 0 && wait_time_us < 1000000) {
|
||||
LARGE_INTEGER due_time { .QuadPart = -10LL * wait_time_us };
|
||||
SetWaitableTimer(timer, &due_time, 0, nullptr, nullptr, false);
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
next_frame += delay;
|
||||
}
|
||||
else {
|
||||
// If the wait time is negative (meaning the frame is past due) or the
|
||||
// computed wait time is beyond a second (meaning possible clock issues),
|
||||
// just capture the frame now and resynchronize the frame interval with
|
||||
// the current time.
|
||||
next_frame = std::chrono::steady_clock::now() + delay;
|
||||
}
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
switch(status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']';
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
bool set_gpu_preference_on_self(int preference) {
|
||||
// The GPU preferences key uses app path as the value name.
|
||||
WCHAR sunshine_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, sunshine_path, ARRAYSIZE(sunshine_path));
|
||||
|
||||
WCHAR value_data[128];
|
||||
swprintf_s(value_data, L"GpuPreference=%d;", preference);
|
||||
|
||||
auto status = RegSetKeyValueW(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\DirectX\\UserGpuPreferences",
|
||||
sunshine_path,
|
||||
REG_SZ,
|
||||
value_data,
|
||||
(wcslen(value_data) + 1) * sizeof(WCHAR));
|
||||
if(status != ERROR_SUCCESS) {
|
||||
BOOST_LOG(error) << "Failed to set GPU preference: "sv << status;
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Set GPU preference: "sv << preference;
|
||||
return true;
|
||||
}
|
||||
|
||||
// On hybrid graphics systems, Windows will change the order of GPUs reported by
|
||||
// DXGI in accordance with the user's GPU preference. If the selected GPU is a
|
||||
// render-only device with no displays, DXGI will add virtual outputs to the
|
||||
// that device to avoid confusing applications. While this works properly for most
|
||||
// applications, it breaks the Desktop Duplication API because DXGI doesn't proxy
|
||||
// the virtual DXGIOutput to the real GPU it is attached to. When trying to call
|
||||
// DuplicateOutput() on one of these virtual outputs, it fails with DXGI_ERROR_UNSUPPORTED
|
||||
// (even if you try sneaky stuff like passing the ID3D11Device for the iGPU and the
|
||||
// virtual DXGIOutput from the dGPU). Because the GPU preference is once-per-process,
|
||||
// we spawn a helper tool to probe for us before we set our own GPU preference.
|
||||
bool probe_for_gpu_preference(const std::string &display_name) {
|
||||
// If we've already been through here, there's nothing to do this time.
|
||||
static bool set_gpu_preference = false;
|
||||
if(set_gpu_preference) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string cmd = "tools\\ddprobe.exe";
|
||||
|
||||
// We start at 1 because 0 is automatic selection which can be overridden by
|
||||
// the GPU driver control panel options. Since ddprobe.exe can have different
|
||||
// GPU driver overrides than Sunshine.exe, we want to avoid a scenario where
|
||||
// autoselection might work for ddprobe.exe but not for us.
|
||||
for(int i = 1; i < 5; i++) {
|
||||
// Run the probe tool. It returns the status of DuplicateOutput().
|
||||
//
|
||||
// Arg format: [GPU preference] [Display name]
|
||||
HRESULT result;
|
||||
try {
|
||||
result = bp::system(cmd, std::to_string(i), display_name, bp::std_out > bp::null, bp::std_err > bp::null);
|
||||
}
|
||||
catch(bp::process_error &e) {
|
||||
BOOST_LOG(error) << "Failed to start ddprobe.exe: "sv << e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "ddprobe.exe ["sv << i << "] ["sv << display_name << "] returned: 0x"sv << util::hex(result).to_string_view();
|
||||
|
||||
// E_ACCESSDENIED can happen at the login screen. If we get this error,
|
||||
// we know capture would have been supported, because DXGI_ERROR_UNSUPPORTED
|
||||
// would have been raised first if it wasn't.
|
||||
if(result == S_OK || result == E_ACCESSDENIED) {
|
||||
// We found a working GPU preference, so set ourselves to use that.
|
||||
if(set_gpu_preference_on_self(i)) {
|
||||
set_gpu_preference = true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This configuration didn't work, so continue testing others
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the manual options worked, leave the GPU preference alone
|
||||
return false;
|
||||
}
|
||||
|
||||
bool test_dxgi_duplication(adapter_t &adapter, output_t &output) {
|
||||
D3D_FEATURE_LEVEL featureLevels[] {
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
D3D_FEATURE_LEVEL_9_2,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
|
||||
device_t device;
|
||||
auto status = D3D11CreateDevice(
|
||||
adapter.get(),
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
nullptr,
|
||||
D3D11_CREATE_DEVICE_FLAGS,
|
||||
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
D3D11_SDK_VERSION,
|
||||
&device,
|
||||
nullptr,
|
||||
nullptr);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return false;
|
||||
}
|
||||
|
||||
output1_t output1;
|
||||
status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we can use the Desktop Duplication API on this output
|
||||
for(int x = 0; x < 2; ++x) {
|
||||
dup_t dup;
|
||||
status = output1->DuplicateOutput((IUnknown *)device.get(), &dup);
|
||||
if(SUCCEEDED(status)) {
|
||||
return true;
|
||||
}
|
||||
Sleep(200);
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "DuplicateOutput() test failed [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return false;
|
||||
}
|
||||
|
||||
int display_base_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
std::once_flag windows_cpp_once_flag;
|
||||
|
||||
std::call_once(windows_cpp_once_flag, []() {
|
||||
@@ -99,7 +303,7 @@ int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
// Ensure we can duplicate the current display
|
||||
syncThreadDesktop();
|
||||
|
||||
delay = std::chrono::nanoseconds { 1s } / framerate;
|
||||
delay = std::chrono::nanoseconds { 1s } / config.framerate;
|
||||
|
||||
// Get rectangle of full desktop for absolute mouse coordinates
|
||||
env_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||
@@ -107,6 +311,11 @@ int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
|
||||
HRESULT status;
|
||||
|
||||
// We must set the GPU preference before calling any DXGI APIs!
|
||||
if(!probe_for_gpu_preference(display_name)) {
|
||||
BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv;
|
||||
}
|
||||
|
||||
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
@@ -140,7 +349,7 @@ int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(desc.AttachedToDesktop) {
|
||||
if(desc.AttachedToDesktop && test_dxgi_duplication(adapter_tmp, output_tmp)) {
|
||||
output = std::move(output_tmp);
|
||||
|
||||
offset_x = desc.DesktopCoordinates.left;
|
||||
@@ -219,7 +428,7 @@ int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
<< "Virtual Desktop : "sv << env_width << 'x' << env_height;
|
||||
|
||||
// Enable DwmFlush() only if the current refresh rate can match the client framerate.
|
||||
auto refresh_rate = framerate;
|
||||
auto refresh_rate = config.framerate;
|
||||
DWM_TIMING_INFO timing_info;
|
||||
timing_info.cbSize = sizeof(timing_info);
|
||||
|
||||
@@ -231,7 +440,7 @@ int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
refresh_rate = std::round((double)timing_info.rateRefresh.uiNumerator / (double)timing_info.rateRefresh.uiDenominator);
|
||||
}
|
||||
|
||||
dup.use_dwmflush = config::video.dwmflush && !(framerate > refresh_rate) ? true : false;
|
||||
dup.use_dwmflush = config::video.dwmflush && !(config.framerate > refresh_rate) ? true : false;
|
||||
|
||||
// Bump up thread priority
|
||||
{
|
||||
@@ -300,7 +509,7 @@ int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
status = output->QueryInterface(IID_IDXGIOutput5, (void **)&output5);
|
||||
if(SUCCEEDED(status)) {
|
||||
// Ask the display implementation which formats it supports
|
||||
auto supported_formats = get_supported_sdr_capture_formats();
|
||||
auto supported_formats = config.dynamicRange ? get_supported_hdr_capture_formats() : get_supported_sdr_capture_formats();
|
||||
if(supported_formats.empty()) {
|
||||
BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv;
|
||||
return -1;
|
||||
@@ -354,12 +563,99 @@ int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']';
|
||||
BOOST_LOG(info) << "Desktop format ["sv << dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']';
|
||||
|
||||
dxgi::output6_t output6 {};
|
||||
status = output->QueryInterface(IID_IDXGIOutput6, (void **)&output6);
|
||||
if(SUCCEEDED(status)) {
|
||||
DXGI_OUTPUT_DESC1 desc1;
|
||||
output6->GetDesc1(&desc1);
|
||||
|
||||
BOOST_LOG(info)
|
||||
<< std::endl
|
||||
<< "Colorspace : "sv << colorspace_to_string(desc1.ColorSpace) << std::endl
|
||||
<< "Bits Per Color : "sv << desc1.BitsPerColor << std::endl
|
||||
<< "Red Primary : ["sv << desc1.RedPrimary[0] << ',' << desc1.RedPrimary[1] << ']' << std::endl
|
||||
<< "Green Primary : ["sv << desc1.GreenPrimary[0] << ',' << desc1.GreenPrimary[1] << ']' << std::endl
|
||||
<< "Blue Primary : ["sv << desc1.BluePrimary[0] << ',' << desc1.BluePrimary[1] << ']' << std::endl
|
||||
<< "White Point : ["sv << desc1.WhitePoint[0] << ',' << desc1.WhitePoint[1] << ']' << std::endl
|
||||
<< "Min Luminance : "sv << desc1.MinLuminance << " nits"sv << std::endl
|
||||
<< "Max Luminance : "sv << desc1.MaxLuminance << " nits"sv << std::endl
|
||||
<< "Max Full Luminance : "sv << desc1.MaxFullFrameLuminance << " nits"sv;
|
||||
}
|
||||
|
||||
// Capture format will be determined from the first call to AcquireNextFrame()
|
||||
capture_format = DXGI_FORMAT_UNKNOWN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool display_base_t::is_hdr() {
|
||||
dxgi::output6_t output6 {};
|
||||
|
||||
auto status = output->QueryInterface(IID_IDXGIOutput6, (void **)&output6);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(warning) << "Failed to query IDXGIOutput6 from the output"sv;
|
||||
return false;
|
||||
}
|
||||
|
||||
DXGI_OUTPUT_DESC1 desc1;
|
||||
output6->GetDesc1(&desc1);
|
||||
|
||||
return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
|
||||
}
|
||||
|
||||
bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
dxgi::output6_t output6 {};
|
||||
|
||||
std::memset(&metadata, 0, sizeof(metadata));
|
||||
|
||||
auto status = output->QueryInterface(IID_IDXGIOutput6, (void **)&output6);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(warning) << "Failed to query IDXGIOutput6 from the output"sv;
|
||||
return false;
|
||||
}
|
||||
|
||||
DXGI_OUTPUT_DESC1 desc1;
|
||||
output6->GetDesc1(&desc1);
|
||||
|
||||
// The primaries reported here seem to correspond to scRGB (Rec. 709)
|
||||
// which we then convert to Rec 2020 in our scRGB FP16 -> PQ shader
|
||||
// prior to encoding. It's not clear to me if we're supposed to report
|
||||
// the primaries of the original colorspace or the one we've converted
|
||||
// it to, but let's just report Rec 2020 primaries and D65 white level
|
||||
// to avoid confusing clients by reporting Rec 709 primaries with a
|
||||
// Rec 2020 colorspace. It seems like most clients ignore the primaries
|
||||
// in the metadata anyway (luminance range is most important).
|
||||
desc1.RedPrimary[0] = 0.708f;
|
||||
desc1.RedPrimary[1] = 0.292f;
|
||||
desc1.GreenPrimary[0] = 0.170f;
|
||||
desc1.GreenPrimary[1] = 0.797f;
|
||||
desc1.BluePrimary[0] = 0.131f;
|
||||
desc1.BluePrimary[1] = 0.046f;
|
||||
desc1.WhitePoint[0] = 0.3127f;
|
||||
desc1.WhitePoint[1] = 0.3290f;
|
||||
|
||||
metadata.displayPrimaries[0].x = desc1.RedPrimary[0] * 50000;
|
||||
metadata.displayPrimaries[0].y = desc1.RedPrimary[1] * 50000;
|
||||
metadata.displayPrimaries[1].x = desc1.GreenPrimary[0] * 50000;
|
||||
metadata.displayPrimaries[1].y = desc1.GreenPrimary[1] * 50000;
|
||||
metadata.displayPrimaries[2].x = desc1.BluePrimary[0] * 50000;
|
||||
metadata.displayPrimaries[2].y = desc1.BluePrimary[1] * 50000;
|
||||
|
||||
metadata.whitePoint.x = desc1.WhitePoint[0] * 50000;
|
||||
metadata.whitePoint.y = desc1.WhitePoint[1] * 50000;
|
||||
|
||||
metadata.maxDisplayLuminance = desc1.MaxLuminance;
|
||||
metadata.minDisplayLuminance = desc1.MinLuminance * 10000;
|
||||
|
||||
// These are content-specific metadata parameters that this interface doesn't give us
|
||||
metadata.maxContentLightLevel = 0;
|
||||
metadata.maxFrameAverageLightLevel = 0;
|
||||
|
||||
metadata.maxFullFrameLuminance = desc1.MaxFullFrameLuminance;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *format_str[] = {
|
||||
"DXGI_FORMAT_UNKNOWN",
|
||||
"DXGI_FORMAT_R32G32B32A32_TYPELESS",
|
||||
@@ -489,21 +785,58 @@ const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
|
||||
return format_str[format];
|
||||
}
|
||||
|
||||
const char *display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) {
|
||||
const char *type_str[] = {
|
||||
"DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709",
|
||||
"DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709",
|
||||
"DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709",
|
||||
"DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020",
|
||||
"DXGI_COLOR_SPACE_RESERVED",
|
||||
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601",
|
||||
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709",
|
||||
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P709",
|
||||
"DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P709",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P2020",
|
||||
"DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020",
|
||||
};
|
||||
|
||||
if(type < ARRAYSIZE(type_str)) {
|
||||
return type_str[type];
|
||||
}
|
||||
else {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace platf::dxgi
|
||||
|
||||
namespace platf {
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if(hwdevice_type == mem_type_e::dxgi) {
|
||||
auto disp = std::make_shared<dxgi::display_vram_t>();
|
||||
|
||||
if(!disp->init(framerate, display_name)) {
|
||||
if(!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
else if(hwdevice_type == mem_type_e::system) {
|
||||
auto disp = std::make_shared<dxgi::display_ram_t>();
|
||||
|
||||
if(!disp->init(framerate, display_name)) {
|
||||
if(!disp->init(config, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
@@ -520,10 +853,15 @@ std::vector<std::string> display_names(mem_type_e) {
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
|
||||
// We must set the GPU preference before calling any DXGI APIs!
|
||||
if(!dxgi::probe_for_gpu_preference(config::video.output_name)) {
|
||||
BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv;
|
||||
}
|
||||
|
||||
dxgi::factory1_t factory;
|
||||
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -562,7 +900,10 @@ std::vector<std::string> display_names(mem_type_e) {
|
||||
<< " Resolution : "sv << width << 'x' << height << std::endl
|
||||
<< std::endl;
|
||||
|
||||
display_names.emplace_back(std::move(device_name));
|
||||
// Don't include the display in the list if we can't actually capture it
|
||||
if(desc.AttachedToDesktop && dxgi::test_dxgi_duplication(adapter, output)) {
|
||||
display_names.emplace_back(std::move(device_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,37 +165,6 @@ void blend_cursor(const cursor_t &cursor, img_t &img) {
|
||||
}
|
||||
}
|
||||
|
||||
capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while(img) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
while(next_frame > now) {
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
switch(status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
std::this_thread::sleep_for(1ms);
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']';
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
auto img = (img_t *)img_base;
|
||||
|
||||
@@ -380,11 +349,16 @@ int display_ram_t::dummy_img(platf::img_t *img) {
|
||||
}
|
||||
|
||||
std::vector<DXGI_FORMAT> display_ram_t::get_supported_sdr_capture_formats() {
|
||||
return std::vector { DXGI_FORMAT_B8G8R8A8_UNORM };
|
||||
return { DXGI_FORMAT_B8G8R8A8_UNORM };
|
||||
}
|
||||
|
||||
int display_ram_t::init(int framerate, const std::string &display_name) {
|
||||
if(display_base_t::init(framerate, display_name)) {
|
||||
std::vector<DXGI_FORMAT> display_ram_t::get_supported_hdr_capture_formats() {
|
||||
// HDR is unsupported
|
||||
return {};
|
||||
}
|
||||
|
||||
int display_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if(display_base_t::init(config, display_name)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,9 +89,12 @@ blend_t make_blend(device_t::pointer device, bool enable, bool invert) {
|
||||
|
||||
blob_t convert_UV_vs_hlsl;
|
||||
blob_t convert_UV_ps_hlsl;
|
||||
blob_t convert_UV_PQ_ps_hlsl;
|
||||
blob_t scene_vs_hlsl;
|
||||
blob_t convert_Y_ps_hlsl;
|
||||
blob_t convert_Y_PQ_ps_hlsl;
|
||||
blob_t scene_ps_hlsl;
|
||||
blob_t scene_NW_ps_hlsl;
|
||||
|
||||
struct img_d3d_t : public platf::img_t {
|
||||
std::shared_ptr<platf::display_t> display;
|
||||
@@ -101,16 +104,16 @@ struct img_d3d_t : public platf::img_t {
|
||||
render_target_t capture_rt;
|
||||
keyed_mutex_t capture_mutex;
|
||||
|
||||
// These objects are owned by the hwdevice_t's ID3D11Device
|
||||
texture2d_t encoder_texture;
|
||||
shader_res_t encoder_input_res;
|
||||
keyed_mutex_t encoder_mutex;
|
||||
|
||||
// This is the shared handle used by hwdevice_t to open capture_texture
|
||||
HANDLE encoder_texture_handle = {};
|
||||
|
||||
// Set to true if the image corresponds to a dummy texture used prior to
|
||||
// the first successful capture of a desktop frame
|
||||
bool dummy = false;
|
||||
|
||||
// Unique identifier for this image
|
||||
uint32_t id = 0;
|
||||
|
||||
virtual ~img_d3d_t() override {
|
||||
if(encoder_texture_handle) {
|
||||
CloseHandle(encoder_texture_handle);
|
||||
@@ -300,62 +303,40 @@ blob_t compile_vertex_shader(LPCSTR file) {
|
||||
class hwdevice_t : public platf::hwdevice_t {
|
||||
public:
|
||||
int convert(platf::img_t &img_base) override {
|
||||
auto &img = (img_d3d_t &)img_base;
|
||||
auto back_d3d_img = (img_d3d_t *)back_img.get();
|
||||
auto &img = (img_d3d_t &)img_base;
|
||||
auto &img_ctx = img_ctx_map[img.id];
|
||||
|
||||
// Open the shared capture texture with our ID3D11Device
|
||||
if(share_img(&img_base)) {
|
||||
if(initialize_image_context(img, img_ctx)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Acquire encoder mutex to synchronize with capture code
|
||||
auto status = img.encoder_mutex->AcquireSync(0, INFINITE);
|
||||
auto status = img_ctx.encoder_mutex->AcquireSync(0, INFINITE);
|
||||
if(status != S_OK) {
|
||||
BOOST_LOG(error) << "Failed to acquire encoder mutex [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Even though this image will never have racing updates, we must acquire the
|
||||
// keyed mutex for PSSetShaderResources() to succeed.
|
||||
status = back_d3d_img->encoder_mutex->AcquireSync(0, INFINITE);
|
||||
if(status != S_OK) {
|
||||
img.encoder_mutex->ReleaseSync(0);
|
||||
BOOST_LOG(error) << "Failed to acquire back_d3d_img mutex [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
device_ctx->IASetInputLayout(input_layout.get());
|
||||
|
||||
_init_view_port(this->img.width, this->img.height);
|
||||
device_ctx->OMSetRenderTargets(1, &nv12_Y_rt, nullptr);
|
||||
device_ctx->VSSetShader(scene_vs.get(), nullptr, 0);
|
||||
device_ctx->PSSetShader(convert_Y_ps.get(), nullptr, 0);
|
||||
device_ctx->PSSetShaderResources(0, 1, &back_d3d_img->encoder_input_res);
|
||||
device_ctx->Draw(3, 0);
|
||||
|
||||
device_ctx->RSSetViewports(1, &outY_view);
|
||||
device_ctx->PSSetShaderResources(0, 1, &img.encoder_input_res);
|
||||
device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res);
|
||||
device_ctx->Draw(3, 0);
|
||||
|
||||
// Artifacts start appearing on the rendered image if Sunshine doesn't flush
|
||||
// before rendering on the UV part of the image.
|
||||
device_ctx->Flush();
|
||||
|
||||
_init_view_port(this->img.width / 2, this->img.height / 2);
|
||||
device_ctx->OMSetRenderTargets(1, &nv12_UV_rt, nullptr);
|
||||
device_ctx->VSSetShader(convert_UV_vs.get(), nullptr, 0);
|
||||
device_ctx->PSSetShader(convert_UV_ps.get(), nullptr, 0);
|
||||
device_ctx->PSSetShaderResources(0, 1, &back_d3d_img->encoder_input_res);
|
||||
device_ctx->Draw(3, 0);
|
||||
|
||||
device_ctx->RSSetViewports(1, &outUV_view);
|
||||
device_ctx->PSSetShaderResources(0, 1, &img.encoder_input_res);
|
||||
device_ctx->Draw(3, 0);
|
||||
device_ctx->Flush();
|
||||
|
||||
// Release encoder mutexes to allow capture code to reuse this image
|
||||
back_d3d_img->encoder_mutex->ReleaseSync(0);
|
||||
img.encoder_mutex->ReleaseSync(0);
|
||||
// Release encoder mutex to allow capture code to reuse this image
|
||||
img_ctx.encoder_mutex->ReleaseSync(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -392,17 +373,80 @@ public:
|
||||
this->color_matrix = std::move(color_matrix);
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame) {
|
||||
void init_hwframes(AVHWFramesContext *frames) override {
|
||||
// We may be called with a QSV or D3D11VA context
|
||||
if(frames->device_ctx->type == AV_HWDEVICE_TYPE_D3D11VA) {
|
||||
auto d3d11_frames = (AVD3D11VAFramesContext *)frames->hwctx;
|
||||
|
||||
// The encoder requires textures with D3D11_BIND_RENDER_TARGET set
|
||||
d3d11_frames->BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
d3d11_frames->MiscFlags = 0;
|
||||
}
|
||||
|
||||
// We require a single texture
|
||||
frames->initial_pool_size = 1;
|
||||
}
|
||||
|
||||
int prepare_to_derive_context(int hw_device_type) override {
|
||||
// QuickSync requires our device to be multithread-protected
|
||||
if(hw_device_type == AV_HWDEVICE_TYPE_QSV) {
|
||||
multithread_t mt;
|
||||
|
||||
auto status = device->QueryInterface(IID_ID3D11Multithread, (void **)&mt);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(warning) << "Failed to query ID3D11Multithread interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
mt->SetMultithreadProtected(TRUE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
// Populate this frame with a hardware buffer if one isn't there already
|
||||
if(!frame->buf[0]) {
|
||||
auto err = av_hwframe_get_buffer(hw_frames_ctx, frame, 0);
|
||||
if(err) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Failed to get hwframe buffer: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a frame from a derived context, we'll need to map it to D3D11
|
||||
ID3D11Texture2D *frame_texture;
|
||||
if(frame->format != AV_PIX_FMT_D3D11) {
|
||||
frame_t d3d11_frame { av_frame_alloc() };
|
||||
|
||||
d3d11_frame->format = AV_PIX_FMT_D3D11;
|
||||
|
||||
auto err = av_hwframe_map(d3d11_frame.get(), frame, AV_HWFRAME_MAP_WRITE | AV_HWFRAME_MAP_OVERWRITE);
|
||||
if(err) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Failed to map D3D11 frame: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the texture from the mapped frame
|
||||
frame_texture = (ID3D11Texture2D *)d3d11_frame->data[0];
|
||||
}
|
||||
else {
|
||||
// Otherwise, we can just use the texture inside the original frame
|
||||
frame_texture = (ID3D11Texture2D *)frame->data[0];
|
||||
}
|
||||
|
||||
auto out_width = frame->width;
|
||||
auto out_height = frame->height;
|
||||
|
||||
float in_width = img.display->width;
|
||||
float in_height = img.display->height;
|
||||
float in_width = display->width;
|
||||
float in_height = display->height;
|
||||
|
||||
// // Ensure aspect ratio is maintained
|
||||
// Ensure aspect ratio is maintained
|
||||
auto scalar = std::fminf(out_width / in_width, out_height / in_height);
|
||||
auto out_width_f = in_width * scalar;
|
||||
auto out_height_f = in_height * scalar;
|
||||
@@ -414,27 +458,9 @@ public:
|
||||
outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f };
|
||||
outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f };
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = out_width;
|
||||
t.Height = out_height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = format;
|
||||
t.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
auto status = device->CreateTexture2D(&t, nullptr, &img.encoder_texture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
img.width = out_width;
|
||||
img.height = out_height;
|
||||
img.data = (std::uint8_t *)img.encoder_texture.get();
|
||||
img.row_pitch = out_width * 4;
|
||||
img.pixel_pitch = 4;
|
||||
// The underlying frame pool owns the texture, so we must reference it for ourselves
|
||||
frame_texture->AddRef();
|
||||
hwframe_texture.reset(frame_texture);
|
||||
|
||||
float info_in[16 / sizeof(float)] { 1.0f / (float)out_width_f }; //aligned to 16-byte
|
||||
info_scene = make_buffer(device.get(), info_in);
|
||||
@@ -449,7 +475,7 @@ public:
|
||||
D3D11_RTV_DIMENSION_TEXTURE2D
|
||||
};
|
||||
|
||||
status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_Y_rt);
|
||||
auto status = device->CreateRenderTargetView(hwframe_texture.get(), &nv12_rt_desc, &nv12_Y_rt);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
@@ -457,28 +483,17 @@ public:
|
||||
|
||||
nv12_rt_desc.Format = (format == DXGI_FORMAT_P010) ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM;
|
||||
|
||||
status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_UV_rt);
|
||||
status = device->CreateRenderTargetView(hwframe_texture.get(), &nv12_rt_desc, &nv12_UV_rt);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Need to have something refcounted
|
||||
if(!frame->buf[0]) {
|
||||
frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor));
|
||||
}
|
||||
|
||||
auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data;
|
||||
desc->texture = (ID3D11Texture2D *)img.data;
|
||||
desc->index = 0;
|
||||
|
||||
frame->data[0] = img.data;
|
||||
frame->data[1] = 0;
|
||||
|
||||
frame->linesize[0] = img.row_pitch;
|
||||
|
||||
frame->height = img.height;
|
||||
frame->width = img.width;
|
||||
// Clear the RTVs to ensure the aspect ratio padding is black
|
||||
const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
device_ctx->ClearRenderTargetView(nv12_Y_rt.get(), y_black);
|
||||
const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f };
|
||||
device_ctx->ClearRenderTargetView(nv12_UV_rt.get(), uv_black);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -534,28 +549,39 @@ public:
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
// If the display is in HDR and we're streaming HDR, we'll be converting scRGB to SMPTE 2084 PQ.
|
||||
// NB: We can consume scRGB in SDR with our regular shaders because it behaves like UNORM input.
|
||||
if(format == DXGI_FORMAT_P010 && display->is_hdr()) {
|
||||
status = device->CreatePixelShader(convert_Y_PQ_ps_hlsl->GetBufferPointer(), convert_Y_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreatePixelShader(convert_UV_PQ_ps_hlsl->GetBufferPointer(), convert_UV_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
color_matrix = make_buffer(device.get(), ::video::colors[0]);
|
||||
@@ -573,15 +599,7 @@ public:
|
||||
convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(),
|
||||
&input_layout);
|
||||
|
||||
img.display = std::move(display);
|
||||
|
||||
// Color the background black, so that the padding for keeping the aspect ratio
|
||||
// is black
|
||||
back_img = img.display->alloc_img();
|
||||
if(img.display->dummy_img(back_img.get()) || share_img(back_img.get())) {
|
||||
BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv;
|
||||
return -1;
|
||||
}
|
||||
this->display = std::move(display);
|
||||
|
||||
blend_disable = make_blend(device.get(), false, false);
|
||||
if(!blend_disable) {
|
||||
@@ -615,28 +633,33 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void _init_view_port(float x, float y, float width, float height) {
|
||||
D3D11_VIEWPORT view {
|
||||
x, y,
|
||||
width, height,
|
||||
0.0f, 1.0f
|
||||
};
|
||||
struct encoder_img_ctx_t {
|
||||
// Used to determine if the underlying texture changes.
|
||||
// Not safe for actual use by the encoder!
|
||||
texture2d_t::pointer capture_texture_p;
|
||||
|
||||
device_ctx->RSSetViewports(1, &view);
|
||||
}
|
||||
texture2d_t encoder_texture;
|
||||
shader_res_t encoder_input_res;
|
||||
keyed_mutex_t encoder_mutex;
|
||||
|
||||
void _init_view_port(float width, float height) {
|
||||
_init_view_port(0.0f, 0.0f, width, height);
|
||||
}
|
||||
|
||||
int share_img(platf::img_t *img_base) {
|
||||
auto img = (img_d3d_t *)img_base;
|
||||
void reset() {
|
||||
capture_texture_p = nullptr;
|
||||
encoder_texture.reset();
|
||||
encoder_input_res.reset();
|
||||
encoder_mutex.reset();
|
||||
}
|
||||
};
|
||||
|
||||
int initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) {
|
||||
// If we've already opened the shared texture, we're done
|
||||
if(img->encoder_texture) {
|
||||
if(img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reset this image context in case it was used before with a different texture.
|
||||
// Textures can change when transitioning from a dummy image to a real image.
|
||||
img_ctx.reset();
|
||||
|
||||
device1_t device1;
|
||||
auto status = device->QueryInterface(__uuidof(ID3D11Device1), (void **)&device1);
|
||||
if(FAILED(status)) {
|
||||
@@ -645,26 +668,27 @@ private:
|
||||
}
|
||||
|
||||
// Open a handle to the shared texture
|
||||
status = device1->OpenSharedResource1(img->encoder_texture_handle, __uuidof(ID3D11Texture2D), (void **)&img->encoder_texture);
|
||||
status = device1->OpenSharedResource1(img.encoder_texture_handle, __uuidof(ID3D11Texture2D), (void **)&img_ctx.encoder_texture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to open shared image texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the keyed mutex to synchronize with the capture code
|
||||
status = img->encoder_texture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void **)&img->encoder_mutex);
|
||||
status = img_ctx.encoder_texture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void **)&img_ctx.encoder_mutex);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query IDXGIKeyedMutex [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create the SRV for the encoder texture
|
||||
status = device->CreateShaderResourceView(img->encoder_texture.get(), nullptr, &img->encoder_input_res);
|
||||
status = device->CreateShaderResourceView(img_ctx.encoder_texture.get(), nullptr, &img_ctx.encoder_input_res);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create shader resource view for encoding [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
img_ctx.capture_texture_p = img.capture_texture.get();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -685,16 +709,19 @@ public:
|
||||
render_target_t nv12_UV_rt;
|
||||
|
||||
// The image referenced by hwframe
|
||||
// The resulting image is stored here.
|
||||
img_d3d_t img;
|
||||
texture2d_t hwframe_texture;
|
||||
|
||||
// Clear nv12 render target to black
|
||||
std::shared_ptr<img_t> back_img;
|
||||
// d3d_img_t::id -> encoder_img_ctx_t
|
||||
// These store the encoder textures for each img_t that passes through
|
||||
// convert(). We can't store them in the img_t itself because it is shared
|
||||
// amongst multiple hwdevice_t objects (and therefore multiple ID3D11Devices).
|
||||
std::map<uint32_t, encoder_img_ctx_t> img_ctx_map;
|
||||
|
||||
std::shared_ptr<platf::display_t> display;
|
||||
|
||||
vs_t convert_UV_vs;
|
||||
ps_t convert_UV_ps;
|
||||
ps_t convert_Y_ps;
|
||||
ps_t scene_ps;
|
||||
vs_t scene_vs;
|
||||
|
||||
D3D11_VIEWPORT outY_view;
|
||||
@@ -706,37 +733,6 @@ public:
|
||||
device_ctx_t device_ctx;
|
||||
};
|
||||
|
||||
capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while(img) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
while(next_frame > now) {
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
switch(status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
img = snapshot_cb(img, false);
|
||||
std::this_thread::sleep_for(1ms);
|
||||
break;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img, true);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']';
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
bool set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t<std::uint8_t> &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) {
|
||||
// This cursor image may not be used
|
||||
if(cursor_img.size() == 0) {
|
||||
@@ -969,8 +965,8 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int display_vram_t::init(int framerate, const std::string &display_name) {
|
||||
if(display_base_t::init(framerate, display_name)) {
|
||||
int display_vram_t::init(const ::video::config_t &config, const std::string &display_name) {
|
||||
if(display_base_t::init(config, display_name)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -995,10 +991,32 @@ int display_vram_t::init(int framerate, const std::string &display_name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
if(config.dynamicRange && is_hdr()) {
|
||||
// This shader will normalize scRGB white levels to a user-defined white level
|
||||
status = device->CreatePixelShader(scene_NW_ps_hlsl->GetBufferPointer(), scene_NW_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use a 300 nit target for the mouse cursor. We should really get
|
||||
// the user's SDR white level in nits, but there is no API that
|
||||
// provides that information to Win32 apps.
|
||||
float sdr_multiplier_data[16 / sizeof(float)] { 300.0f / 80.f }; // aligned to 16-byte
|
||||
auto sdr_multiplier = make_buffer(device.get(), sdr_multiplier_data);
|
||||
if(!sdr_multiplier) {
|
||||
BOOST_LOG(warning) << "Failed to create SDR multiplier"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
device_ctx->PSSetConstantBuffers(0, 1, &sdr_multiplier);
|
||||
}
|
||||
else {
|
||||
status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
blend_alpha = make_blend(device.get(), true, false);
|
||||
@@ -1023,6 +1041,7 @@ std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->display = shared_from_this();
|
||||
img->id = next_image_id++;
|
||||
|
||||
return img;
|
||||
}
|
||||
@@ -1046,9 +1065,6 @@ int display_vram_t::complete_img(platf::img_t *img_base, bool dummy) {
|
||||
img->capture_texture.reset();
|
||||
img->capture_rt.reset();
|
||||
img->capture_mutex.reset();
|
||||
img->encoder_texture.reset();
|
||||
img->encoder_input_res.reset();
|
||||
img->encoder_mutex.reset();
|
||||
img->data = nullptr;
|
||||
if(img->encoder_texture_handle) {
|
||||
CloseHandle(img->encoder_texture_handle);
|
||||
@@ -1123,7 +1139,29 @@ int display_vram_t::dummy_img(platf::img_t *img_base) {
|
||||
}
|
||||
|
||||
std::vector<DXGI_FORMAT> display_vram_t::get_supported_sdr_capture_formats() {
|
||||
return std::vector { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM };
|
||||
return { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM };
|
||||
}
|
||||
|
||||
std::vector<DXGI_FORMAT> display_vram_t::get_supported_hdr_capture_formats() {
|
||||
return {
|
||||
// scRGB FP16 is the desired format for HDR content. This will also handle
|
||||
// 10-bit SDR displays with the increased precision of FP16 vs 8-bit UNORMs.
|
||||
DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
|
||||
// DXGI_FORMAT_R10G10B10A2_UNORM seems like it might give us frames already
|
||||
// converted to SMPTE 2084 PQ, however it seems to actually just clamp the
|
||||
// scRGB FP16 values that DWM is using when the desktop format is scRGB FP16.
|
||||
//
|
||||
// If there is a case where the desktop format is really SMPTE 2084 PQ, it
|
||||
// might make sense to support capturing it without conversion to scRGB,
|
||||
// but we avoid it for now.
|
||||
|
||||
// We include the 8-bit modes too for when the display is in SDR mode,
|
||||
// while the client stream is HDR-capable. These UNORM formats behave
|
||||
// like a degenerate case of scRGB FP16 with values between 0.0f-1.0f.
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
};
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::hwdevice_t> display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) {
|
||||
@@ -1159,11 +1197,21 @@ int init() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
convert_Y_PQ_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS_PQ.hlsl");
|
||||
if(!convert_Y_PQ_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl");
|
||||
if(!convert_UV_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
convert_UV_PQ_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS_PQ.hlsl");
|
||||
if(!convert_UV_PQ_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl");
|
||||
if(!convert_UV_vs_hlsl) {
|
||||
return -1;
|
||||
@@ -1173,6 +1221,11 @@ int init() {
|
||||
if(!scene_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
scene_NW_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS_NW.hlsl");
|
||||
if(!scene_NW_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
BOOST_LOG(info) << "Compiled shaders"sv;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -303,6 +303,18 @@ void scroll(input_t &input, int distance) {
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void hscroll(input_t &input, int distance) {
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
mi.dwFlags = MOUSEEVENTF_HWHEEL;
|
||||
mi.mouseData = distance;
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void keyboard(input_t &input, uint16_t modcode, bool release) {
|
||||
auto raw = (input_raw_t *)input.get();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/process.hpp>
|
||||
|
||||
// prevent clang format from "optimizing" the header include order
|
||||
@@ -16,12 +17,27 @@
|
||||
#include <userenv.h>
|
||||
#include <dwmapi.h>
|
||||
#include <timeapi.h>
|
||||
#include <wlanapi.h>
|
||||
// clang-format on
|
||||
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
// UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK
|
||||
#ifndef UDP_SEND_MSG_SIZE
|
||||
#define UDP_SEND_MSG_SIZE 2
|
||||
#endif
|
||||
|
||||
// MinGW headers are missing qWAVE stuff
|
||||
typedef UINT32 QOS_FLOWID, *PQOS_FLOWID;
|
||||
#define QOS_NON_ADAPTIVE_FLOW 0x00000002
|
||||
#include <qos2.h>
|
||||
|
||||
#ifndef WLAN_API_MAKE_VERSION
|
||||
#define WLAN_API_MAKE_VERSION(_major, _minor) (((DWORD)(_minor)) << 16 | (_major))
|
||||
#endif
|
||||
|
||||
namespace bp = boost::process;
|
||||
|
||||
using namespace std::literals;
|
||||
@@ -31,6 +47,20 @@ using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
||||
bool enabled_mouse_keys = false;
|
||||
MOUSEKEYS previous_mouse_keys_state;
|
||||
|
||||
HANDLE qos_handle = nullptr;
|
||||
|
||||
decltype(QOSCreateHandle) *fn_QOSCreateHandle = nullptr;
|
||||
decltype(QOSAddSocketToFlow) *fn_QOSAddSocketToFlow = nullptr;
|
||||
decltype(QOSRemoveSocketFromFlow) *fn_QOSRemoveSocketFromFlow = nullptr;
|
||||
|
||||
HANDLE wlan_handle = nullptr;
|
||||
|
||||
decltype(WlanOpenHandle) *fn_WlanOpenHandle = nullptr;
|
||||
decltype(WlanCloseHandle) *fn_WlanCloseHandle = nullptr;
|
||||
decltype(WlanFreeMemory) *fn_WlanFreeMemory = nullptr;
|
||||
decltype(WlanEnumInterfaces) *fn_WlanEnumInterfaces = nullptr;
|
||||
decltype(WlanSetInterface) *fn_WlanSetInterface = nullptr;
|
||||
|
||||
std::filesystem::path appdata() {
|
||||
WCHAR sunshine_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, sunshine_path, _countof(sunshine_path));
|
||||
@@ -345,7 +375,7 @@ void free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) {
|
||||
HeapFree(GetProcessHeap(), 0, list);
|
||||
}
|
||||
|
||||
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) {
|
||||
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
|
||||
HANDLE shell_token = duplicate_shell_token();
|
||||
if(!shell_token) {
|
||||
// This can happen if the shell has crashed. Fail the launch rather than risking launching with
|
||||
@@ -460,6 +490,9 @@ bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &work
|
||||
// Since we are always spawning a process with a less privileged token than ourselves,
|
||||
// bp::child() should have no problem opening it with any access rights it wants.
|
||||
auto child = bp::child((bp::pid_t)process_info.dwProcessId);
|
||||
if(group) {
|
||||
group->add(child);
|
||||
}
|
||||
|
||||
// Only close handles after bp::child() has opened the process. If the process terminates
|
||||
// quickly, the PID could be reused if we close the process handle.
|
||||
@@ -507,6 +540,35 @@ void adjust_thread_priority(thread_priority_e priority) {
|
||||
}
|
||||
|
||||
void streaming_will_start() {
|
||||
static std::once_flag load_wlanapi_once_flag;
|
||||
std::call_once(load_wlanapi_once_flag, []() {
|
||||
// wlanapi.dll is not installed by default on Windows Server, so we load it dynamically
|
||||
HMODULE wlanapi = LoadLibraryExA("wlanapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if(!wlanapi) {
|
||||
BOOST_LOG(debug) << "wlanapi.dll is not available on this OS"sv;
|
||||
return;
|
||||
}
|
||||
|
||||
fn_WlanOpenHandle = (decltype(fn_WlanOpenHandle))GetProcAddress(wlanapi, "WlanOpenHandle");
|
||||
fn_WlanCloseHandle = (decltype(fn_WlanCloseHandle))GetProcAddress(wlanapi, "WlanCloseHandle");
|
||||
fn_WlanFreeMemory = (decltype(fn_WlanFreeMemory))GetProcAddress(wlanapi, "WlanFreeMemory");
|
||||
fn_WlanEnumInterfaces = (decltype(fn_WlanEnumInterfaces))GetProcAddress(wlanapi, "WlanEnumInterfaces");
|
||||
fn_WlanSetInterface = (decltype(fn_WlanSetInterface))GetProcAddress(wlanapi, "WlanSetInterface");
|
||||
|
||||
if(!fn_WlanOpenHandle || !fn_WlanCloseHandle || !fn_WlanFreeMemory || !fn_WlanEnumInterfaces || !fn_WlanSetInterface) {
|
||||
BOOST_LOG(error) << "wlanapi.dll is missing exports?"sv;
|
||||
|
||||
fn_WlanOpenHandle = nullptr;
|
||||
fn_WlanCloseHandle = nullptr;
|
||||
fn_WlanFreeMemory = nullptr;
|
||||
fn_WlanEnumInterfaces = nullptr;
|
||||
fn_WlanSetInterface = nullptr;
|
||||
|
||||
FreeLibrary(wlanapi);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Enable MMCSS scheduling for DWM
|
||||
DwmEnableMMCSS(true);
|
||||
|
||||
@@ -516,6 +578,39 @@ void streaming_will_start() {
|
||||
// Promote ourselves to high priority class
|
||||
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
|
||||
|
||||
// Enable low latency mode on all connected WLAN NICs if wlanapi.dll is available
|
||||
if(fn_WlanOpenHandle) {
|
||||
DWORD negotiated_version;
|
||||
|
||||
if(fn_WlanOpenHandle(WLAN_API_MAKE_VERSION(2, 0), nullptr, &negotiated_version, &wlan_handle) == ERROR_SUCCESS) {
|
||||
PWLAN_INTERFACE_INFO_LIST wlan_interface_list;
|
||||
|
||||
if(fn_WlanEnumInterfaces(wlan_handle, nullptr, &wlan_interface_list) == ERROR_SUCCESS) {
|
||||
for(DWORD i = 0; i < wlan_interface_list->dwNumberOfItems; i++) {
|
||||
if(wlan_interface_list->InterfaceInfo[i].isState == wlan_interface_state_connected) {
|
||||
// Enable media streaming mode for 802.11 wireless interfaces to reduce latency and
|
||||
// unneccessary background scanning operations that cause packet loss and jitter.
|
||||
//
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/network/oid-wdi-set-connection-quality
|
||||
// https://docs.microsoft.com/en-us/previous-versions/windows/hardware/wireless/native-802-11-media-streaming
|
||||
BOOL value = TRUE;
|
||||
auto error = fn_WlanSetInterface(wlan_handle, &wlan_interface_list->InterfaceInfo[i].InterfaceGuid,
|
||||
wlan_intf_opcode_media_streaming_mode, sizeof(value), &value, nullptr);
|
||||
if(error == ERROR_SUCCESS) {
|
||||
BOOST_LOG(info) << "WLAN interface "sv << i << " is now in low latency mode"sv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn_WlanFreeMemory(wlan_interface_list);
|
||||
}
|
||||
else {
|
||||
fn_WlanCloseHandle(wlan_handle, nullptr);
|
||||
wlan_handle = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no mouse connected, enable Mouse Keys to force the cursor to appear
|
||||
if(!GetSystemMetrics(SM_MOUSEPRESENT)) {
|
||||
BOOST_LOG(info) << "A mouse was not detected. Sunshine will enable Mouse Keys while streaming to force the mouse cursor to appear.";
|
||||
@@ -556,6 +651,12 @@ void streaming_will_stop() {
|
||||
// Disable MMCSS scheduling for DWM
|
||||
DwmEnableMMCSS(false);
|
||||
|
||||
// Closing our WLAN client handle will undo our optimizations
|
||||
if(wlan_handle != nullptr) {
|
||||
fn_WlanCloseHandle(wlan_handle, nullptr);
|
||||
wlan_handle = nullptr;
|
||||
}
|
||||
|
||||
// Restore Mouse Keys back to the previous settings if we turned it on
|
||||
if(enabled_mouse_keys) {
|
||||
enabled_mouse_keys = false;
|
||||
@@ -578,4 +679,166 @@ bool restart() {
|
||||
return true;
|
||||
}
|
||||
|
||||
SOCKADDR_IN to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
SOCKADDR_IN saddr_v4 = {};
|
||||
|
||||
saddr_v4.sin_family = AF_INET;
|
||||
saddr_v4.sin_port = htons(port);
|
||||
|
||||
auto addr_bytes = address.to_bytes();
|
||||
memcpy(&saddr_v4.sin_addr, addr_bytes.data(), sizeof(saddr_v4.sin_addr));
|
||||
|
||||
return saddr_v4;
|
||||
}
|
||||
|
||||
SOCKADDR_IN6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
SOCKADDR_IN6 saddr_v6 = {};
|
||||
|
||||
saddr_v6.sin6_family = AF_INET6;
|
||||
saddr_v6.sin6_port = htons(port);
|
||||
saddr_v6.sin6_scope_id = address.scope_id();
|
||||
|
||||
auto addr_bytes = address.to_bytes();
|
||||
memcpy(&saddr_v6.sin6_addr, addr_bytes.data(), sizeof(saddr_v6.sin6_addr));
|
||||
|
||||
return saddr_v6;
|
||||
}
|
||||
|
||||
// Use UDP segmentation offload if it is supported by the OS. If the NIC is capable, this will use
|
||||
// hardware acceleration to reduce CPU usage. Support for USO was introduced in Windows 10 20H1.
|
||||
bool send_batch(batched_send_info_t &send_info) {
|
||||
WSAMSG msg;
|
||||
|
||||
// Convert the target address into a SOCKADDR
|
||||
SOCKADDR_IN saddr_v4;
|
||||
SOCKADDR_IN6 saddr_v6;
|
||||
if(send_info.target_address.is_v6()) {
|
||||
saddr_v6 = to_sockaddr(send_info.target_address.to_v6(), send_info.target_port);
|
||||
|
||||
msg.name = (PSOCKADDR)&saddr_v6;
|
||||
msg.namelen = sizeof(saddr_v6);
|
||||
}
|
||||
else {
|
||||
saddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port);
|
||||
|
||||
msg.name = (PSOCKADDR)&saddr_v4;
|
||||
msg.namelen = sizeof(saddr_v4);
|
||||
}
|
||||
|
||||
WSABUF buf;
|
||||
buf.buf = (char *)send_info.buffer;
|
||||
buf.len = send_info.block_size * send_info.block_count;
|
||||
|
||||
msg.lpBuffers = &buf;
|
||||
msg.dwBufferCount = 1;
|
||||
msg.dwFlags = 0;
|
||||
|
||||
char cmbuf[WSA_CMSG_SPACE(sizeof(DWORD))];
|
||||
msg.Control.buf = cmbuf;
|
||||
msg.Control.len = 0;
|
||||
|
||||
if(send_info.block_count > 1) {
|
||||
msg.Control.len += WSA_CMSG_SPACE(sizeof(DWORD));
|
||||
|
||||
auto cm = WSA_CMSG_FIRSTHDR(&msg);
|
||||
cm->cmsg_level = IPPROTO_UDP;
|
||||
cm->cmsg_type = UDP_SEND_MSG_SIZE;
|
||||
cm->cmsg_len = WSA_CMSG_LEN(sizeof(DWORD));
|
||||
*((DWORD *)WSA_CMSG_DATA(cm)) = send_info.block_size;
|
||||
}
|
||||
|
||||
// If USO is not supported, this will fail and the caller will fall back to unbatched sends.
|
||||
DWORD bytes_sent;
|
||||
return WSASendMsg((SOCKET)send_info.native_socket, &msg, 1, &bytes_sent, nullptr, nullptr) != SOCKET_ERROR;
|
||||
}
|
||||
|
||||
class qos_t : public deinit_t {
|
||||
public:
|
||||
qos_t(QOS_FLOWID flow_id) : flow_id(flow_id) {}
|
||||
|
||||
virtual ~qos_t() {
|
||||
if(!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET)NULL, flow_id, 0)) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(warning) << "QOSRemoveSocketFromFlow() failed: "sv << winerr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QOS_FLOWID flow_id;
|
||||
};
|
||||
|
||||
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) {
|
||||
SOCKADDR_IN saddr_v4;
|
||||
SOCKADDR_IN6 saddr_v6;
|
||||
PSOCKADDR dest_addr;
|
||||
|
||||
static std::once_flag load_qwave_once_flag;
|
||||
std::call_once(load_qwave_once_flag, []() {
|
||||
// qWAVE is not installed by default on Windows Server, so we load it dynamically
|
||||
HMODULE qwave = LoadLibraryExA("qwave.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if(!qwave) {
|
||||
BOOST_LOG(debug) << "qwave.dll is not available on this OS"sv;
|
||||
return;
|
||||
}
|
||||
|
||||
fn_QOSCreateHandle = (decltype(fn_QOSCreateHandle))GetProcAddress(qwave, "QOSCreateHandle");
|
||||
fn_QOSAddSocketToFlow = (decltype(fn_QOSAddSocketToFlow))GetProcAddress(qwave, "QOSAddSocketToFlow");
|
||||
fn_QOSRemoveSocketFromFlow = (decltype(fn_QOSRemoveSocketFromFlow))GetProcAddress(qwave, "QOSRemoveSocketFromFlow");
|
||||
|
||||
if(!fn_QOSCreateHandle || !fn_QOSAddSocketToFlow || !fn_QOSRemoveSocketFromFlow) {
|
||||
BOOST_LOG(error) << "qwave.dll is missing exports?"sv;
|
||||
|
||||
fn_QOSCreateHandle = nullptr;
|
||||
fn_QOSAddSocketToFlow = nullptr;
|
||||
fn_QOSRemoveSocketFromFlow = nullptr;
|
||||
|
||||
FreeLibrary(qwave);
|
||||
return;
|
||||
}
|
||||
|
||||
QOS_VERSION qos_version { 1, 0 };
|
||||
if(!fn_QOSCreateHandle(&qos_version, &qos_handle)) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(warning) << "QOSCreateHandle() failed: "sv << winerr;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// If qWAVE is unavailable, just return
|
||||
if(!fn_QOSAddSocketToFlow || !qos_handle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(address.is_v6()) {
|
||||
saddr_v6 = to_sockaddr(address.to_v6(), port);
|
||||
dest_addr = (PSOCKADDR)&saddr_v6;
|
||||
}
|
||||
else {
|
||||
saddr_v4 = to_sockaddr(address.to_v4(), port);
|
||||
dest_addr = (PSOCKADDR)&saddr_v4;
|
||||
}
|
||||
|
||||
QOS_TRAFFIC_TYPE traffic_type;
|
||||
switch(data_type) {
|
||||
case qos_data_type_e::audio:
|
||||
traffic_type = QOSTrafficTypeVoice;
|
||||
break;
|
||||
case qos_data_type_e::video:
|
||||
traffic_type = QOSTrafficTypeAudioVideo;
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unknown traffic type: "sv << (int)data_type;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QOS_FLOWID flow_id = 0;
|
||||
if(!fn_QOSAddSocketToFlow(qos_handle, (SOCKET)native_socket, dest_addr, traffic_type, QOS_NON_ADAPTIVE_FLOW, &flow_id)) {
|
||||
auto winerr = GetLastError();
|
||||
BOOST_LOG(warning) << "QOSAddSocketToFlow() failed: "sv << winerr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<qos_t>(flow_id);
|
||||
}
|
||||
|
||||
} // namespace platf
|
||||
@@ -150,7 +150,7 @@ int proc_t::execute(int app_id) {
|
||||
find_working_directory(cmd, _env) :
|
||||
boost::filesystem::path(proc.working_dir);
|
||||
BOOST_LOG(info) << "Spawning ["sv << cmd << "] in ["sv << working_dir << ']';
|
||||
auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec);
|
||||
auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec, nullptr);
|
||||
if(ec) {
|
||||
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
|
||||
}
|
||||
@@ -168,13 +168,11 @@ int proc_t::execute(int app_id) {
|
||||
find_working_directory(proc.cmd, _env) :
|
||||
boost::filesystem::path(proc.working_dir);
|
||||
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << "] in ["sv << working_dir << ']';
|
||||
_process = platf::run_unprivileged(proc.cmd, working_dir, _env, _pipe.get(), ec);
|
||||
_process = platf::run_unprivileged(proc.cmd, working_dir, _env, _pipe.get(), ec, &_process_handle);
|
||||
if(ec) {
|
||||
BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message();
|
||||
return -1;
|
||||
}
|
||||
|
||||
_process_handle.add(_process);
|
||||
}
|
||||
|
||||
fg.disable();
|
||||
|
||||
@@ -613,6 +613,8 @@ void cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
|
||||
args.try_emplace("x-nv-general.useReliableUdp"sv, "1"sv);
|
||||
args.try_emplace("x-nv-vqos[0].fec.minRequiredFecPackets"sv, "0"sv);
|
||||
args.try_emplace("x-nv-general.featureFlags"sv, "135"sv);
|
||||
args.try_emplace("x-nv-vqos[0].qosTrafficType"sv, "5"sv);
|
||||
args.try_emplace("x-nv-aqos.qosTrafficType"sv, "4"sv);
|
||||
|
||||
config_t config;
|
||||
|
||||
@@ -629,6 +631,8 @@ void cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) {
|
||||
config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));
|
||||
config.minRequiredFecPackets = util::from_view(args.at("x-nv-vqos[0].fec.minRequiredFecPackets"sv));
|
||||
config.featureFlags = util::from_view(args.at("x-nv-general.featureFlags"sv));
|
||||
config.audioQosType = util::from_view(args.at("x-nv-aqos.qosTrafficType"sv));
|
||||
config.videoQosType = util::from_view(args.at("x-nv-vqos[0].qosTrafficType"sv));
|
||||
|
||||
config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv));
|
||||
config.monitor.width = util::from_view(args.at("x-nv-video[0].clientViewportWd"sv));
|
||||
|
||||
@@ -33,6 +33,7 @@ extern "C" {
|
||||
#define IDX_PERIODIC_PING 8
|
||||
#define IDX_REQUEST_IDR_FRAME 9
|
||||
#define IDX_ENCRYPTED 10
|
||||
#define IDX_HDR_MODE 11
|
||||
|
||||
static const short packetTypes[] = {
|
||||
0x0305, // Start A
|
||||
@@ -46,6 +47,7 @@ static const short packetTypes[] = {
|
||||
0x0200, // Periodic Ping
|
||||
0x0302, // IDR frame
|
||||
0x0001, // fully encrypted
|
||||
0x010e, // HDR mode
|
||||
};
|
||||
|
||||
namespace asio = boost::asio;
|
||||
@@ -131,6 +133,15 @@ struct control_rumble_t {
|
||||
std::uint16_t highfreq;
|
||||
};
|
||||
|
||||
struct control_hdr_mode_t {
|
||||
control_header_v2 header;
|
||||
|
||||
std::uint8_t enabled;
|
||||
|
||||
// Sunshine protocol extension
|
||||
SS_HDR_METADATA metadata;
|
||||
};
|
||||
|
||||
typedef struct control_encrypted_t {
|
||||
std::uint16_t encryptedHeaderType; // Always LE 0x0001
|
||||
std::uint16_t length; // sizeof(seq) + 16 byte tag + secondary header and data
|
||||
@@ -287,6 +298,7 @@ struct session_t {
|
||||
int lowseq;
|
||||
udp::endpoint peer;
|
||||
safe::mail_raw_t::event_t<bool> idr_events;
|
||||
std::unique_ptr<platf::deinit_t> qos;
|
||||
} video;
|
||||
|
||||
struct {
|
||||
@@ -302,6 +314,7 @@ struct session_t {
|
||||
util::buffer_t<uint8_t *> shards_p;
|
||||
|
||||
audio_fec_packet_t fec_packet;
|
||||
std::unique_ptr<platf::deinit_t> qos;
|
||||
} audio;
|
||||
|
||||
struct {
|
||||
@@ -312,6 +325,7 @@ struct session_t {
|
||||
std::uint8_t seq;
|
||||
|
||||
platf::rumble_queue_t rumble_queue;
|
||||
safe::mail_raw_t::event_t<video::hdr_info_t> hdr_queue;
|
||||
} control;
|
||||
|
||||
safe::mail_raw_t::event_t<bool> shutdown_event;
|
||||
@@ -397,13 +411,12 @@ session_t *control_server_t::get_session(const net::peer_t peer) {
|
||||
void control_server_t::call(std::uint16_t type, session_t *session, const std::string_view &payload) {
|
||||
auto cb = _map_type_cb.find(type);
|
||||
if(cb == std::end(_map_type_cb)) {
|
||||
BOOST_LOG(warning)
|
||||
BOOST_LOG(debug)
|
||||
<< "type [Unknown] { "sv << util::hex(type).to_string_view() << " }"sv << std::endl
|
||||
<< "---data---"sv << std::endl
|
||||
<< util::hex_vec(payload) << std::endl
|
||||
<< "---end data---"sv;
|
||||
}
|
||||
|
||||
else {
|
||||
cb->second(session, payload);
|
||||
}
|
||||
@@ -606,6 +619,36 @@ int send_rumble(session_t *session, std::uint16_t id, std::uint16_t lowfreq, std
|
||||
return 0;
|
||||
}
|
||||
|
||||
int send_hdr_mode(session_t *session, video::hdr_info_t hdr_info) {
|
||||
if(!session->control.peer) {
|
||||
BOOST_LOG(warning) << "Couldn't send HDR mode, still waiting for PING from Moonlight"sv;
|
||||
// Still waiting for PING from Moonlight
|
||||
return -1;
|
||||
}
|
||||
|
||||
control_hdr_mode_t plaintext {};
|
||||
plaintext.header.type = packetTypes[IDX_HDR_MODE];
|
||||
plaintext.header.payloadLength = sizeof(control_hdr_mode_t) - sizeof(control_header_v2);
|
||||
|
||||
plaintext.enabled = hdr_info->enabled;
|
||||
plaintext.metadata = hdr_info->metadata;
|
||||
|
||||
std::array<std::uint8_t,
|
||||
sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
|
||||
encrypted_payload;
|
||||
|
||||
auto payload = encode_control(session, util::view(plaintext), encrypted_payload);
|
||||
if(session->broadcast_ref->control_server.send(payload, session->control.peer)) {
|
||||
TUPLE_2D(port, addr, platf::from_sockaddr_ex((sockaddr *)&session->control.peer->address.address));
|
||||
BOOST_LOG(warning) << "Couldn't send HDR mode to ["sv << addr << ':' << port << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Sent HDR mode: " << hdr_info->enabled;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void controlBroadcastThread(control_server_t *server) {
|
||||
server->map(packetTypes[IDX_PERIODIC_PING], [](session_t *session, const std::string_view &payload) {
|
||||
BOOST_LOG(verbose) << "type [IDX_START_A]"sv;
|
||||
@@ -762,7 +805,10 @@ void controlBroadcastThread(control_server_t *server) {
|
||||
if(session->state.load(std::memory_order_acquire) == session::state_e::STOPPING) {
|
||||
pos = server->_map_addr_session->erase(pos);
|
||||
|
||||
enet_peer_disconnect_now(session->control.peer, 0);
|
||||
if(session->control.peer) {
|
||||
enet_peer_disconnect_now(session->control.peer, 0);
|
||||
}
|
||||
|
||||
session->controlEnd.raise(true);
|
||||
continue;
|
||||
}
|
||||
@@ -774,6 +820,16 @@ void controlBroadcastThread(control_server_t *server) {
|
||||
send_rumble(session, rumble->id, rumble->lowfreq, rumble->highfreq);
|
||||
}
|
||||
|
||||
// Unlike rumble which we send as best-effort, HDR state messages are critical
|
||||
// for proper functioning of some clients. We must wait to pop entries from
|
||||
// the queue until we're sure we have a peer to send them to.
|
||||
auto &hdr_queue = session->control.hdr_queue;
|
||||
while(session->control.peer && hdr_queue->peek()) {
|
||||
auto hdr_info = hdr_queue->pop();
|
||||
|
||||
send_hdr_mode(session, std::move(hdr_info));
|
||||
}
|
||||
|
||||
++pos;
|
||||
})
|
||||
}
|
||||
@@ -1036,8 +1092,25 @@ void videoBroadcastThread(udp::socket &sock) {
|
||||
|
||||
inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex;
|
||||
inspect->packet.frameIndex = av_packet->pts;
|
||||
}
|
||||
|
||||
sock.send_to(asio::buffer(shards[x]), session->video.peer);
|
||||
auto peer_address = session->video.peer.address();
|
||||
auto batch_info = platf::batched_send_info_t {
|
||||
shards.shards.begin(),
|
||||
shards.blocksize,
|
||||
shards.nr_shards,
|
||||
(uintptr_t)sock.native_handle(),
|
||||
peer_address,
|
||||
session->video.peer.port(),
|
||||
};
|
||||
|
||||
// Use a batched send if it's supported on this platform
|
||||
if(!platf::send_batch(batch_info)) {
|
||||
// Batched send is not available, so send each packet individually
|
||||
BOOST_LOG(verbose) << "Falling back to unbatched send"sv;
|
||||
for(auto x = 0; x < shards.size(); ++x) {
|
||||
sock.send_to(asio::buffer(shards[x]), session->video.peer);
|
||||
}
|
||||
}
|
||||
|
||||
if(av_packet->flags & AV_PKT_FLAG_KEY) {
|
||||
@@ -1077,8 +1150,7 @@ void audioBroadcastThread(udp::socket &sock) {
|
||||
// works correctly. This is possible because the data and FEC shard count is
|
||||
// constant and known in advance.
|
||||
const unsigned char parity[] = { 0x77, 0x40, 0x38, 0x0e, 0xc7, 0xa7, 0x0d, 0x6c };
|
||||
memcpy(&rs.get()->m[16], parity, sizeof(parity));
|
||||
memcpy(rs.get()->parity, parity, sizeof(parity));
|
||||
memcpy(rs.get()->p, parity, sizeof(parity));
|
||||
|
||||
audio_packet->rtp.header = 0x80;
|
||||
audio_packet->rtp.packetType = 97;
|
||||
@@ -1319,6 +1391,13 @@ void videoThread(session_t *session) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable QoS tagging on video traffic if requested by the client
|
||||
if(session->config.videoQosType) {
|
||||
auto address = session->video.peer.address();
|
||||
session->video.qos = std::move(platf::enable_socket_qos(ref->video_sock.native_handle(), address,
|
||||
session->video.peer.port(), platf::qos_data_type_e::video));
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Start capturing Video"sv;
|
||||
video::capture(session->mail, session->config.monitor, session);
|
||||
}
|
||||
@@ -1336,6 +1415,13 @@ void audioThread(session_t *session) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable QoS tagging on audio traffic if requested by the client
|
||||
if(session->config.audioQosType) {
|
||||
auto address = session->audio.peer.address();
|
||||
session->audio.qos = std::move(platf::enable_socket_qos(ref->audio_sock.native_handle(), address,
|
||||
session->audio.peer.port(), platf::qos_data_type_e::audio));
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Start capturing Audio"sv;
|
||||
audio::capture(session->mail, session->config.audio, session);
|
||||
}
|
||||
@@ -1479,6 +1565,7 @@ std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypt
|
||||
session->config = config;
|
||||
|
||||
session->control.rumble_queue = mail->queue<platf::rumble_t>(mail::rumble);
|
||||
session->control.hdr_queue = mail->event<video::hdr_info_t>(mail::hdr);
|
||||
session->control.iv = iv;
|
||||
session->control.cipher = crypto::cipher::gcm_t {
|
||||
gcm_key, false
|
||||
|
||||
@@ -23,6 +23,8 @@ struct config_t {
|
||||
int minRequiredFecPackets;
|
||||
int featureFlags;
|
||||
int controlProtocolType;
|
||||
int audioQosType;
|
||||
int videoQosType;
|
||||
|
||||
std::optional<int> gcmap;
|
||||
};
|
||||
|
||||
@@ -91,6 +91,10 @@ static std::string_view status_string(int status) {
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::deinit_t> start() {
|
||||
if(!config::sunshine.flags[config::flag::UPNP]) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int err {};
|
||||
|
||||
device_t device { upnpDiscover(2000, nullptr, nullptr, 0, IPv4, 2, &err) };
|
||||
@@ -128,10 +132,6 @@ std::unique_ptr<platf::deinit_t> start() {
|
||||
}
|
||||
}
|
||||
|
||||
if(!config::sunshine.flags[config::flag::UPNP]) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto rtsp = std::to_string(map_port(stream::RTSP_SETUP_PORT));
|
||||
auto video = std::to_string(map_port(stream::VIDEO_STREAM_PORT));
|
||||
auto audio = std::to_string(map_port(stream::AUDIO_STREAM_PORT));
|
||||
|
||||
394
src/video.cpp
394
src/video.cpp
@@ -5,6 +5,7 @@
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/mastering_display_metadata.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
@@ -63,15 +64,29 @@ enum class profile_hevc_e : int {
|
||||
};
|
||||
} // namespace nv
|
||||
|
||||
namespace qsv {
|
||||
|
||||
platf::mem_type_e map_dev_type(AVHWDeviceType type);
|
||||
enum class profile_h264_e : int {
|
||||
baseline = 66,
|
||||
main = 77,
|
||||
high = 100,
|
||||
};
|
||||
|
||||
enum class profile_hevc_e : int {
|
||||
main = 1,
|
||||
main_10 = 2,
|
||||
};
|
||||
} // namespace qsv
|
||||
|
||||
|
||||
platf::mem_type_e map_base_dev_type(AVHWDeviceType type);
|
||||
platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt);
|
||||
|
||||
util::Either<buffer_t, int> dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
||||
util::Either<buffer_t, int> vaapi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
||||
util::Either<buffer_t, int> cuda_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
||||
|
||||
int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format);
|
||||
int hwframe_ctx(ctx_t &ctx, platf::hwdevice_t *hwdevice, buffer_t &hwdevice_ctx, AVPixelFormat format);
|
||||
|
||||
class swdevice_t : public platf::hwdevice_t {
|
||||
public:
|
||||
@@ -116,17 +131,16 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame) {
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
|
||||
this->frame = frame;
|
||||
|
||||
// If it's a hwframe, allocate buffers for hardware
|
||||
if(frame->hw_frames_ctx) {
|
||||
if(hw_frames_ctx) {
|
||||
hw_frame.reset(frame);
|
||||
|
||||
if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) return -1;
|
||||
if(av_hwframe_get_buffer(hw_frames_ctx, frame, 0)) return -1;
|
||||
}
|
||||
|
||||
if(!frame->hw_frames_ctx) {
|
||||
else {
|
||||
sw_frame.reset(frame);
|
||||
}
|
||||
|
||||
@@ -181,9 +195,9 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init(int in_width, int in_height, AVFrame *frame, AVPixelFormat format) {
|
||||
int init(int in_width, int in_height, AVFrame *frame, AVPixelFormat format, bool hardware) {
|
||||
// If the device used is hardware, yet the image resides on main memory
|
||||
if(frame->hw_frames_ctx) {
|
||||
if(hardware) {
|
||||
sw_frame.reset(av_frame_alloc());
|
||||
|
||||
sw_frame->width = frame->width;
|
||||
@@ -235,11 +249,14 @@ public:
|
||||
};
|
||||
|
||||
enum flag_e {
|
||||
DEFAULT = 0x00,
|
||||
PARALLEL_ENCODING = 0x01,
|
||||
H264_ONLY = 0x02, // When HEVC is too heavy
|
||||
LIMITED_GOP_SIZE = 0x04, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough*
|
||||
SINGLE_SLICE_ONLY = 0x08, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P
|
||||
DEFAULT = 0x00,
|
||||
PARALLEL_ENCODING = 0x01,
|
||||
H264_ONLY = 0x02, // When HEVC is too heavy
|
||||
LIMITED_GOP_SIZE = 0x04, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough*
|
||||
SINGLE_SLICE_ONLY = 0x08, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P
|
||||
CBR_WITH_VBR = 0x10, // Use a VBR rate control mode to simulate CBR
|
||||
RELAXED_COMPLIANCE = 0x20, // Use FF_COMPLIANCE_UNOFFICIAL compliance mode
|
||||
NO_RC_BUF_LIMIT = 0x40, // Don't set rc_buffer_size
|
||||
};
|
||||
|
||||
struct encoder_t {
|
||||
@@ -286,11 +303,10 @@ struct encoder_t {
|
||||
option_t(std::string &&name, decltype(value) &&value) : name { std::move(name) }, value { std::move(value) } {}
|
||||
};
|
||||
|
||||
AVHWDeviceType dev_type;
|
||||
AVHWDeviceType base_dev_type, derived_dev_type;
|
||||
AVPixelFormat dev_pix_fmt;
|
||||
|
||||
AVPixelFormat static_pix_fmt;
|
||||
AVPixelFormat dynamic_pix_fmt;
|
||||
AVPixelFormat static_pix_fmt, dynamic_pix_fmt;
|
||||
|
||||
struct {
|
||||
std::vector<option_t> common_options;
|
||||
@@ -357,6 +373,7 @@ struct sync_session_ctx_t {
|
||||
safe::mail_raw_t::event_t<bool> shutdown_event;
|
||||
safe::mail_raw_t::queue_t<packet_t> packets;
|
||||
safe::mail_raw_t::event_t<bool> idr_events;
|
||||
safe::mail_raw_t::event_t<hdr_info_t> hdr_events;
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_events;
|
||||
|
||||
config_t config;
|
||||
@@ -376,7 +393,7 @@ using encode_e = platf::capture_e;
|
||||
|
||||
struct capture_ctx_t {
|
||||
img_event_t images;
|
||||
int framerate;
|
||||
config_t config;
|
||||
};
|
||||
|
||||
struct capture_thread_async_ctx_t {
|
||||
@@ -404,10 +421,10 @@ auto capture_thread_sync = safe::make_shared<capture_thread_sync_ctx_t>(start_c
|
||||
static encoder_t nvenc {
|
||||
"nvenc"sv,
|
||||
#ifdef _WIN32
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE,
|
||||
AV_PIX_FMT_D3D11,
|
||||
#else
|
||||
AV_HWDEVICE_TYPE_CUDA,
|
||||
AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_NONE,
|
||||
AV_PIX_FMT_CUDA,
|
||||
#endif
|
||||
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
||||
@@ -459,9 +476,64 @@ static encoder_t nvenc {
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
static encoder_t quicksync {
|
||||
"quicksync"sv,
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_HWDEVICE_TYPE_QSV,
|
||||
AV_PIX_FMT_QSV,
|
||||
AV_PIX_FMT_NV12,
|
||||
AV_PIX_FMT_P010,
|
||||
{
|
||||
// Common options
|
||||
{
|
||||
{ "preset"s, &config::video.qsv.preset },
|
||||
{ "forced_idr"s, 1 },
|
||||
{ "async_depth"s, 1 },
|
||||
{ "low_delay_brc"s, 1 },
|
||||
{ "low_power"s, 1 },
|
||||
{ "recovery_point_sei"s, 0 },
|
||||
{ "pic_timing_sei"s, 0 },
|
||||
},
|
||||
// SDR-specific options
|
||||
{
|
||||
{ "profile"s, (int)qsv::profile_hevc_e::main },
|
||||
},
|
||||
// HDR-specific options
|
||||
{
|
||||
{ "profile"s, (int)qsv::profile_hevc_e::main_10 },
|
||||
},
|
||||
std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
||||
"hevc_qsv"s,
|
||||
},
|
||||
{
|
||||
// Common options
|
||||
{
|
||||
{ "preset"s, &config::video.qsv.preset },
|
||||
{ "cavlc"s, &config::video.qsv.cavlc },
|
||||
{ "forced_idr"s, 1 },
|
||||
{ "async_depth"s, 1 },
|
||||
{ "low_delay_brc"s, 1 },
|
||||
{ "low_power"s, 1 },
|
||||
{ "recovery_point_sei"s, 0 },
|
||||
{ "vcm"s, 1 },
|
||||
{ "pic_timing_sei"s, 0 },
|
||||
{ "max_dec_frame_buffering"s, 1 },
|
||||
},
|
||||
// SDR-specific options
|
||||
{
|
||||
{ "profile"s, (int)qsv::profile_h264_e::high },
|
||||
},
|
||||
{}, // HDR-specific options
|
||||
std::make_optional<encoder_t::option_t>({ "qp"s, &config::video.qp }),
|
||||
"h264_qsv"s,
|
||||
},
|
||||
PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT,
|
||||
dxgi_make_hwdevice_ctx,
|
||||
};
|
||||
|
||||
static encoder_t amdvce {
|
||||
"amdvce"sv,
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE,
|
||||
AV_PIX_FMT_D3D11,
|
||||
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
||||
{
|
||||
@@ -506,7 +578,7 @@ static encoder_t amdvce {
|
||||
|
||||
static encoder_t software {
|
||||
"software"sv,
|
||||
AV_HWDEVICE_TYPE_NONE,
|
||||
AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE,
|
||||
AV_PIX_FMT_NONE,
|
||||
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10,
|
||||
{
|
||||
@@ -544,7 +616,7 @@ static encoder_t software {
|
||||
#ifdef __linux__
|
||||
static encoder_t vaapi {
|
||||
"vaapi"sv,
|
||||
AV_HWDEVICE_TYPE_VAAPI,
|
||||
AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_NONE,
|
||||
AV_PIX_FMT_VAAPI,
|
||||
AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P10,
|
||||
{
|
||||
@@ -580,7 +652,7 @@ static encoder_t vaapi {
|
||||
#ifdef __APPLE__
|
||||
static encoder_t videotoolbox {
|
||||
"videotoolbox"sv,
|
||||
AV_HWDEVICE_TYPE_NONE,
|
||||
AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE,
|
||||
AV_PIX_FMT_VIDEOTOOLBOX,
|
||||
AV_PIX_FMT_NV12, AV_PIX_FMT_NV12,
|
||||
{
|
||||
@@ -618,6 +690,7 @@ static std::vector<encoder_t> encoders {
|
||||
nvenc,
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
quicksync,
|
||||
amdvce,
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
@@ -629,15 +702,16 @@ static std::vector<encoder_t> encoders {
|
||||
software
|
||||
};
|
||||
|
||||
void reset_display(std::shared_ptr<platf::display_t> &disp, AVHWDeviceType type, const std::string &display_name, int framerate) {
|
||||
void reset_display(std::shared_ptr<platf::display_t> &disp, AVHWDeviceType type, const std::string &display_name, const config_t &config) {
|
||||
// We try this twice, in case we still get an error on reinitialization
|
||||
for(int x = 0; x < 2; ++x) {
|
||||
disp.reset();
|
||||
disp = platf::display(map_dev_type(type), display_name, framerate);
|
||||
disp = platf::display(map_base_dev_type(type), display_name, config);
|
||||
if(disp) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The capture code depends on us to sleep between failures
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
}
|
||||
@@ -665,7 +739,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(map_dev_type(encoder.dev_type));
|
||||
auto display_names = platf::display_names(map_base_dev_type(encoder.base_dev_type));
|
||||
int display_p = 0;
|
||||
|
||||
if(display_names.empty()) {
|
||||
@@ -684,7 +758,7 @@ void captureThread(
|
||||
capture_ctxs.emplace_back(std::move(*capture_ctx));
|
||||
}
|
||||
|
||||
auto disp = platf::display(map_dev_type(encoder.dev_type), display_names[display_p], capture_ctxs.front().framerate);
|
||||
auto disp = platf::display(map_base_dev_type(encoder.base_dev_type), display_names[display_p], capture_ctxs.front().config);
|
||||
if(!disp) {
|
||||
return;
|
||||
}
|
||||
@@ -766,16 +840,32 @@ void captureThread(
|
||||
// Wait for the other shared_ptr's of display to be destroyed.
|
||||
// New displays will only be created in this thread.
|
||||
while(display_wp->use_count() != 1) {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
// Free images that weren't consumed by the encoders. These can reference the display and prevent
|
||||
// the ref count from reaching 1. We do this here rather than on the encoder thread to avoid race
|
||||
// conditions where the encoding loop might free a good frame after reinitializing if we capture
|
||||
// a new frame here before the encoder has finished reinitializing.
|
||||
KITTY_WHILE_LOOP(auto capture_ctx = std::begin(capture_ctxs), capture_ctx != std::end(capture_ctxs), {
|
||||
if(!capture_ctx->images->running()) {
|
||||
capture_ctx = capture_ctxs.erase(capture_ctx);
|
||||
continue;
|
||||
}
|
||||
|
||||
while(capture_ctx->images->peek()) {
|
||||
capture_ctx->images->pop();
|
||||
}
|
||||
|
||||
++capture_ctx;
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(20ms);
|
||||
}
|
||||
|
||||
while(capture_ctx_queue->running()) {
|
||||
reset_display(disp, encoder.dev_type, display_names[display_p], capture_ctxs.front().framerate);
|
||||
|
||||
// reset_display() will sleep between retries
|
||||
reset_display(disp, encoder.base_dev_type, display_names[display_p], capture_ctxs.front().config);
|
||||
if(disp) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
if(!disp) {
|
||||
return;
|
||||
@@ -868,8 +958,8 @@ 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, std::shared_ptr<platf::hwdevice_t> &&hwdevice) {
|
||||
bool hardware = encoder.dev_type != AV_HWDEVICE_TYPE_NONE;
|
||||
std::optional<session_t> make_session(platf::display_t *disp, const encoder_t &encoder, const config_t &config, int width, int height, std::shared_ptr<platf::hwdevice_t> &&hwdevice) {
|
||||
bool hardware = encoder.base_dev_type != AV_HWDEVICE_TYPE_NONE;
|
||||
|
||||
auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc;
|
||||
if(!video_format[encoder_t::PASSED]) {
|
||||
@@ -929,35 +1019,46 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
ctx->color_range = (config.encoderCscMode & 0x1) ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
|
||||
|
||||
int sws_color_space;
|
||||
switch(config.encoderCscMode >> 1) {
|
||||
case 0:
|
||||
default:
|
||||
// Rec. 601
|
||||
BOOST_LOG(info) << "Color coding [Rec. 601]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_SMPTE170M;
|
||||
ctx->color_trc = AVCOL_TRC_SMPTE170M;
|
||||
ctx->colorspace = AVCOL_SPC_SMPTE170M;
|
||||
sws_color_space = SWS_CS_SMPTE170M;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Rec. 709
|
||||
BOOST_LOG(info) << "Color coding [Rec. 709]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_BT709;
|
||||
ctx->color_trc = AVCOL_TRC_BT709;
|
||||
ctx->colorspace = AVCOL_SPC_BT709;
|
||||
sws_color_space = SWS_CS_ITU709;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Rec. 2020
|
||||
BOOST_LOG(info) << "Color coding [Rec. 2020]"sv;
|
||||
if(config.dynamicRange && disp->is_hdr()) {
|
||||
// When HDR is active, that overrides the colorspace the client requested
|
||||
BOOST_LOG(info) << "HDR color coding [Rec. 2020 + SMPTE 2084 PQ]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_BT2020;
|
||||
ctx->color_trc = AVCOL_TRC_BT2020_10;
|
||||
ctx->color_trc = AVCOL_TRC_SMPTE2084;
|
||||
ctx->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
sws_color_space = SWS_CS_BT2020;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
switch(config.encoderCscMode >> 1) {
|
||||
case 0:
|
||||
default:
|
||||
// Rec. 601
|
||||
BOOST_LOG(info) << "SDR color coding [Rec. 601]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_SMPTE170M;
|
||||
ctx->color_trc = AVCOL_TRC_SMPTE170M;
|
||||
ctx->colorspace = AVCOL_SPC_SMPTE170M;
|
||||
sws_color_space = SWS_CS_SMPTE170M;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Rec. 709
|
||||
BOOST_LOG(info) << "SDR color coding [Rec. 709]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_BT709;
|
||||
ctx->color_trc = AVCOL_TRC_BT709;
|
||||
ctx->colorspace = AVCOL_SPC_BT709;
|
||||
sws_color_space = SWS_CS_ITU709;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Rec. 2020
|
||||
BOOST_LOG(info) << "SDR color coding [Rec. 2020]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_BT2020;
|
||||
ctx->color_trc = AVCOL_TRC_BT2020_10;
|
||||
ctx->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
sws_color_space = SWS_CS_BT2020;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Color range: ["sv << ((config.encoderCscMode & 0x1) ? "JPEG"sv : "MPEG"sv) << ']';
|
||||
|
||||
AVPixelFormat sw_fmt;
|
||||
@@ -971,17 +1072,39 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
// Used by cbs::make_sps_hevc
|
||||
ctx->sw_pix_fmt = sw_fmt;
|
||||
|
||||
buffer_t hwdevice_ctx;
|
||||
if(hardware) {
|
||||
buffer_t hwdevice_ctx;
|
||||
|
||||
ctx->pix_fmt = encoder.dev_pix_fmt;
|
||||
|
||||
// Create the base hwdevice context
|
||||
auto buf_or_error = encoder.make_hwdevice_ctx(hwdevice.get());
|
||||
if(buf_or_error.has_right()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
hwdevice_ctx = std::move(buf_or_error.left());
|
||||
if(hwframe_ctx(ctx, hwdevice_ctx, sw_fmt)) {
|
||||
|
||||
// If this encoder requires derivation from the base, derive the desired type
|
||||
if(encoder.derived_dev_type != AV_HWDEVICE_TYPE_NONE) {
|
||||
buffer_t derived_hwdevice_ctx;
|
||||
|
||||
// Allow the hwdevice to prepare for this type of context to be derived
|
||||
if(hwdevice->prepare_to_derive_context(encoder.derived_dev_type)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto err = av_hwdevice_ctx_create_derived(&derived_hwdevice_ctx, encoder.derived_dev_type, hwdevice_ctx.get(), 0);
|
||||
if(err) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Failed to derive device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
hwdevice_ctx = std::move(derived_hwdevice_ctx);
|
||||
}
|
||||
|
||||
if(hwframe_ctx(ctx, hwdevice.get(), hwdevice_ctx, sw_fmt)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -1027,17 +1150,30 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
auto bitrate = config.bitrate * 1000;
|
||||
ctx->rc_max_rate = bitrate;
|
||||
ctx->bit_rate = bitrate;
|
||||
ctx->rc_min_rate = bitrate;
|
||||
|
||||
if(!hardware && (ctx->slices > 1 || config.videoFormat != 0)) {
|
||||
// Use a larger rc_buffer_size for software encoding when slices are enabled,
|
||||
// because libx264 can severely degrade quality if the buffer is too small.
|
||||
// libx265 encounters this issue more frequently, so always scale the
|
||||
// buffer by 1.5x for software HEVC encoding.
|
||||
ctx->rc_buffer_size = bitrate / ((config.framerate * 10) / 15);
|
||||
if(encoder.flags & CBR_WITH_VBR) {
|
||||
// Ensure rc_max_bitrate != bit_rate to force VBR mode
|
||||
ctx->bit_rate--;
|
||||
}
|
||||
else {
|
||||
ctx->rc_buffer_size = bitrate / config.framerate;
|
||||
ctx->rc_min_rate = bitrate;
|
||||
}
|
||||
|
||||
if(encoder.flags & RELAXED_COMPLIANCE) {
|
||||
ctx->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
|
||||
}
|
||||
|
||||
if(!(encoder.flags & NO_RC_BUF_LIMIT)) {
|
||||
if(!hardware && (ctx->slices > 1 || config.videoFormat != 0)) {
|
||||
// Use a larger rc_buffer_size for software encoding when slices are enabled,
|
||||
// because libx264 can severely degrade quality if the buffer is too small.
|
||||
// libx265 encounters this issue more frequently, so always scale the
|
||||
// buffer by 1.5x for software HEVC encoding.
|
||||
ctx->rc_buffer_size = bitrate / ((config.framerate * 10) / 15);
|
||||
}
|
||||
else {
|
||||
ctx->rc_buffer_size = bitrate / config.framerate;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(video_format.qp) {
|
||||
@@ -1063,9 +1199,35 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
frame->width = ctx->width;
|
||||
frame->height = ctx->height;
|
||||
|
||||
// Attach HDR metadata to the AVFrame
|
||||
if(config.dynamicRange && disp->is_hdr()) {
|
||||
SS_HDR_METADATA hdr_metadata;
|
||||
if(disp->get_hdr_metadata(hdr_metadata)) {
|
||||
auto mdm = av_mastering_display_metadata_create_side_data(frame.get());
|
||||
|
||||
if(hardware) {
|
||||
frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frames_ctx);
|
||||
mdm->display_primaries[0][0] = av_make_q(hdr_metadata.displayPrimaries[0].x, 50000);
|
||||
mdm->display_primaries[0][1] = av_make_q(hdr_metadata.displayPrimaries[0].y, 50000);
|
||||
mdm->display_primaries[1][0] = av_make_q(hdr_metadata.displayPrimaries[1].x, 50000);
|
||||
mdm->display_primaries[1][1] = av_make_q(hdr_metadata.displayPrimaries[1].y, 50000);
|
||||
mdm->display_primaries[2][0] = av_make_q(hdr_metadata.displayPrimaries[2].x, 50000);
|
||||
mdm->display_primaries[2][1] = av_make_q(hdr_metadata.displayPrimaries[2].y, 50000);
|
||||
|
||||
mdm->white_point[0] = av_make_q(hdr_metadata.whitePoint.x, 50000);
|
||||
mdm->white_point[1] = av_make_q(hdr_metadata.whitePoint.y, 50000);
|
||||
|
||||
mdm->min_luminance = av_make_q(hdr_metadata.minDisplayLuminance, 10000);
|
||||
mdm->max_luminance = av_make_q(hdr_metadata.maxDisplayLuminance, 1);
|
||||
|
||||
mdm->has_luminance = hdr_metadata.maxDisplayLuminance != 0 ? 1 : 0;
|
||||
mdm->has_primaries = hdr_metadata.displayPrimaries[0].x != 0 ? 1 : 0;
|
||||
|
||||
if(hdr_metadata.maxContentLightLevel != 0 || hdr_metadata.maxFrameAverageLightLevel != 0) {
|
||||
auto clm = av_content_light_metadata_create_side_data(frame.get());
|
||||
|
||||
clm->MaxCLL = hdr_metadata.maxContentLightLevel;
|
||||
clm->MaxFALL = hdr_metadata.maxFrameAverageLightLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::hwdevice_t> device;
|
||||
@@ -1073,7 +1235,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
if(!hwdevice->data) {
|
||||
auto device_tmp = std::make_unique<swdevice_t>();
|
||||
|
||||
if(device_tmp->init(width, height, frame.get(), sw_fmt)) {
|
||||
if(device_tmp->init(width, height, frame.get(), sw_fmt, hardware)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -1083,7 +1245,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
device = std::move(hwdevice);
|
||||
}
|
||||
|
||||
if(device->set_frame(frame.release())) {
|
||||
if(device->set_frame(frame.release(), ctx->hw_frames_ctx)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -1111,13 +1273,13 @@ void encode_run(
|
||||
safe::mail_t mail,
|
||||
img_event_t images,
|
||||
config_t config,
|
||||
int width, int height,
|
||||
std::shared_ptr<platf::display_t> disp,
|
||||
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, std::move(hwdevice));
|
||||
auto session = make_session(disp.get(), encoder, config, disp->width, disp->height, std::move(hwdevice));
|
||||
if(!session) {
|
||||
return;
|
||||
}
|
||||
@@ -1128,6 +1290,13 @@ void encode_run(
|
||||
auto packets = mail::man->queue<packet_t>(mail::video_packets);
|
||||
auto idr_events = mail->event<bool>(mail::idr);
|
||||
|
||||
// Load a dummy image into the AVFrame to ensure we have something to encode
|
||||
// even if we time out waiting on the first frame.
|
||||
auto dummy_img = disp->alloc_img();
|
||||
if(!dummy_img || disp->dummy_img(dummy_img.get()) || session->device->convert(*dummy_img)) {
|
||||
return;
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(shutdown_event->peek() || reinit_event.peek() || !images->running()) {
|
||||
break;
|
||||
@@ -1140,14 +1309,15 @@ void encode_run(
|
||||
idr_events->pop();
|
||||
}
|
||||
|
||||
// Encode at a minimum of 10 FPS to avoid image quality issues with static content
|
||||
if(!frame->key_frame || images->peek()) {
|
||||
if(auto img = images->pop(100ms)) {
|
||||
session->device->convert(*img);
|
||||
if(session->device->convert(*img)) {
|
||||
BOOST_LOG(error) << "Could not convert image"sv;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(images->running()) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
else if(!images->running()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1204,7 +1374,15 @@ 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, std::move(hwdevice));
|
||||
// Update client with our current HDR display state
|
||||
hdr_info_t hdr_info = std::make_unique<hdr_info_raw_t>(false);
|
||||
if(ctx.config.dynamicRange && disp->is_hdr()) {
|
||||
disp->get_hdr_metadata(hdr_info->metadata);
|
||||
hdr_info->enabled = true;
|
||||
}
|
||||
ctx.hdr_events->raise(std::move(hdr_info));
|
||||
|
||||
auto session = make_session(disp, encoder, ctx.config, img.width, img.height, std::move(hwdevice));
|
||||
if(!session) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -1219,7 +1397,7 @@ encode_e encode_run_sync(
|
||||
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));
|
||||
auto display_names = platf::display_names(map_base_dev_type(encoder.base_dev_type));
|
||||
int display_p = 0;
|
||||
|
||||
if(display_names.empty()) {
|
||||
@@ -1247,15 +1425,12 @@ encode_e encode_run_sync(
|
||||
synced_session_ctxs.emplace_back(std::make_unique<sync_session_ctx_t>(std::move(*ctx)));
|
||||
}
|
||||
|
||||
int framerate = synced_session_ctxs.front()->config.framerate;
|
||||
|
||||
while(encode_session_ctx_queue.running()) {
|
||||
reset_display(disp, encoder.dev_type, display_names[display_p], framerate);
|
||||
// reset_display() will sleep between retries
|
||||
reset_display(disp, encoder.base_dev_type, display_names[display_p], synced_session_ctxs.front()->config);
|
||||
if(disp) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
|
||||
if(!disp) {
|
||||
@@ -1410,8 +1585,7 @@ void capture_async(
|
||||
return;
|
||||
}
|
||||
|
||||
ref->capture_ctx_queue->raise(capture_ctx_t {
|
||||
images, config.framerate });
|
||||
ref->capture_ctx_queue->raise(capture_ctx_t { images, config });
|
||||
|
||||
if(!ref->capture_ctx_queue->running()) {
|
||||
return;
|
||||
@@ -1420,6 +1594,7 @@ void capture_async(
|
||||
int frame_nr = 1;
|
||||
|
||||
auto touch_port_event = mail->event<input::touch_port_t>(mail::touch_port);
|
||||
auto hdr_event = mail->event<hdr_info_t>(mail::hdr);
|
||||
|
||||
// Encoding takes place on this thread
|
||||
platf::adjust_thread_priority(platf::thread_priority_e::high);
|
||||
@@ -1427,7 +1602,7 @@ void capture_async(
|
||||
while(!shutdown_event->peek() && images->running()) {
|
||||
// Wait for the main capture event when the display is being reinitialized
|
||||
if(ref->reinit_event.peek()) {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
std::this_thread::sleep_for(20ms);
|
||||
continue;
|
||||
}
|
||||
// Wait for the display to be ready
|
||||
@@ -1448,29 +1623,24 @@ void capture_async(
|
||||
return;
|
||||
}
|
||||
|
||||
auto dummy_img = display->alloc_img();
|
||||
if(!dummy_img || display->dummy_img(dummy_img.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
images->raise(std::move(dummy_img));
|
||||
|
||||
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||
touch_port_event->raise(make_port(display.get(), config));
|
||||
|
||||
// Update client with our current HDR display state
|
||||
hdr_info_t hdr_info = std::make_unique<hdr_info_raw_t>(false);
|
||||
if(config.dynamicRange && display->is_hdr()) {
|
||||
display->get_hdr_metadata(hdr_info->metadata);
|
||||
hdr_info->enabled = true;
|
||||
}
|
||||
hdr_event->raise(std::move(hdr_info));
|
||||
|
||||
encode_run(
|
||||
frame_nr,
|
||||
mail, images,
|
||||
config, display->width, display->height,
|
||||
config, display,
|
||||
std::move(hwdevice),
|
||||
ref->reinit_event, *ref->encoder_p,
|
||||
channel_data);
|
||||
|
||||
// Free images that weren't consumed by the encoder before it quit.
|
||||
// This is critical to allow the display_t to be freed correctly.
|
||||
while(images->peek()) {
|
||||
images->pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1493,6 +1663,7 @@ void capture(
|
||||
mail->event<bool>(mail::shutdown),
|
||||
mail::man->queue<packet_t>(mail::video_packets),
|
||||
std::move(idr_events),
|
||||
mail->event<hdr_info_t>(mail::hdr),
|
||||
mail->event<input::touch_port_t>(mail::touch_port),
|
||||
config,
|
||||
1,
|
||||
@@ -1510,7 +1681,7 @@ enum validate_flag_e {
|
||||
};
|
||||
|
||||
int validate_config(std::shared_ptr<platf::display_t> &disp, const encoder_t &encoder, const config_t &config) {
|
||||
reset_display(disp, encoder.dev_type, config::video.output_name, config.framerate);
|
||||
reset_display(disp, encoder.base_dev_type, config::video.output_name, config);
|
||||
if(!disp) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1521,7 +1692,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, std::move(hwdevice));
|
||||
auto session = make_session(disp.get(), encoder, config, disp->width, disp->height, std::move(hwdevice));
|
||||
if(!session) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1812,8 +1983,8 @@ int init() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) {
|
||||
buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice.get()) };
|
||||
int hwframe_ctx(ctx_t &ctx, platf::hwdevice_t *hwdevice, buffer_t &hwdevice_ctx, AVPixelFormat format) {
|
||||
buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice_ctx.get()) };
|
||||
|
||||
auto frame_ctx = (AVHWFramesContext *)frame_ref->data;
|
||||
frame_ctx->format = ctx->pix_fmt;
|
||||
@@ -1822,6 +1993,9 @@ int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) {
|
||||
frame_ctx->width = ctx->width;
|
||||
frame_ctx->initial_pool_size = 0;
|
||||
|
||||
// Allow the hwdevice to modify hwframe context parameters
|
||||
hwdevice->init_hwframes(frame_ctx);
|
||||
|
||||
if(auto err = av_hwframe_ctx_init(frame_ref.get()); err < 0) {
|
||||
return err;
|
||||
}
|
||||
@@ -1932,7 +2106,7 @@ int start_capture_sync(capture_thread_sync_ctx_t &ctx) {
|
||||
}
|
||||
void end_capture_sync(capture_thread_sync_ctx_t &ctx) {}
|
||||
|
||||
platf::mem_type_e map_dev_type(AVHWDeviceType type) {
|
||||
platf::mem_type_e map_base_dev_type(AVHWDeviceType type) {
|
||||
switch(type) {
|
||||
case AV_HWDEVICE_TYPE_D3D11VA:
|
||||
return platf::mem_type_e::dxgi;
|
||||
|
||||
10
src/video.h
10
src/video.h
@@ -48,6 +48,16 @@ struct packet_raw_t {
|
||||
|
||||
using packet_t = std::unique_ptr<packet_raw_t>;
|
||||
|
||||
struct hdr_info_raw_t {
|
||||
explicit hdr_info_raw_t(bool enabled) : enabled { enabled }, metadata {} {};
|
||||
explicit hdr_info_raw_t(bool enabled, const SS_HDR_METADATA &metadata) : enabled { enabled }, metadata { metadata } {};
|
||||
|
||||
bool enabled;
|
||||
SS_HDR_METADATA metadata;
|
||||
};
|
||||
|
||||
using hdr_info_t = std::unique_ptr<hdr_info_raw_t>;
|
||||
|
||||
struct config_t {
|
||||
int width;
|
||||
int height;
|
||||
|
||||
@@ -523,6 +523,7 @@
|
||||
<select id="encoder" class="form-select" v-model="config.encoder">
|
||||
<option value>Autodetect</option>
|
||||
<option value="nvenc" v-if="platform === 'windows' || platform === 'linux'">NVIDIA NVENC</option>
|
||||
<option value="quicksync" v-if="platform === 'windows'">Intel QuickSync</option>
|
||||
<option value="amdvce" v-if="platform === 'windows'">AMD AMF/VCE</option>
|
||||
<option value="vaapi" v-if="platform === 'linux'">VA-API</option>
|
||||
<option value="videotoolbox" v-if="platform === 'macos'">VideoToolbox</option>
|
||||
@@ -695,6 +696,29 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!--Intel Encoder Settings-->
|
||||
<div v-if="currentTab === 'qsv'" class="config-page">
|
||||
<div class="mb-3">
|
||||
<label for="qsv_preset" class="form-label">QuickSync Preset</label>
|
||||
<select id="qsv_preset" class="form-select" v-model="config.qsv_preset">
|
||||
<option value="veryfast">fastest (lowest quality)</option>
|
||||
<option value="faster">faster (lower quality)</option>
|
||||
<option value="fast">fast (low quality)</option>
|
||||
<option value="medium">medium (default)</option>
|
||||
<option value="slow">slow (good quality)</option>
|
||||
<option value="slower">slower (better quality)</option>
|
||||
<option value="slowest">slowest (best quality)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="qsv_coder" class="form-label">QuickSync Coder (H264)</label>
|
||||
<select id="qsv_coder" class="form-select" v-model="config.qsv_coder">
|
||||
<option value="auto">auto -- let ffmpeg decide (default)</option>
|
||||
<option value="cabac">cabac -- context adaptive binary arithmetic coding - higher quality</option>
|
||||
<option value="cavlc">cavlc -- context adaptive variable-length coding - faster decode</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!--AMD Encoder Settings-->
|
||||
<div v-if="currentTab === 'amd'" class="config-page">
|
||||
<!--Presets-->
|
||||
@@ -820,6 +844,10 @@
|
||||
id: "nv",
|
||||
name: "NVIDIA NVENC Encoder",
|
||||
},
|
||||
{
|
||||
id: "qsv",
|
||||
name: "Intel QuickSync Encoder",
|
||||
},
|
||||
{
|
||||
id: "amd",
|
||||
name: "AMD AMF Encoder",
|
||||
@@ -855,12 +883,12 @@
|
||||
}
|
||||
if (this.platform == "linux") {
|
||||
this.tabs = this.tabs.filter((el) => {
|
||||
return el.id !== "amd" && el.id !== "vt";
|
||||
return el.id !== "amd" && el.id !== "qsv" && el.id !== "vt";
|
||||
});
|
||||
}
|
||||
if (this.platform == "macos") {
|
||||
this.tabs = this.tabs.filter((el) => {
|
||||
return el.id !== "amd" && el.id !== "nv" && el.id !== "va-api";
|
||||
return el.id !== "amd" && el.id !== "nv" && el.id !== "qsv" && el.id !== "va-api";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -884,6 +912,8 @@
|
||||
this.config.nv_tune = this.config.nv_tune || "ull";
|
||||
this.config.nv_coder = this.config.nv_coder || "auto";
|
||||
this.config.nv_rc = this.config.nv_rc || "cbr";
|
||||
this.config.qsv_preset = this.config.qsv_preset || "medium";
|
||||
this.config.qsv_coder = this.config.qsv_coder || "auto";
|
||||
this.config.amd_coder = this.config.amd_coder || "auto"
|
||||
this.config.amd_quality = this.config.amd_quality || "balanced";
|
||||
this.config.amd_rc = this.config.amd_rc || "vbr_latency";
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Steam BigPicture",
|
||||
"name": "Steam Big Picture",
|
||||
"detached": [
|
||||
"setsid steam steam://open/bigpicture"
|
||||
],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"image-path": "desktop.png"
|
||||
},
|
||||
{
|
||||
"name": "Steam BigPicture",
|
||||
"name": "Steam Big Picture",
|
||||
"detached": [
|
||||
"open steam://open/bigpicture"
|
||||
],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"image-path": "desktop.png"
|
||||
},
|
||||
{
|
||||
"name": "Steam BigPicture",
|
||||
"name": "Steam Big Picture",
|
||||
"detached": [
|
||||
"steam steam://open/bigpicture"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
Texture2D image : register(t0);
|
||||
|
||||
SamplerState def_sampler : register(s0);
|
||||
|
||||
struct FragTexWide {
|
||||
float3 uuv : TEXCOORD0;
|
||||
};
|
||||
|
||||
cbuffer ColorMatrix : register(b0) {
|
||||
float4 color_vec_y;
|
||||
float4 color_vec_u;
|
||||
float4 color_vec_v;
|
||||
float2 range_y;
|
||||
float2 range_uv;
|
||||
};
|
||||
|
||||
float3 NitsToPQ(float3 L)
|
||||
{
|
||||
// Constants from SMPTE 2084 PQ
|
||||
static const float m1 = 2610.0 / 4096.0 / 4;
|
||||
static const float m2 = 2523.0 / 4096.0 * 128;
|
||||
static const float c1 = 3424.0 / 4096.0;
|
||||
static const float c2 = 2413.0 / 4096.0 * 32;
|
||||
static const float c3 = 2392.0 / 4096.0 * 32;
|
||||
|
||||
float3 Lp = pow(saturate(L / 10000.0), m1);
|
||||
return pow((c1 + c2 * Lp) / (1 + c3 * Lp), m2);
|
||||
}
|
||||
|
||||
float3 Rec709toRec2020(float3 rec709)
|
||||
{
|
||||
static const float3x3 ConvMat =
|
||||
{
|
||||
0.627402, 0.329292, 0.043306,
|
||||
0.069095, 0.919544, 0.011360,
|
||||
0.016394, 0.088028, 0.895578
|
||||
};
|
||||
return mul(ConvMat, rec709);
|
||||
}
|
||||
|
||||
float3 scRGBTo2100PQ(float3 rgb)
|
||||
{
|
||||
// Convert from Rec 709 primaries (used by scRGB) to Rec 2020 primaries (used by Rec 2100)
|
||||
rgb = Rec709toRec2020(rgb);
|
||||
|
||||
// 1.0f is defined as 80 nits in the scRGB colorspace
|
||||
rgb *= 80;
|
||||
|
||||
// Apply the PQ transfer function on the raw color values in nits
|
||||
return NitsToPQ(rgb);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Pixel Shader
|
||||
//--------------------------------------------------------------------------------------
|
||||
float2 main_ps(FragTexWide input) : SV_Target
|
||||
{
|
||||
float3 rgb_left = scRGBTo2100PQ(image.Sample(def_sampler, input.uuv.xz).rgb);
|
||||
float3 rgb_right = scRGBTo2100PQ(image.Sample(def_sampler, input.uuv.yz).rgb);
|
||||
float3 rgb = (rgb_left + rgb_right) * 0.5;
|
||||
|
||||
float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w;
|
||||
float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w;
|
||||
|
||||
u = u * range_uv.x + range_uv.y;
|
||||
v = v * range_uv.x + range_uv.y;
|
||||
|
||||
return float2(u, v);
|
||||
}
|
||||
62
src_assets/windows/assets/shaders/directx/ConvertYPS_PQ.hlsl
Normal file
62
src_assets/windows/assets/shaders/directx/ConvertYPS_PQ.hlsl
Normal file
@@ -0,0 +1,62 @@
|
||||
Texture2D image : register(t0);
|
||||
|
||||
SamplerState def_sampler : register(s0);
|
||||
|
||||
cbuffer ColorMatrix : register(b0) {
|
||||
float4 color_vec_y;
|
||||
float4 color_vec_u;
|
||||
float4 color_vec_v;
|
||||
float2 range_y;
|
||||
float2 range_uv;
|
||||
};
|
||||
|
||||
struct PS_INPUT
|
||||
{
|
||||
float4 pos : SV_POSITION;
|
||||
float2 tex : TEXCOORD;
|
||||
};
|
||||
|
||||
float3 NitsToPQ(float3 L)
|
||||
{
|
||||
// Constants from SMPTE 2084 PQ
|
||||
static const float m1 = 2610.0 / 4096.0 / 4;
|
||||
static const float m2 = 2523.0 / 4096.0 * 128;
|
||||
static const float c1 = 3424.0 / 4096.0;
|
||||
static const float c2 = 2413.0 / 4096.0 * 32;
|
||||
static const float c3 = 2392.0 / 4096.0 * 32;
|
||||
|
||||
float3 Lp = pow(saturate(L / 10000.0), m1);
|
||||
return pow((c1 + c2 * Lp) / (1 + c3 * Lp), m2);
|
||||
}
|
||||
|
||||
float3 Rec709toRec2020(float3 rec709)
|
||||
{
|
||||
static const float3x3 ConvMat =
|
||||
{
|
||||
0.627402, 0.329292, 0.043306,
|
||||
0.069095, 0.919544, 0.011360,
|
||||
0.016394, 0.088028, 0.895578
|
||||
};
|
||||
return mul(ConvMat, rec709);
|
||||
}
|
||||
|
||||
float3 scRGBTo2100PQ(float3 rgb)
|
||||
{
|
||||
// Convert from Rec 709 primaries (used by scRGB) to Rec 2020 primaries (used by Rec 2100)
|
||||
rgb = Rec709toRec2020(rgb);
|
||||
|
||||
// 1.0f is defined as 80 nits in the scRGB colorspace
|
||||
rgb *= 80;
|
||||
|
||||
// Apply the PQ transfer function on the raw color values in nits
|
||||
return NitsToPQ(rgb);
|
||||
}
|
||||
|
||||
float main_ps(PS_INPUT frag_in) : SV_Target
|
||||
{
|
||||
float3 rgb = scRGBTo2100PQ(image.Sample(def_sampler, frag_in.tex, 0).rgb);
|
||||
|
||||
float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w;
|
||||
|
||||
return y * range_y.x + range_y.y;
|
||||
}
|
||||
22
src_assets/windows/assets/shaders/directx/ScenePS_NW.hlsl
Normal file
22
src_assets/windows/assets/shaders/directx/ScenePS_NW.hlsl
Normal file
@@ -0,0 +1,22 @@
|
||||
Texture2D image : register(t0);
|
||||
|
||||
SamplerState def_sampler : register(s0);
|
||||
|
||||
struct PS_INPUT
|
||||
{
|
||||
float4 pos : SV_POSITION;
|
||||
float2 tex : TEXCOORD;
|
||||
};
|
||||
|
||||
cbuffer SdrScaling : register(b0) {
|
||||
float scale_factor;
|
||||
};
|
||||
|
||||
float4 main_ps(PS_INPUT frag_in) : SV_Target
|
||||
{
|
||||
float4 rgba = image.Sample(def_sampler, frag_in.tex, 0);
|
||||
|
||||
rgba.rgb = rgba.rgb * scale_factor;
|
||||
|
||||
return rgba;
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
[Unit]
|
||||
Description=@PROJECT_DESCRIPTION@
|
||||
StartLimitIntervalSec=500
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
ExecStart=@SUNSHINE_EXECUTABLE_PATH@
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
|
||||
2
third-party/ffmpeg-linux-aarch64
vendored
2
third-party/ffmpeg-linux-aarch64
vendored
Submodule third-party/ffmpeg-linux-aarch64 updated: 0341a8fe5a...12dfcbe5f3
2
third-party/ffmpeg-linux-x86_64
vendored
2
third-party/ffmpeg-linux-x86_64
vendored
Submodule third-party/ffmpeg-linux-x86_64 updated: 999e6746bd...09011c45a7
2
third-party/ffmpeg-macos-aarch64
vendored
2
third-party/ffmpeg-macos-aarch64
vendored
Submodule third-party/ffmpeg-macos-aarch64 updated: 3507f2d579...9bc919589b
2
third-party/ffmpeg-macos-x86_64
vendored
2
third-party/ffmpeg-macos-x86_64
vendored
Submodule third-party/ffmpeg-macos-x86_64 updated: 01421e5a14...4f572c8d14
2
third-party/ffmpeg-windows-x86_64
vendored
2
third-party/ffmpeg-windows-x86_64
vendored
Submodule third-party/ffmpeg-windows-x86_64 updated: e0ba0df136...843be4fa79
2
third-party/miniupnp
vendored
2
third-party/miniupnp
vendored
Submodule third-party/miniupnp updated: 207cf440a2...014c9df8ee
2
third-party/moonlight-common-c
vendored
2
third-party/moonlight-common-c
vendored
Submodule third-party/moonlight-common-c updated: ef9ad529a4...07beb0f0a5
1
third-party/nanors
vendored
Submodule
1
third-party/nanors
vendored
Submodule
Submodule third-party/nanors added at 395e5ada44
2
third-party/nv-codec-headers
vendored
2
third-party/nv-codec-headers
vendored
Submodule third-party/nv-codec-headers updated: b550d4042f...2055784e5d
@@ -27,3 +27,12 @@ target_link_libraries(sunshinesvc
|
||||
wtsapi32
|
||||
${PLATFORM_LIBRARIES})
|
||||
target_compile_options(sunshinesvc PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
|
||||
|
||||
add_executable(ddprobe ddprobe.cpp)
|
||||
set_target_properties(ddprobe PROPERTIES CXX_STANDARD 17)
|
||||
target_link_libraries(ddprobe
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
dxgi
|
||||
d3d11
|
||||
${PLATFORM_LIBRARIES})
|
||||
target_compile_options(ddprobe PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
|
||||
|
||||
153
tools/ddprobe.cpp
Normal file
153
tools/ddprobe.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include <d3d11.h>
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
#include <codecvt>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
|
||||
#include "src/utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
namespace dxgi {
|
||||
template<class T>
|
||||
void Release(T *dxgi) {
|
||||
dxgi->Release();
|
||||
}
|
||||
|
||||
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
|
||||
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
|
||||
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
|
||||
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
|
||||
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
|
||||
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
|
||||
|
||||
} // namespace dxgi
|
||||
|
||||
LSTATUS set_gpu_preference(int preference) {
|
||||
// The GPU preferences key uses app path as the value name.
|
||||
WCHAR executable_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, executable_path, ARRAYSIZE(executable_path));
|
||||
|
||||
WCHAR value_data[128];
|
||||
swprintf_s(value_data, L"GpuPreference=%d;", preference);
|
||||
|
||||
auto status = RegSetKeyValueW(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\DirectX\\UserGpuPreferences",
|
||||
executable_path,
|
||||
REG_SZ,
|
||||
value_data,
|
||||
(wcslen(value_data) + 1) * sizeof(WCHAR));
|
||||
if(status != ERROR_SUCCESS) {
|
||||
std::cout << "Failed to set GPU preference: "sv << status << std::endl;
|
||||
return status;
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) {
|
||||
D3D_FEATURE_LEVEL featureLevels[] {
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
D3D_FEATURE_LEVEL_9_2,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
|
||||
dxgi::device_t device;
|
||||
auto status = D3D11CreateDevice(
|
||||
adapter.get(),
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
nullptr,
|
||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
|
||||
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
D3D11_SDK_VERSION,
|
||||
&device,
|
||||
nullptr,
|
||||
nullptr);
|
||||
if(FAILED(status)) {
|
||||
std::cout << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||
return status;
|
||||
}
|
||||
|
||||
dxgi::output1_t output1;
|
||||
status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1);
|
||||
if(FAILED(status)) {
|
||||
std::cout << "Failed to query IDXGIOutput1 from the output"sv << std::endl;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Return the result of DuplicateOutput() to Sunshine
|
||||
dxgi::dup_t dup;
|
||||
return output1->DuplicateOutput((IUnknown *)device.get(), &dup);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
HRESULT status;
|
||||
|
||||
// Display name may be omitted
|
||||
if(argc != 2 && argc != 3) {
|
||||
std::cout << "ddprobe.exe [GPU preference value] [display name]"sv << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::wstring display_name;
|
||||
if(argc == 3) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
display_name = converter.from_bytes(argv[2]);
|
||||
}
|
||||
|
||||
// We must set the GPU preference before making any DXGI/D3D calls
|
||||
status = set_gpu_preference(atoi(argv[1]));
|
||||
if(status != ERROR_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Remove the GPU preference when we're done
|
||||
auto reset_gpu = util::fail_guard([]() {
|
||||
WCHAR tool_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, tool_path, ARRAYSIZE(tool_path));
|
||||
|
||||
RegDeleteKeyValueW(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\DirectX\\UserGpuPreferences",
|
||||
tool_path);
|
||||
});
|
||||
|
||||
dxgi::factory1_t factory;
|
||||
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
|
||||
if(FAILED(status)) {
|
||||
std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||
return status;
|
||||
}
|
||||
|
||||
dxgi::adapter_t::pointer adapter_p {};
|
||||
for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
|
||||
dxgi::adapter_t adapter { adapter_p };
|
||||
|
||||
dxgi::output_t::pointer output_p {};
|
||||
for(int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
|
||||
dxgi::output_t output { output_p };
|
||||
|
||||
DXGI_OUTPUT_DESC desc;
|
||||
output->GetDesc(&desc);
|
||||
|
||||
// If a display name was specified and this one doesn't match, skip it
|
||||
if(!display_name.empty() && desc.DeviceName != display_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this display is not part of the desktop, we definitely can't capture it
|
||||
if(!desc.AttachedToDesktop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We found the matching output. Test it and return the result.
|
||||
return test_dxgi_duplication(adapter, output);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user