diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 8e4bfc76..bd21df29 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,3 +1,4 @@ +--- name: Bug Report description: Create a bug report to help us improve. body: @@ -5,7 +6,7 @@ body: attributes: value: > **THIS IS NOT THE PLACE TO ASK FOR SUPPORT!** - Please use [Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. + Please use [Discord](https://docs.lizardbyte.dev/en/latest/about/support.html#discord) for support issues. - type: textarea id: description attributes: @@ -90,9 +91,12 @@ body: id: logs attributes: label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + description: | + Please copy and paste any relevant log output. This will be automatically formatted into code, + so no need for backticks. render: Shell - type: markdown attributes: value: | - Make sure to close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it. + Make sure to close your issue when it's solved! If you found the solution yourself please comment + so that others benefit from it. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 28669539..ba507895 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1285370b..e3b47ad7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 1b6d70f1..6d0a74a2 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ad690428..c9e6b0df 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,3 +1,4 @@ +--- name: CI on: @@ -52,7 +53,8 @@ jobs: - name: Check CMakeLists.txt Version run: | - version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') + version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | \ + grep -o -E '[0-9]+\.[0-9]+\.[0-9]+') echo "cmakelists_version=${version}" >> $GITHUB_ENV - name: Compare CMakeList.txt Version @@ -60,7 +62,8 @@ jobs: run: | echo CMakeLists version: "$cmakelists_version" echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}" - echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" + echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to \ + "project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]" exit 1 build_linux_aur: @@ -76,7 +79,7 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y \ - cmake + cmake - name: Configure PKGBUILD files run: | @@ -86,47 +89,55 @@ jobs: sub_version="" conflicts="'sunshine'" provides="'sunshine'" - + branch=${GITHUB_HEAD_REF} - + # check the branch variable 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 - aur_pkg=sunshine - conflicts="" - provides="" - - echo "aur_publish=true" >> $GITHUB_ENV - elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then - aur_pkg=sunshine-git - sub_version=".r${commit}" - - echo "aur_publish=true" >> $GITHUB_ENV - fi - else - echo "This is a PR event" - commit=${{ github.event.pull_request.head.sha }} - clone_url=${{ github.event.pull_request.head.repo.clone_url }} - + echo "This is a PUSH event" + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} + + if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then + aur_pkg=sunshine + conflicts="" + provides="" + + echo "aur_publish=true" >> $GITHUB_ENV + elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then + aur_pkg=sunshine-git sub_version=".r${commit}" + + echo "aur_publish=true" >> $GITHUB_ENV + fi + else + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} + + sub_version=".r${commit}" fi echo "Commit: ${commit}" echo "Clone URL: ${clone_url}" - + echo "aur_pkg=${aur_pkg}" >> $GITHUB_ENV - + mkdir -p artifacts mkdir -p build - + cd build - cmake -DSUNSHINE_CONFIGURE_AUR=ON -DSUNSHINE_AUR_PKG=${aur_pkg} -DSUNSHINE_SUB_VERSION=${sub_version} -DSUNSHINE_AUR_CONFLICTS=${conflicts} -DSUNSHINE_AUR_PROVIDES=${provides} -DGITHUB_CLONE_URL=${clone_url} -DGITHUB_COMMIT=${commit} -DSUNSHINE_CONFIGURE_ONLY=ON .. + cmake -DSUNSHINE_CONFIGURE_AUR=ON \ + -DSUNSHINE_AUR_PKG=${aur_pkg} \ + -DSUNSHINE_SUB_VERSION=${sub_version} \ + -DSUNSHINE_AUR_CONFLICTS=${conflicts} \ + -DSUNSHINE_AUR_PROVIDES=${provides} \ + -DGITHUB_CLONE_URL=${clone_url} \ + -DGITHUB_COMMIT=${commit} \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. cd .. - + mv ./build/PKGBUILD ./artifacts/ - name: Validate package @@ -172,42 +183,50 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y \ - cmake \ - flatpak - sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo' - sudo su $(whoami) -c 'flatpak install --user flathub org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y' + cmake \ + flatpak + sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub \ + https://flathub.org/repo/flathub.flatpakrepo' + sudo su $(whoami) -c 'flatpak install --user flathub \ + org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y' - name: Configure Flatpak Manifest run: | # variables for manifest branch=${GITHUB_HEAD_REF} - + # check the branch variable if [ -z "$branch" ] then - echo "This is a PUSH event" - branch=${{ github.ref_name }} - commit=${{ github.sha }} - clone_url=${{ github.event.repository.clone_url }} + echo "This is a PUSH event" + branch=${{ github.ref_name }} + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} else - echo "This is a PR event" - commit=${{ github.event.pull_request.head.sha }} - clone_url=${{ github.event.pull_request.head.repo.clone_url }} + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} fi echo "Branch: ${branch}" echo "Commit: ${commit}" echo "Clone URL: ${clone_url}" - + mkdir -p build mkdir -p artifacts - + cd build - cmake -DGITHUB_CLONE_URL=${clone_url} -DGITHUB_BRANCH=${branch} -DGITHUB_COMMIT=${commit} -DSUNSHINE_CONFIGURE_FLATPAK=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. + cmake -DGITHUB_CLONE_URL=${clone_url} \ + -DGITHUB_BRANCH=${branch} \ + -DGITHUB_COMMIT=${commit} \ + -DSUNSHINE_CONFIGURE_FLATPAK=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. - name: Build Linux Flatpak working-directory: build run: | - sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine dev.lizardbyte.sunshine.yml' + sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine \ + dev.lizardbyte.sunshine.yml' sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak dev.lizardbyte.sunshine' - name: Upload Artifacts @@ -232,7 +251,7 @@ jobs: strategy: fail-fast: false # false to test all, true to fail entire job if any fail matrix: - include: # package these differently + include: # package these differently - type: cpack CMAKE_INSTALL_PREFIX: '/usr' SUNSHINE_ASSETS_DIR: 'local/sunshine/assets' @@ -255,57 +274,60 @@ jobs: sudo add-apt-repository ppa:savoury1/ffmpeg4 -y # sudo add-apt-repository ppa:savoury1/boost-defaults-1.71 -y sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - + sudo apt-get update -y sudo apt-get install -y \ - build-essential \ - cmake \ - gcc-10 \ - git \ - g++-10 \ - libavdevice-dev \ - libboost-filesystem-dev \ - libboost-log-dev \ - libboost-thread-dev \ - libcap-dev \ - libdrm-dev \ - libevdev-dev \ - libpulse-dev \ - libopus-dev \ - libssl-dev \ - libwayland-dev \ - libx11-dev \ - libxcb-shm0-dev \ - libxcb-xfixes0-dev \ - libxcb1-dev \ - libxfixes-dev \ - libxrandr-dev \ - libxtst-dev \ - wget + build-essential \ + cmake \ + gcc-10 \ + git \ + g++-10 \ + libavdevice-dev \ + libboost-filesystem-dev \ + libboost-log-dev \ + libboost-thread-dev \ + libcap-dev \ + libdrm-dev \ + libevdev-dev \ + libpulse-dev \ + libopus-dev \ + libssl-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + wget # # Ubuntu 20.04+ packages # libboost-filesystem-dev # libboost-log-dev # libboost-thread-dev - + # # Ubuntu 18.04 packages # libboost-filesystem1.71-dev \ # libboost-log1.71-dev \ # libboost-regex1.71-dev \ # libboost-thread1.71-dev \ - + # clean apt cache sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* - + # Update gcc alias - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - + sudo update-alternatives --install \ + /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 + # 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 --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run + sudo wget \ + https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run \ + --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run sudo chmod a+x /root/cuda.run sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm sudo rm /root/cuda.run - + # # Install cmake (necessary for 18.04) # wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh # chmod +x cmake-3.22.2-linux-x86_64.sh @@ -318,9 +340,19 @@ jobs: run: | mkdir -p build mkdir -p artifacts - + cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine -DSUNSHINE_ENABLE_WAYLAND=ON -DSUNSHINE_ENABLE_X11=ON -DSUNSHINE_ENABLE_DRM=ON -DSUNSHINE_ENABLE_CUDA=ON ${{ matrix.EXTRA_ARGS }} .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} \ + -DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} \ + -DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + ${{ matrix.EXTRA_ARGS }} \ + .. make -j ${nproc} - name: Package Linux - CPACK @@ -336,7 +368,7 @@ jobs: mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm - name: Set AppImage Version - if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} + if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} # yamllint disable-line rule:line-length run: | version=${{ needs.check_changelog.outputs.next_version_bare }} echo "VERSION=${version}" >> $GITHUB_ENV @@ -347,43 +379,44 @@ jobs: run: | # install sunshine to the DESTDIR make install DESTDIR=AppDir - + # portable home and config # todo - this is ugly... we should use a custom AppRun script to take care of this mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/ mkdir -p ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }} - cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ - + cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json \ + ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/ + # variables DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}" ICON_FILE="${ICON_FILE:-sunshine.png}" - + # AppImage # https://docs.appimage.org/packaging-guide/index.html wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage - + # # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk # sudo apt-get install libgtk-3-dev librsvg2-dev -y # wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh # chmod +x linuxdeploy-plugin-gtk.sh # export DEPLOY_GTK_VERSION=3 - + ./linuxdeploy-x86_64.AppImage \ - --appdir ./AppDir \ - --executable ./sunshine \ - --icon-file "../$ICON_FILE" \ - --desktop-file "./$DESKTOP_FILE" \ - --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ - --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ - --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ - --output appimage + --appdir ./AppDir \ + --executable ./sunshine \ + --icon-file "../$ICON_FILE" \ + --desktop-file "./$DESKTOP_FILE" \ + --library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \ + --library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \ + --output appimage # # add this argument back if using gtk plugin # --plugin gtk \ # move mv Sunshine*.AppImage ../artifacts/sunshine.AppImage - + # permissions chmod +x ../artifacts/sunshine.AppImage @@ -392,9 +425,9 @@ jobs: run: | wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage chmod +x appimagelint-x86_64.AppImage - + # rm -rf ~/.cache/appimagelint/ - + ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - name: Archive AppImage @@ -402,7 +435,7 @@ jobs: working-directory: artifacts run: | chmod +x ./sunshine.AppImage - + zip --recurse-paths --move --test ./sunshine-appimage.zip ./* - name: Upload Artifacts @@ -443,7 +476,11 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DSUNSHINE_ASSETS_DIR=local/sunshine/assets -DSUNSHINE_CONFIG_DIR=local/sunshine/config .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=local/sunshine/assets \ + -DSUNSHINE_CONFIG_DIR=local/sunshine/config \ + .. make -j ${nproc} - name: Package MacOS @@ -454,10 +491,10 @@ jobs: # package cpack -G DragNDrop mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-dragndrop.dmg - + cpack -G Bundle mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-bundle.dmg - + cpack -G ZIP mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip @@ -476,25 +513,20 @@ jobs: rm -f ./sunshine-macos-experimental-bundle.dmg rm -f ./sunshine-macos-experimental-archive.zip - # no artifacts to release currently -# - name: Create Release -# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} -# uses: LizardByte/.github/actions/create_release@master -# with: -# token: ${{ secrets.GH_BOT_TOKEN }} -# next_version: ${{ needs.check_changelog.outputs.next_version }} -# last_version: ${{ needs.check_changelog.outputs.last_version }} -# release_body: ${{ needs.check_changelog.outputs.release_body }} + ## no artifacts to release currently + # - name: Create Release + # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + # uses: LizardByte/.github/actions/create_release@master + # with: + # token: ${{ secrets.GH_BOT_TOKEN }} + # next_version: ${{ needs.check_changelog.outputs.next_version }} + # last_version: ${{ needs.check_changelog.outputs.last_version }} + # release_body: ${{ needs.check_changelog.outputs.release_body }} build_mac_port: name: Macports needs: check_changelog runs-on: macos-11 -# runs-on: ${{ matrix.os }} -# strategy: -# fail-fast: false -# matrix: -# os: [ macos-10.15, macos-11, macos-12 ] steps: - name: Checkout @@ -522,42 +554,45 @@ jobs: run: | # variables for Portfile branch=${GITHUB_HEAD_REF} - + # check the branch variable if [ -z "$branch" ] then - echo "This is a PUSH event" - commit=${{ github.sha }} - clone_url=${{ github.event.repository.clone_url }} + echo "This is a PUSH event" + commit=${{ github.sha }} + clone_url=${{ github.event.repository.clone_url }} else - echo "This is a PR event" - commit=${{ github.event.pull_request.head.sha }} - clone_url=${{ github.event.pull_request.head.repo.clone_url }} + echo "This is a PR event" + commit=${{ github.event.pull_request.head.sha }} + clone_url=${{ github.event.pull_request.head.repo.clone_url }} fi echo "Commit: ${commit}" echo "Clone URL: ${clone_url}" - + mkdir build cd build - cmake -DGITHUB_COMMIT=${commit} -DGITHUB_CLONE_URL=${clone_url} -DSUNSHINE_CONFIGURE_PORTFILE=ON -DSUNSHINE_CONFIGURE_ONLY=ON .. - + cmake -DGITHUB_COMMIT=${commit} \ + -DGITHUB_CLONE_URL=${clone_url} \ + -DSUNSHINE_CONFIGURE_PORTFILE=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ + .. cd .. - + # copy Portfile to artifacts mkdir -p artifacts cp -f ./build/Portfile ./artifacts/ - + # copy Portfile to ports mkdir -p ./ports/multimedia/Sunshine cp -f ./build/Portfile ./ports/multimedia/Sunshine/Portfile - + # testing cat ./artifacts/Portfile - name: Bootstrap MacPorts run: | . ports/.github/workflows/bootstrap.sh - + # Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps. echo "/opt/mports/bin" >> $GITHUB_PATH echo "${PWD}/mpbb" >> $GITHUB_PATH @@ -573,12 +608,12 @@ jobs: echo "Listing subports for Sunshine" new_subports=$(mpbb \ - --work-dir /tmp/mpbb \ - list-subports \ - --archive-site= \ - --archive-site-private= \ - --include-deps=no \ - "$port" \ + --work-dir /tmp/mpbb \ + list-subports \ + --archive-site= \ + --archive-site-private= \ + --include-deps=no \ + "$port" \ | tr '\n' ' ') for subport in $new_subports; do echo "$subport" @@ -671,14 +706,14 @@ jobs: work=$(port work sunshine) echo "Sunshine port work directory: ${work}" - + # move components out of port work directory sudo mv ${work}/Sunshine*component.pkg /tmp/ - + # copy artifacts sudo mv ${work}/Sunshine*.pkg ./artifacts/sunshine.pkg sudo mv ${work}/Sunshine*.dmg ./artifacts/sunshine.dmg - + # move components back # sudo mv /tmp/Sunshine*component.pkg ${work}/ @@ -733,7 +768,11 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DSUNSHINE_ASSETS_DIR=assets -DSUNSHINE_CONFIG_DIR=config -G "MinGW Makefiles" .. + cmake -DCMAKE_BUILD_TYPE=Release \ + -DSUNSHINE_ASSETS_DIR=assets \ + -DSUNSHINE_CONFIG_DIR=config \ + -G "MinGW Makefiles" \ + .. mingw32-make -j2 - name: Package Windows diff --git a/.github/workflows/auto-create-pr.yml b/.github/workflows/auto-create-pr.yml index ef32f2b5..ef19e40f 100644 --- a/.github/workflows/auto-create-pr.yml +++ b/.github/workflows/auto-create-pr.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. @@ -5,15 +6,12 @@ name: Auto create PR on: - pull_request: - types: - - closed + push: branches: - 'nightly' jobs: create_pr: - if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: @@ -25,7 +23,7 @@ jobs: with: source_branch: "" # should be "nightly" as it's the triggering branch destination_branch: "master" - pr_title: "Pulling ${{ github.ref }} into master" + pr_title: "Pulling ${{ github.ref_name }} into master" pr_template: ".github/pr_release_template.md" pr_assignee: "${{ secrets.GH_BOT_NAME }}" pr_draft: true diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index da7b4b03..7ff83e0a 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. @@ -17,22 +18,22 @@ jobs: contains(fromJson('["LizardByte-bot"]'), github.actor) runs-on: ubuntu-latest steps: - - name: Autoapproving - uses: hmarr/auto-approve-action@v2 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Autoapproving + uses: hmarr/auto-approve-action@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Label autoapproved - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GH_BOT_TOKEN }} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['autoapproved', 'autoupdate'] - }) + - name: Label autoapproved + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_BOT_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['autoapproved', 'autoupdate'] + }) automerge: needs: [autoapprove] diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml new file mode 100644 index 00000000..f32e65c7 --- /dev/null +++ b/.github/workflows/autoupdate.yml @@ -0,0 +1,32 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# 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 designed to work with: +# - automerge workflows + +# It uses GitHub 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. + +name: autoupdate + +on: + push: + branches: + - 'nightly' + +jobs: + autoupdate-for-bot: + name: Autoupdate autoapproved PR created in the upstream + if: startsWith(github.repository, 'LizardByte/') + runs-on: ubuntu-latest + steps: + - name: Update + uses: docker://chinthakagodawita/autoupdate-action:v1 + env: + GITHUB_TOKEN: '${{ secrets.GH_BOT_TOKEN }}' + PR_FILTER: "labelled" + PR_LABELS: "autoupdate" + PR_READY_STATE: "ready_for_review" + MERGE_CONFLICT_ACTION: "ignore" diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml deleted file mode 100644 index 3c0a70e6..00000000 --- a/.github/workflows/clang.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: clang-format-lint - -on: - pull_request: - branches: [master, nightly] - types: [opened, synchronize, reopened] - -jobs: - lint: - name: Clang Format Lint - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Clang format lint - uses: DoozyX/clang-format-lint-action@v0.14 - with: - source: './sunshine' - extensions: 'cpp,h,m,mm' - clangFormatVersion: 13 - style: file - inplace: false - - - name: Upload Artifacts - if: failure() - uses: actions/upload-artifact@v3 - with: - name: sunshine - path: sunshine/ diff --git a/.github/workflows/cpp-clang-format-lint.yml b/.github/workflows/cpp-clang-format-lint.yml new file mode 100644 index 00000000..4717f70a --- /dev/null +++ b/.github/workflows/cpp-clang-format-lint.yml @@ -0,0 +1,60 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# 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. + +name: Clang Format Lint + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + check_src: + name: Check src + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check + id: check + run: | + if [ -d "./src" ] + then + FOUND=true + else + FOUND=false + fi + + echo "::set-output name=src::${FOUND}" + + outputs: + src: ${{ steps.check.outputs.src }} + + lint: + name: Clang Format Lint + needs: [check_src] + if: ${{ needs.check_src.outputs.src == 'true' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Clang format lint + uses: DoozyX/clang-format-lint-action@v0.14 + with: + source: './src' + extensions: 'cpp,h,m,mm' + clangFormatVersion: 13 + style: file + inplace: false + + - name: Upload Artifacts + if: failure() + uses: actions/upload-artifact@v3 + with: + name: clang-format-fixes + path: src/ diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 57b11129..225b07de 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 9f4a08db..f89975f9 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. @@ -8,7 +9,7 @@ on: issues: types: [labeled, unlabeled] discussion: - types: [ labeled, unlabeled ] + types: [labeled, unlabeled] jobs: label: diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index d48ab223..79674aa4 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -1,3 +1,4 @@ +--- name: localize on: @@ -5,7 +6,7 @@ on: branches: [nightly] paths: # prevents workflow from running unless these files change - '.github/workflows/localize.yml' - - 'sunshine/**' + - 'src/**' - 'locale/sunshine.po' workflow_dispatch: @@ -18,76 +19,77 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - name: Install Python 3.9 - uses: actions/setup-python@v4 # https://github.com/actions/setup-python - with: - python-version: '3.9' + - name: Install Python 3.9 + uses: actions/setup-python@v4 # https://github.com/actions/setup-python + with: + python-version: '3.9' - - name: Set up Python 3.9 Dependencies - run: | - cd ./scripts - python -m pip install --upgrade pip setuptools - python -m pip install -r requirements.txt + - name: Set up Python 3.9 Dependencies + run: | + cd ./scripts + python -m pip install --upgrade pip setuptools + python -m pip install -r requirements.txt - - name: Set up xgettext - run: | - sudo apt-get update -y && \ - sudo apt-get --reinstall install -y \ - gettext + - name: Set up xgettext + run: | + sudo apt-get update -y && \ + sudo apt-get --reinstall install -y \ + gettext - - name: Update Strings - run: | - # first, try to remove existing file as xgettext does not remove unused translations - if [ -f "${{ env.file }}" ]; - then - rm ${{ env.file }} - echo "new_file=false" >> $GITHUB_ENV - else - echo "new_file=true" >> $GITHUB_ENV - fi + - name: Update Strings + run: | + # first, try to remove existing file as xgettext does not remove unused translations + if [ -f "${{ env.file }}" ]; + then + rm ${{ env.file }} + echo "new_file=false" >> $GITHUB_ENV + else + echo "new_file=true" >> $GITHUB_ENV + fi - # extract the new strings - python ./scripts/_locale.py --extract + # extract the new strings + python ./scripts/_locale.py --extract - - name: git diff - if: ${{ env.new_file == 'false' }} - run: | - # disable the pager - git config --global pager.diff false + - name: git diff + if: ${{ env.new_file == 'false' }} + run: | + # disable the pager + git config --global pager.diff false - # print the git diff - git diff locale/sunshine.po + # print the git diff + git diff locale/sunshine.po - # set the variable with minimal output - OUTPUT=$(git diff --numstat locale/sunshine.po) - echo "git_diff=${OUTPUT}" >> $GITHUB_ENV + # set the variable with minimal output + OUTPUT=$(git diff --numstat locale/sunshine.po) + echo "git_diff=${OUTPUT}" >> $GITHUB_ENV - - name: git reset - # only run if a single line changed (date/time) and file already existed - if: ${{ env.git_diff == '1 1 locale/sunshine.po' && env.new_file == 'false' }} - run: | - git reset --hard + - name: git reset + # only run if a single line changed (date/time) and file already existed + # \t in next line is a tab character + if: ${{ env.git_diff == '1\t1\tlocale/sunshine.po' && env.new_file == 'false' }} + run: | + git reset --hard - - name: Create/Update Pull Request - uses: peter-evans/create-pull-request@v4 - with: - add-paths: | - locale/*.po - token: ${{ secrets.GH_BOT_TOKEN }} # must trigger PR tests - commit-message: New localization template - branch: localize/update - delete-branch: true - base: nightly - title: New Babel Updates - body: | - Update report - - Updated with *today's* date - - Auto-generated by [create-pull-request][1] + - name: Create/Update Pull Request + uses: peter-evans/create-pull-request@v4 + with: + add-paths: | + locale/*.po + token: ${{ secrets.GH_BOT_TOKEN }} # must trigger PR tests + commit-message: New localization template + branch: localize/update + delete-branch: true + base: nightly + title: New Babel Updates + body: | + Update report + - Updated with *today's* date + - Auto-generated by [create-pull-request][1] - [1]: https://github.com/peter-evans/create-pull-request - labels: | - babel - l10n + [1]: https://github.com/peter-evans/create-pull-request + labels: | + babel + l10n diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 99543875..0abc26b8 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. @@ -18,7 +19,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: target: master - exclude: nightly # Don't prevent going from nightly -> master + exclude: nightly # Don't prevent going from nightly -> master change-to: nightly comment: | Your PR was set to `master`, PRs should be sent to `nightly`. diff --git a/.github/workflows/python-flake8.yml b/.github/workflows/python-flake8.yml index 8769016f..463fb8a2 100644 --- a/.github/workflows/python-flake8.yml +++ b/.github/workflows/python-flake8.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. diff --git a/.github/workflows/release-notifier.yml b/.github/workflows/release-notifier.yml index 6b33643b..7a2c13ea 100644 --- a/.github/workflows/release-notifier.yml +++ b/.github/workflows/release-notifier.yml @@ -1,3 +1,4 @@ +--- # This action is centrally managed in https://github.com//.github/ # 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. diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index d1cd5166..0cf5b371 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -1,14 +1,17 @@ -name: Publish to WinGet - -on: - release: - types: [released] - -jobs: - publish: - runs-on: windows-latest - steps: - - uses: vedantmgoyal2009/winget-releaser@latest - with: - identifier: LizardByte.Sunshine - token: ${{ secrets.GH_BOT_TOKEN }} +--- +name: Publish to WinGet + +on: + release: + types: [released] + +jobs: + winget-releaser: + name: winget releaser + runs-on: windows-latest + steps: + - name: winget releaser + uses: vedantmgoyal2009/winget-releaser@latest + with: + identifier: LizardByte.Sunshine + token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml new file mode 100644 index 00000000..83de6c23 --- /dev/null +++ b/.github/workflows/yaml-lint.yml @@ -0,0 +1,46 @@ +--- +# This action is centrally managed in https://github.com//.github/ +# 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. + +name: yaml lint + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + +jobs: + yaml-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: yaml lint + id: yaml-lint + uses: ibiqlik/action-yamllint@v3 + with: + # https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration + config_data: | + extends: default + rules: + comments: + level: error + line-length: + max: 120 + truthy: + allowed-values: ['true', 'false', 'on'] # GitHub uses "on" for workflow event triggers + check-keys: true + level: error + + - name: Log + run: | + echo ${{ steps.yaml-lint.outputs.logfile }} + + - name: Upload logs + uses: actions/upload-artifact@v2 + if: failure() + with: + name: yamllint-logfile + path: ${{ steps.yaml-lint.outputs.logfile }} diff --git a/.gitmodules b/.gitmodules index 814f3409..1b60acde 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,6 @@ [submodule "third-party/nv-codec-headers"] path = third-party/nv-codec-headers url = https://github.com/FFmpeg/nv-codec-headers -[submodule "sunshine/platform/macos/TPCircularBuffer"] - path = sunshine/platform/macos/TPCircularBuffer +[submodule "third-party/TPCircularBuffer"] + path = third-party/TPCircularBuffer url = https://github.com/michaeltyson/TPCircularBuffer diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 762371f8..156e5624 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,3 +1,4 @@ +--- # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details @@ -25,9 +26,9 @@ build: # - cmake . ## Include the submodules, required for cmake -#submodules: -# include: all -# recursive: true +# submodules: +# include: all +# recursive: true # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e94d53a..4b5d28e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,18 +100,18 @@ if(WIN32) if(NOT DEFINED SUNSHINE_ICON_PATH) set(SUNSHINE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sunshine.ico") endif() - configure_file(sunshine/platform/windows/windows.rs.in windows.rc @ONLY) + configure_file(src/platform/windows/windows.rs.in windows.rc @ONLY) set(PLATFORM_TARGET_FILES "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" - sunshine/platform/windows/publish.cpp - sunshine/platform/windows/misc.h - sunshine/platform/windows/misc.cpp - sunshine/platform/windows/input.cpp - sunshine/platform/windows/display.h - sunshine/platform/windows/display_base.cpp - sunshine/platform/windows/display_vram.cpp - sunshine/platform/windows/display_ram.cpp - sunshine/platform/windows/audio.cpp + src/platform/windows/publish.cpp + src/platform/windows/misc.h + src/platform/windows/misc.cpp + src/platform/windows/input.cpp + src/platform/windows/display.h + src/platform/windows/display_base.cpp + src/platform/windows/display_vram.cpp + src/platform/windows/display_ram.cpp + src/platform/windows/audio.cpp third-party/ViGEmClient/src/ViGEmClient.cpp third-party/ViGEmClient/include/ViGEm/Client.h third-party/ViGEmClient/include/ViGEm/Common.h @@ -180,21 +180,21 @@ elseif(APPLE) set(APPLE_PLIST_FILE ${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist) set(PLATFORM_TARGET_FILES - sunshine/platform/macos/av_audio.h - sunshine/platform/macos/av_audio.m - sunshine/platform/macos/av_img_t.h - sunshine/platform/macos/av_video.h - sunshine/platform/macos/av_video.m - sunshine/platform/macos/display.mm - sunshine/platform/macos/input.cpp - sunshine/platform/macos/microphone.mm - sunshine/platform/macos/misc.cpp - sunshine/platform/macos/misc.h - sunshine/platform/macos/nv12_zero_device.cpp - sunshine/platform/macos/nv12_zero_device.h - sunshine/platform/macos/publish.cpp - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.c - sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h + src/platform/macos/av_audio.h + src/platform/macos/av_audio.m + src/platform/macos/av_img_t.h + src/platform/macos/av_video.h + src/platform/macos/av_video.m + src/platform/macos/display.mm + src/platform/macos/input.cpp + src/platform/macos/microphone.mm + src/platform/macos/misc.cpp + src/platform/macos/misc.h + src/platform/macos/nv12_zero_device.cpp + src/platform/macos/nv12_zero_device.h + src/platform/macos/publish.cpp + third-party/TPCircularBuffer/TPCircularBuffer.c + third-party/TPCircularBuffer/TPCircularBuffer.h ${APPLE_PLIST_FILE}) else() add_compile_definitions(SUNSHINE_PLATFORM="linux") @@ -242,14 +242,14 @@ else() if(X11_FOUND) add_compile_definitions(SUNSHINE_BUILD_X11) include_directories(${X11_INCLUDE_DIR}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/x11grab.cpp) endif() if(CUDA_FOUND) include_directories(third-party/nvfbc) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/cuda.cu - sunshine/platform/linux/cuda.cpp + src/platform/linux/cuda.cu + src/platform/linux/cuda.cpp third-party/nvfbc/NvFBC.h) add_compile_definitions(SUNSHINE_BUILD_CUDA) @@ -259,7 +259,7 @@ else() add_compile_definitions(SUNSHINE_BUILD_DRM) include_directories(${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS}) list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/kmsgrab.cpp) + list(APPEND PLATFORM_TARGET_FILES src/platform/linux/kmsgrab.cpp) list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) elseif(LIBDRM_FOUND) message(WARNING "Found libdrm, yet there is no libcap") @@ -301,26 +301,26 @@ else() list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/wlgrab.cpp - sunshine/platform/linux/wayland.cpp) + src/platform/linux/wlgrab.cpp + src/platform/linux/wayland.cpp) endif() if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND} AND NOT ${}) message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)") endif() list(APPEND PLATFORM_TARGET_FILES - sunshine/platform/linux/publish.cpp - sunshine/platform/linux/vaapi.h - sunshine/platform/linux/vaapi.cpp - sunshine/platform/linux/cuda.h - sunshine/platform/linux/graphics.h - sunshine/platform/linux/graphics.cpp - sunshine/platform/linux/misc.h - sunshine/platform/linux/misc.cpp - sunshine/platform/linux/audio.cpp - sunshine/platform/linux/input.cpp - sunshine/platform/linux/x11grab.h - sunshine/platform/linux/wayland.h + src/platform/linux/publish.cpp + src/platform/linux/vaapi.h + src/platform/linux/vaapi.cpp + src/platform/linux/cuda.h + src/platform/linux/graphics.h + src/platform/linux/graphics.cpp + src/platform/linux/misc.h + src/platform/linux/misc.cpp + src/platform/linux/audio.cpp + src/platform/linux/input.cpp + src/platform/linux/x11grab.h + src/platform/linux/wayland.h third-party/glad/src/egl.c third-party/glad/src/gl.c third-party/glad/include/EGL/eglplatform.h @@ -356,47 +356,47 @@ set(SUNSHINE_TARGET_FILES third-party/moonlight-common-c/src/Rtsp.h third-party/moonlight-common-c/src/RtspParser.c third-party/moonlight-common-c/src/Video.h - sunshine/upnp.cpp - sunshine/upnp.h - sunshine/cbs.cpp - sunshine/utility.h - sunshine/uuid.h - sunshine/config.h - sunshine/config.cpp - sunshine/main.cpp - sunshine/main.h - sunshine/crypto.cpp - sunshine/crypto.h - sunshine/nvhttp.cpp - sunshine/nvhttp.h - sunshine/httpcommon.cpp - sunshine/httpcommon.h - sunshine/confighttp.cpp - sunshine/confighttp.h - sunshine/rtsp.cpp - sunshine/rtsp.h - sunshine/stream.cpp - sunshine/stream.h - sunshine/video.cpp - sunshine/video.h - sunshine/input.cpp - sunshine/input.h - sunshine/audio.cpp - sunshine/audio.h - sunshine/platform/common.h - sunshine/process.cpp - sunshine/process.h - sunshine/network.cpp - sunshine/network.h - sunshine/move_by_copy.h - sunshine/task_pool.h - sunshine/thread_pool.h - sunshine/thread_safe.h - sunshine/sync.h - sunshine/round_robin.h + src/upnp.cpp + src/upnp.h + src/cbs.cpp + src/utility.h + src/uuid.h + src/config.h + src/config.cpp + src/main.cpp + src/main.h + src/crypto.cpp + src/crypto.h + src/nvhttp.cpp + src/nvhttp.h + src/httpcommon.cpp + src/httpcommon.h + src/confighttp.cpp + src/confighttp.h + src/rtsp.cpp + src/rtsp.h + src/stream.cpp + src/stream.h + src/video.cpp + src/video.h + src/input.cpp + src/input.h + src/audio.cpp + src/audio.h + src/platform/common.h + src/process.cpp + src/process.h + src/network.cpp + src/network.h + src/move_by_copy.h + src/task_pool.h + src/thread_pool.h + src/thread_safe.h + src/sync.h + src/round_robin.h ${PLATFORM_TARGET_FILES}) -set_source_files_properties(sunshine/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) +set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} @@ -414,7 +414,7 @@ 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(sunshine/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) + set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) endif() else() add_definitions(-DNDEBUG) diff --git a/crowdin.yml b/crowdin.yml index ca9c8c5c..0be504ba 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,4 @@ +--- "base_path": "." "base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) "preserve_hierarchy": false # flatten tree on crowdin @@ -6,10 +7,10 @@ "l10n" ] -"files" : [ +"files": [ { - "source" : "/locale/*.po", - "translation" : "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", + "source": "/locale/*.po", + "translation": "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", "languages_mapping": { "two_letters_code": { # map non-two letter codes here, left side is crowdin designation, right side is babel designation diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 32ec4a5e..8b04867f 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -67,7 +67,7 @@ any of the following paths are modified. .. code-block:: yaml - - 'sunshine/**' + - 'src/**' When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 15fdb1f2..04d3df5a 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -1,3 +1,4 @@ +--- app-id: dev.lizardbyte.sunshine runtime: org.freedesktop.Platform runtime-version: "21.08" @@ -35,7 +36,7 @@ modules: - '*' 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 + - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR # yamllint disable-line rule:line-length - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 - rm ./cuda.run sources: @@ -48,7 +49,7 @@ modules: - 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 + 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 @@ -56,7 +57,7 @@ modules: buildsystem: simple build-commands: - ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log - - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS + - ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length sources: - type: archive url: https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2 @@ -170,7 +171,7 @@ modules: - /bin sources: - type: archive - url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz + url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz # yamllint disable-line rule:line-length sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - name: libevdev @@ -210,6 +211,6 @@ modules: - -DSUNSHINE_ENABLE_CUDA=ON sources: - type: git - url: @GITHUB_CLONE_URL@ - branch: @GITHUB_BRANCH@ - commit: @GITHUB_COMMIT@ + url: '@GITHUB_CLONE_URL@' + branch: '@GITHUB_BRANCH@' + commit: '@GITHUB_COMMIT@' diff --git a/sunshine/audio.cpp b/src/audio.cpp similarity index 100% rename from sunshine/audio.cpp rename to src/audio.cpp diff --git a/sunshine/audio.h b/src/audio.h similarity index 100% rename from sunshine/audio.h rename to src/audio.h diff --git a/sunshine/cbs.cpp b/src/cbs.cpp similarity index 100% rename from sunshine/cbs.cpp rename to src/cbs.cpp diff --git a/sunshine/cbs.h b/src/cbs.h similarity index 100% rename from sunshine/cbs.h rename to src/cbs.h diff --git a/sunshine/config.cpp b/src/config.cpp similarity index 100% rename from sunshine/config.cpp rename to src/config.cpp diff --git a/sunshine/config.h b/src/config.h similarity index 100% rename from sunshine/config.h rename to src/config.h diff --git a/sunshine/confighttp.cpp b/src/confighttp.cpp similarity index 100% rename from sunshine/confighttp.cpp rename to src/confighttp.cpp diff --git a/sunshine/confighttp.h b/src/confighttp.h similarity index 100% rename from sunshine/confighttp.h rename to src/confighttp.h diff --git a/sunshine/crypto.cpp b/src/crypto.cpp similarity index 100% rename from sunshine/crypto.cpp rename to src/crypto.cpp diff --git a/sunshine/crypto.h b/src/crypto.h similarity index 100% rename from sunshine/crypto.h rename to src/crypto.h diff --git a/sunshine/httpcommon.cpp b/src/httpcommon.cpp similarity index 100% rename from sunshine/httpcommon.cpp rename to src/httpcommon.cpp diff --git a/sunshine/httpcommon.h b/src/httpcommon.h similarity index 100% rename from sunshine/httpcommon.h rename to src/httpcommon.h diff --git a/sunshine/input.cpp b/src/input.cpp similarity index 100% rename from sunshine/input.cpp rename to src/input.cpp diff --git a/sunshine/input.h b/src/input.h similarity index 100% rename from sunshine/input.h rename to src/input.h diff --git a/sunshine/main.cpp b/src/main.cpp similarity index 100% rename from sunshine/main.cpp rename to src/main.cpp diff --git a/sunshine/main.h b/src/main.h similarity index 100% rename from sunshine/main.h rename to src/main.h diff --git a/sunshine/move_by_copy.h b/src/move_by_copy.h similarity index 100% rename from sunshine/move_by_copy.h rename to src/move_by_copy.h diff --git a/sunshine/network.cpp b/src/network.cpp similarity index 100% rename from sunshine/network.cpp rename to src/network.cpp diff --git a/sunshine/network.h b/src/network.h similarity index 100% rename from sunshine/network.h rename to src/network.h diff --git a/sunshine/nvhttp.cpp b/src/nvhttp.cpp similarity index 100% rename from sunshine/nvhttp.cpp rename to src/nvhttp.cpp diff --git a/sunshine/nvhttp.h b/src/nvhttp.h similarity index 100% rename from sunshine/nvhttp.h rename to src/nvhttp.h diff --git a/sunshine/platform/common.h b/src/platform/common.h similarity index 99% rename from sunshine/platform/common.h rename to src/platform/common.h index ef9a2cf6..60ae9d14 100644 --- a/sunshine/platform/common.h +++ b/src/platform/common.h @@ -11,8 +11,8 @@ #include #include -#include "sunshine/thread_safe.h" -#include "sunshine/utility.h" +#include "src/thread_safe.h" +#include "src/utility.h" struct sockaddr; struct AVFrame; diff --git a/sunshine/platform/linux/audio.cpp b/src/platform/linux/audio.cpp similarity index 99% rename from sunshine/platform/linux/audio.cpp rename to src/platform/linux/audio.cpp index 2770e515..d3ac03f9 100644 --- a/sunshine/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -10,11 +10,11 @@ #include #include -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/thread_safe.h" +#include "src/config.h" +#include "src/main.h" +#include "src/thread_safe.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp similarity index 99% rename from sunshine/platform/linux/cuda.cpp rename to src/platform/linux/cuda.cpp index c907ae6c..faef891d 100644 --- a/sunshine/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -11,8 +11,8 @@ extern "C" { #include "cuda.h" #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/utility.h" #include "wayland.h" #define SUNSHINE_STRINGVIEW_HELPER(x) x##sv diff --git a/sunshine/platform/linux/cuda.cu b/src/platform/linux/cuda.cu similarity index 100% rename from sunshine/platform/linux/cuda.cu rename to src/platform/linux/cuda.cu diff --git a/sunshine/platform/linux/cuda.h b/src/platform/linux/cuda.h similarity index 100% rename from sunshine/platform/linux/cuda.h rename to src/platform/linux/cuda.h diff --git a/sunshine/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp similarity index 99% rename from sunshine/platform/linux/graphics.cpp rename to src/platform/linux/graphics.cpp index 8d31e37b..3c0adbda 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -1,5 +1,5 @@ #include "graphics.h" -#include "sunshine/video.h" +#include "src/video.h" #include diff --git a/sunshine/platform/linux/graphics.h b/src/platform/linux/graphics.h similarity index 98% rename from sunshine/platform/linux/graphics.h rename to src/platform/linux/graphics.h index 2cc24b01..c566b48f 100644 --- a/sunshine/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -8,9 +8,9 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" #define SUNSHINE_STRINGIFY_HELPER(x) #x #define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x) diff --git a/sunshine/platform/linux/input.cpp b/src/platform/linux/input.cpp similarity index 99% rename from sunshine/platform/linux/input.cpp rename to src/platform/linux/input.cpp index be923936..c8e5887e 100644 --- a/sunshine/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -9,11 +9,11 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" // Support older versions #ifndef REL_HWHEEL_HI_RES diff --git a/sunshine/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp similarity index 99% rename from sunshine/platform/linux/kmsgrab.cpp rename to src/platform/linux/kmsgrab.cpp index 28ef656e..3b9257be 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -8,10 +8,10 @@ #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" // Cursor rendering support through x11 #include "graphics.h" diff --git a/sunshine/platform/linux/misc.cpp b/src/platform/linux/misc.cpp similarity index 99% rename from sunshine/platform/linux/misc.cpp rename to src/platform/linux/misc.cpp index 6d7643c0..36896dd9 100644 --- a/sunshine/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -11,8 +11,8 @@ #include "misc.h" #include "vaapi.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" #ifdef __GNUC__ #define SUNSHINE_GNUC_EXTENSION __extension__ diff --git a/sunshine/platform/linux/misc.h b/src/platform/linux/misc.h similarity index 94% rename from sunshine/platform/linux/misc.h rename to src/platform/linux/misc.h index 432aa9ed..515087d7 100644 --- a/sunshine/platform/linux/misc.h +++ b/src/platform/linux/misc.h @@ -4,7 +4,7 @@ #include #include -#include "sunshine/utility.h" +#include "src/utility.h" KITTY_USING_MOVE_T(file_t, int, -1, { if(el >= 0) { diff --git a/sunshine/platform/linux/publish.cpp b/src/platform/linux/publish.cpp similarity index 99% rename from sunshine/platform/linux/publish.cpp rename to src/platform/linux/publish.cpp index 825f3740..19cbb3f5 100644 --- a/sunshine/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp similarity index 99% rename from sunshine/platform/linux/vaapi.cpp rename to src/platform/linux/vaapi.cpp index 37a64cb8..9dcb2dfe 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -9,10 +9,10 @@ extern "C" { #include "graphics.h" #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/linux/vaapi.h b/src/platform/linux/vaapi.h similarity index 95% rename from sunshine/platform/linux/vaapi.h rename to src/platform/linux/vaapi.h index ddb5c61e..27c97a7f 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/src/platform/linux/vaapi.h @@ -2,7 +2,7 @@ #define SUNSHINE_VAAPI_H #include "misc.h" -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace egl { struct surface_descriptor_t; diff --git a/sunshine/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp similarity index 98% rename from sunshine/platform/linux/wayland.cpp rename to src/platform/linux/wayland.cpp index 9b65ef8d..dace461c 100644 --- a/sunshine/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -4,10 +4,10 @@ #include #include "graphics.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/round_robin.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/round_robin.h" +#include "src/utility.h" #include "wayland.h" extern const wl_interface wl_output_interface; diff --git a/sunshine/platform/linux/wayland.h b/src/platform/linux/wayland.h similarity index 100% rename from sunshine/platform/linux/wayland.h rename to src/platform/linux/wayland.h diff --git a/sunshine/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp similarity index 99% rename from sunshine/platform/linux/wlgrab.cpp rename to src/platform/linux/wlgrab.cpp index c4ab3dbf..31d5cfa0 100644 --- a/sunshine/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -1,6 +1,6 @@ -#include "sunshine/platform/common.h" +#include "src/platform/common.h" -#include "sunshine/main.h" +#include "src/main.h" #include "vaapi.h" #include "wayland.h" diff --git a/sunshine/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp similarity index 99% rename from sunshine/platform/linux/x11grab.cpp rename to src/platform/linux/x11grab.cpp index c9b4beaa..c024f4d8 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -2,7 +2,7 @@ // Created by loki on 6/21/19. // -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include @@ -16,9 +16,9 @@ #include #include -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/task_pool.h" +#include "src/config.h" +#include "src/main.h" +#include "src/task_pool.h" #include "cuda.h" #include "graphics.h" diff --git a/sunshine/platform/linux/x11grab.h b/src/platform/linux/x11grab.h similarity index 94% rename from sunshine/platform/linux/x11grab.h rename to src/platform/linux/x11grab.h index 9fde2664..801daad5 100644 --- a/sunshine/platform/linux/x11grab.h +++ b/src/platform/linux/x11grab.h @@ -3,8 +3,8 @@ #include -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/platform/common.h" +#include "src/utility.h" // X11 Display extern "C" struct _XDisplay; diff --git a/sunshine/platform/macos/av_audio.h b/src/platform/macos/av_audio.h similarity index 91% rename from sunshine/platform/macos/av_audio.h rename to src/platform/macos/av_audio.h index 8c04e5bb..2a0b5a43 100644 --- a/sunshine/platform/macos/av_audio.h +++ b/src/platform/macos/av_audio.h @@ -3,7 +3,7 @@ #import -#include "sunshine/platform/macos/TPCircularBuffer/TPCircularBuffer.h" +#include "third-party/TPCircularBuffer/TPCircularBuffer.h" #define kBufferLength 2048 diff --git a/sunshine/platform/macos/av_audio.m b/src/platform/macos/av_audio.m similarity index 100% rename from sunshine/platform/macos/av_audio.m rename to src/platform/macos/av_audio.m diff --git a/sunshine/platform/macos/av_img_t.h b/src/platform/macos/av_img_t.h similarity index 89% rename from sunshine/platform/macos/av_img_t.h rename to src/platform/macos/av_img_t.h index 7af3cbcc..a7a24935 100644 --- a/sunshine/platform/macos/av_img_t.h +++ b/src/platform/macos/av_img_t.h @@ -1,7 +1,7 @@ #ifndef av_img_t_h #define av_img_t_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" #include #include diff --git a/sunshine/platform/macos/av_video.h b/src/platform/macos/av_video.h similarity index 100% rename from sunshine/platform/macos/av_video.h rename to src/platform/macos/av_video.h diff --git a/sunshine/platform/macos/av_video.m b/src/platform/macos/av_video.m similarity index 100% rename from sunshine/platform/macos/av_video.m rename to src/platform/macos/av_video.m diff --git a/sunshine/platform/macos/display.mm b/src/platform/macos/display.mm similarity index 96% rename from sunshine/platform/macos/display.mm rename to src/platform/macos/display.mm index f00297c2..1601e3ec 100644 --- a/sunshine/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -1,10 +1,10 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_img_t.h" -#include "sunshine/platform/macos/av_video.h" -#include "sunshine/platform/macos/nv12_zero_device.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_img_t.h" +#include "src/platform/macos/av_video.h" +#include "src/platform/macos/nv12_zero_device.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/input.cpp b/src/platform/macos/input.cpp similarity index 99% rename from sunshine/platform/macos/input.cpp rename to src/platform/macos/input.cpp index fbea6619..3d9c21a3 100644 --- a/sunshine/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -2,9 +2,9 @@ #include #include -#include "sunshine/main.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/platform/common.h" +#include "src/utility.h" // Delay for a double click // FIXME: we probably want to make this configurable diff --git a/sunshine/platform/macos/microphone.mm b/src/platform/macos/microphone.mm similarity index 94% rename from sunshine/platform/macos/microphone.mm rename to src/platform/macos/microphone.mm index 4b651690..cfe9562d 100644 --- a/sunshine/platform/macos/microphone.mm +++ b/src/platform/macos/microphone.mm @@ -1,8 +1,8 @@ -#include "sunshine/platform/common.h" -#include "sunshine/platform/macos/av_audio.h" +#include "src/platform/common.h" +#include "src/platform/macos/av_audio.h" -#include "sunshine/config.h" -#include "sunshine/main.h" +#include "src/config.h" +#include "src/main.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/macos/misc.cpp b/src/platform/macos/misc.cpp similarity index 98% rename from sunshine/platform/macos/misc.cpp rename to src/platform/macos/misc.cpp index fdc46688..a7b0a89f 100644 --- a/sunshine/platform/macos/misc.cpp +++ b/src/platform/macos/misc.cpp @@ -6,8 +6,8 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/main.h" +#include "src/platform/common.h" using namespace std::literals; namespace fs = std::filesystem; diff --git a/sunshine/platform/macos/misc.h b/src/platform/macos/misc.h similarity index 100% rename from sunshine/platform/macos/misc.h rename to src/platform/macos/misc.h diff --git a/sunshine/platform/macos/nv12_zero_device.cpp b/src/platform/macos/nv12_zero_device.cpp similarity index 95% rename from sunshine/platform/macos/nv12_zero_device.cpp rename to src/platform/macos/nv12_zero_device.cpp index 7e0a4a77..1af0e058 100644 --- a/sunshine/platform/macos/nv12_zero_device.cpp +++ b/src/platform/macos/nv12_zero_device.cpp @@ -1,7 +1,7 @@ -#include "sunshine/platform/macos/nv12_zero_device.h" -#include "sunshine/platform/macos/av_img_t.h" +#include "src/platform/macos/nv12_zero_device.h" +#include "src/platform/macos/av_img_t.h" -#include "sunshine/video.h" +#include "src/video.h" extern "C" { #include "libavutil/imgutils.h" diff --git a/sunshine/platform/macos/nv12_zero_device.h b/src/platform/macos/nv12_zero_device.h similarity index 96% rename from sunshine/platform/macos/nv12_zero_device.h rename to src/platform/macos/nv12_zero_device.h index 847a8f0a..3b74ebcc 100644 --- a/sunshine/platform/macos/nv12_zero_device.h +++ b/src/platform/macos/nv12_zero_device.h @@ -1,7 +1,7 @@ #ifndef vtdevice_h #define vtdevice_h -#include "sunshine/platform/common.h" +#include "src/platform/common.h" namespace platf { diff --git a/sunshine/platform/macos/publish.cpp b/src/platform/macos/publish.cpp similarity index 99% rename from sunshine/platform/macos/publish.cpp rename to src/platform/macos/publish.cpp index cc10cd82..bd943ec9 100644 --- a/sunshine/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -3,10 +3,10 @@ #include #include "misc.h" -#include "sunshine/main.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" +#include "src/main.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/utility.h" using namespace std::literals; diff --git a/sunshine/platform/windows/PolicyConfig.h b/src/platform/windows/PolicyConfig.h similarity index 96% rename from sunshine/platform/windows/PolicyConfig.h rename to src/platform/windows/PolicyConfig.h index 60f36a68..be18ec6f 100644 --- a/sunshine/platform/windows/PolicyConfig.h +++ b/src/platform/windows/PolicyConfig.h @@ -1,164 +1,164 @@ -// ---------------------------------------------------------------------------- -// PolicyConfig.h -// Undocumented COM-interface IPolicyConfig. -// Use for set default audio render endpoint -// @author EreTIk -// http://eretik.omegahg.com/ -// ---------------------------------------------------------------------------- - - -#pragma once - -#ifdef __MINGW32__ -#undef DEFINE_GUID -#ifdef __cplusplus -#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } -#else -#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } -#endif - -DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8); -DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9); - -#endif - -interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; -class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; -// ---------------------------------------------------------------------------- -// class CPolicyConfigClient -// {870af99c-171d-4f9e-af0d-e63df40c2bc9} -// -// interface IPolicyConfig -// {f8679f50-850a-41cf-9c72-430f290290c8} -// -// Query interface: -// CComPtr PolicyConfig; -// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); -// -// @compatible: Windows 7 and Later -// ---------------------------------------------------------------------------- -interface IPolicyConfig : public IUnknown { -public: - virtual HRESULT GetMixFormat( - PCWSTR, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( - PCWSTR, - INT, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( - PCWSTR); - - virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( - PCWSTR, - WAVEFORMATEX *, - WAVEFORMATEX *); - - virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( - PCWSTR, - INT, - PINT64, - PINT64); - - virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( - PCWSTR, - PINT64); - - virtual HRESULT STDMETHODCALLTYPE GetShareMode( - PCWSTR, - struct DeviceShareMode *); - - virtual HRESULT STDMETHODCALLTYPE SetShareMode( - PCWSTR, - struct DeviceShareMode *); - - virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( - PCWSTR wszDeviceId, - ERole eRole); - - virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( - PCWSTR, - INT); -}; - -interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista; -class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient; -// ---------------------------------------------------------------------------- -// class CPolicyConfigVistaClient -// {294935CE-F637-4E7C-A41B-AB255460B862} -// -// interface IPolicyConfigVista -// {568b9108-44bf-40b4-9006-86afe5b5a620} -// -// Query interface: -// CComPtr PolicyConfig; -// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); -// -// @compatible: Windows Vista and Later -// ---------------------------------------------------------------------------- -interface IPolicyConfigVista : public IUnknown { -public: - virtual HRESULT GetMixFormat( - PCWSTR, - WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( - PCWSTR, - INT, - WAVEFORMATEX **); - - virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( - PCWSTR, - WAVEFORMATEX *, - WAVEFORMATEX *); - - virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( - PCWSTR, - INT, - PINT64, - PINT64); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( - PCWSTR, - PINT64); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetShareMode( - PCWSTR, - struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE SetShareMode( - PCWSTR, - struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig - - virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT *); - - virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( - PCWSTR wszDeviceId, - ERole eRole); - - virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( - PCWSTR, - INT); // not available on Windows 7, use method from IPolicyConfig -}; - -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- +// PolicyConfig.h +// Undocumented COM-interface IPolicyConfig. +// Use for set default audio render endpoint +// @author EreTIk +// http://eretik.omegahg.com/ +// ---------------------------------------------------------------------------- + + +#pragma once + +#ifdef __MINGW32__ +#undef DEFINE_GUID +#ifdef __cplusplus +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#else +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#endif + +DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8); +DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9); + +#endif + +interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; +class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigClient +// {870af99c-171d-4f9e-af0d-e63df40c2bc9} +// +// interface IPolicyConfig +// {f8679f50-850a-41cf-9c72-430f290290c8} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); +// +// @compatible: Windows 7 and Later +// ---------------------------------------------------------------------------- +interface IPolicyConfig : public IUnknown { +public: + virtual HRESULT GetMixFormat( + PCWSTR, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( + PCWSTR, + INT, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( + PCWSTR); + + virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( + PCWSTR, + WAVEFORMATEX *, + WAVEFORMATEX *); + + virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( + PCWSTR, + INT, + PINT64, + PINT64); + + virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( + PCWSTR, + PINT64); + + virtual HRESULT STDMETHODCALLTYPE GetShareMode( + PCWSTR, + struct DeviceShareMode *); + + virtual HRESULT STDMETHODCALLTYPE SetShareMode( + PCWSTR, + struct DeviceShareMode *); + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + ERole eRole); + + virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( + PCWSTR, + INT); +}; + +interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista; +class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigVistaClient +// {294935CE-F637-4E7C-A41B-AB255460B862} +// +// interface IPolicyConfigVista +// {568b9108-44bf-40b4-9006-86afe5b5a620} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); +// +// @compatible: Windows Vista and Later +// ---------------------------------------------------------------------------- +interface IPolicyConfigVista : public IUnknown { +public: + virtual HRESULT GetMixFormat( + PCWSTR, + WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( + PCWSTR, + INT, + WAVEFORMATEX **); + + virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( + PCWSTR, + WAVEFORMATEX *, + WAVEFORMATEX *); + + virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( + PCWSTR, + INT, + PINT64, + PINT64); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( + PCWSTR, + PINT64); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetShareMode( + PCWSTR, + struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE SetShareMode( + PCWSTR, + struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT *); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + ERole eRole); + + virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( + PCWSTR, + INT); // not available on Windows 7, use method from IPolicyConfig +}; + +// ---------------------------------------------------------------------------- diff --git a/sunshine/platform/windows/audio.cpp b/src/platform/windows/audio.cpp similarity index 96% rename from sunshine/platform/windows/audio.cpp rename to src/platform/windows/audio.cpp index 61df413a..b630f73e 100644 --- a/sunshine/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -1,988 +1,988 @@ -// -// Created by loki on 1/12/20. -// - -#include -#include -#include - -#include - -#include - -#define INITGUID -#include -#undef INITGUID - -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" - -// Must be the last included file -// clang-format off -#include "PolicyConfig.h" -// clang-format on - -DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING -DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING -DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); - -const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); -const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); -const IID IID_IAudioClient = __uuidof(IAudioClient); -const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); - -using namespace std::literals; -namespace platf::audio { -constexpr auto SAMPLE_RATE = 48000; - -template -void Release(T *p) { - p->Release(); -} - -template -void co_task_free(T *p) { - CoTaskMemFree((LPVOID)p); -} - -using device_enum_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using collection_t = util::safe_ptr>; -using audio_client_t = util::safe_ptr>; -using audio_capture_t = util::safe_ptr>; -using wave_format_t = util::safe_ptr>; -using wstring_t = util::safe_ptr>; -using handle_t = util::safe_ptr_v2; -using policy_t = util::safe_ptr>; -using prop_t = util::safe_ptr>; - -class co_init_t : public deinit_t { -public: - co_init_t() { - CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); - } - - ~co_init_t() override { - CoUninitialize(); - } -}; - -class prop_var_t { -public: - prop_var_t() { - PropVariantInit(&prop); - } - - ~prop_var_t() { - PropVariantClear(&prop); - } - - PROPVARIANT prop; -}; - -class audio_pipe_t { -public: - static constexpr auto stereo = 2; - static constexpr auto channels51 = 6; - static constexpr auto channels71 = 8; - - using samples_t = std::vector; - using buf_t = util::buffer_t; - - virtual void to_stereo(samples_t &out, const buf_t &in) = 0; - virtual void to_51(samples_t &out, const buf_t &in) = 0; - virtual void to_71(samples_t &out, const buf_t &in) = 0; -}; - -class mono_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) override { - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) { - *sample_out_p++ = *sample_in_pos * 7 / 10; - *sample_out_p++ = *sample_in_pos++ * 7 / 10; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - int left = *sample_in_pos++; - - auto fl = (left * 7 / 10); - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fl; - sample_out_p[FRONT_CENTER] = fl * 6; - sample_out_p[LOW_FREQUENCY] = fl / 10; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = left * 4 / 10; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int left = *sample_in_pos++; - - auto fl = (left * 7 / 10); - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fl; - sample_out_p[FRONT_CENTER] = fl * 6; - sample_out_p[LOW_FREQUENCY] = fl / 10; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = left * 4 / 10; - sample_out_p[SIDE_LEFT] = left * 5 / 10; - sample_out_p[SIDE_RIGHT] = left * 5 / 10; - } - } -}; - -class stereo_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - int left = sample_in_pos[speaker::FRONT_LEFT]; - int right = sample_in_pos[speaker::FRONT_RIGHT]; - - sample_in_pos += 2; - - auto fl = (left * 7 / 10); - auto fr = (right * 7 / 10); - - auto mix = (fl + fr) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = mix; - sample_out_p[LOW_FREQUENCY] = mix / 2; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = right * 4 / 10; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int left = sample_in_pos[speaker::FRONT_LEFT]; - int right = sample_in_pos[speaker::FRONT_RIGHT]; - - sample_in_pos += 2; - - auto fl = (left * 7 / 10); - auto fr = (right * 7 / 10); - - auto mix = (fl + fr) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = mix; - sample_out_p[LOW_FREQUENCY] = mix / 2; - sample_out_p[BACK_LEFT] = left * 4 / 10; - sample_out_p[BACK_RIGHT] = right * 4 / 10; - sample_out_p[SIDE_LEFT] = left * 5 / 10; - sample_out_p[SIDE_RIGHT] = right * 5 / 10; - } - } -}; - -class surr51_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { - int left {}, right {}; - - left += sample_in_pos[FRONT_LEFT]; - left += sample_in_pos[FRONT_CENTER] * 9 / 10; - left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - left += sample_in_pos[BACK_LEFT] * 7 / 10; - left += sample_in_pos[BACK_RIGHT] * 3 / 10; - - right += sample_in_pos[FRONT_RIGHT]; - right += sample_in_pos[FRONT_CENTER] * 9 / 10; - right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - right += sample_in_pos[BACK_LEFT] * 3 / 10; - right += sample_in_pos[BACK_RIGHT] * 7 / 10; - - sample_out_p[0] = left; - sample_out_p[1] = right; - - sample_in_pos += channels51; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } - - void to_71(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { - int fl = sample_in_pos[FRONT_LEFT]; - int fr = sample_in_pos[FRONT_RIGHT]; - int bl = sample_in_pos[BACK_LEFT]; - int br = sample_in_pos[BACK_RIGHT]; - - auto mix_l = (fl + bl) / 2; - auto mix_r = (bl + br) / 2; - - sample_out_p[FRONT_LEFT] = fl; - sample_out_p[FRONT_RIGHT] = fr; - sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; - sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; - sample_out_p[BACK_LEFT] = bl; - sample_out_p[BACK_RIGHT] = br; - sample_out_p[SIDE_LEFT] = mix_l; - sample_out_p[SIDE_RIGHT] = mix_r; - - sample_in_pos += channels51; - } - } -}; - -class surr71_t : public audio_pipe_t { -public: - void to_stereo(samples_t &out, const buf_t &in) { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { - int left {}, right {}; - - left += sample_in_pos[FRONT_LEFT]; - left += sample_in_pos[FRONT_CENTER] * 9 / 10; - left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - left += sample_in_pos[BACK_LEFT] * 7 / 10; - left += sample_in_pos[BACK_RIGHT] * 3 / 10; - left += sample_in_pos[SIDE_LEFT]; - - right += sample_in_pos[FRONT_RIGHT]; - right += sample_in_pos[FRONT_CENTER] * 9 / 10; - right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; - right += sample_in_pos[BACK_LEFT] * 3 / 10; - right += sample_in_pos[BACK_RIGHT] * 7 / 10; - right += sample_in_pos[SIDE_RIGHT]; - - sample_out_p[0] = left; - sample_out_p[1] = right; - - sample_in_pos += channels71; - } - } - - void to_51(samples_t &out, const buf_t &in) override { - using namespace speaker; - - auto sample_in_pos = std::begin(in); - auto sample_end = std::begin(out) + out.size(); - - for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { - auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10; - auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10; - - sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl; - sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr; - sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; - sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; - sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl; - sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr; - - sample_in_pos += channels71; - } - } - - void to_71(samples_t &out, const buf_t &in) override { - std::copy_n(std::begin(in), out.size(), std::begin(out)); - } -}; - -static std::wstring_convert, wchar_t> converter; -struct format_t { - enum type_e : int { - none, - mono, - stereo, - surr51, - surr71, - } type; - - std::string_view name; - int channels; - int channel_mask; -} formats[] { - { - format_t::mono, - "Mono"sv, - 1, - SPEAKER_FRONT_CENTER, - }, - { - format_t::stereo, - "Stereo"sv, - 2, - SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, - }, - { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT, - }, - { - format_t::surr71, - "Surround 7.1"sv, - 8, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_BACK_LEFT | - SPEAKER_BACK_RIGHT | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, - }, -}; - -static format_t surround_51_side_speakers { - format_t::surr51, - "Surround 5.1"sv, - 6, - SPEAKER_FRONT_LEFT | - SPEAKER_FRONT_RIGHT | - SPEAKER_FRONT_CENTER | - SPEAKER_LOW_FREQUENCY | - SPEAKER_SIDE_LEFT | - SPEAKER_SIDE_RIGHT, -}; - -void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { - wave_format->nChannels = format.channels; - wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; - wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; - - if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; - } -} - -int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { - wave_format->wBitsPerSample = 16; - wave_format->nSamplesPerSec = sample_rate; - switch(wave_format->wFormatTag) { - case WAVE_FORMAT_PCM: - break; - case WAVE_FORMAT_IEEE_FLOAT: - break; - case WAVE_FORMAT_EXTENSIBLE: { - auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); - if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { - wave_ex->Samples.wValidBitsPerSample = 16; - wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - break; - } - - BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; - } - default: - BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; - return -1; - }; - - return 0; -} - -audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { - audio_client_t audio_client; - auto status = device->Activate( - IID_IAudioClient, - CLSCTX_ALL, - nullptr, - (void **)&audio_client); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - wave_format_t wave_format; - status = audio_client->GetMixFormat(&wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - if(init_wave_format(wave_format, sample_rate)) { - return nullptr; - } - set_wave_format(wave_format, format); - - status = audio_client->Initialize( - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - 0, 0, - wave_format.get(), - nullptr); - - if(status) { - BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return audio_client; -} - -const wchar_t *no_null(const wchar_t *str) { - return str ? str : L"Unknown"; -} - -format_t::type_e validate_device(device_t &device, int sample_rate) { - for(const auto &format : formats) { - // Ensure WaveFromat is compatible - auto audio_client = make_audio_client(device, format, sample_rate); - - BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); - - if(audio_client) { - return format.type; - } - } - - return format_t::none; -} - -device_t default_device(device_enum_t &device_enum) { - device_t device; - HRESULT status; - status = device_enum->GetDefaultAudioEndpoint( - eRender, - eConsole, - &device); - - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; - - return nullptr; - } - - return device; -} - -class mic_wasapi_t : public mic_t { -public: - capture_e sample(std::vector &sample_out) override { - auto sample_size = sample_out.size() / channels_out * channels_in; - while(sample_buf_pos - std::begin(sample_buf) < sample_size) { - //FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples - auto capture_result = _fill_buffer(); - - if(capture_result != capture_e::ok) { - return capture_result; - } - } - - switch(channels_out) { - case 2: - pipe->to_stereo(sample_out, sample_buf); - break; - case 6: - pipe->to_51(sample_out, sample_buf); - break; - case 8: - pipe->to_71(sample_out, sample_buf); - break; - default: - BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; - return capture_e::error; - } - - // The excess samples should be in front of the queue - std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); - sample_buf_pos -= sample_size; - - return capture_e::ok; - } - - - int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) { - audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); - if(!audio_event) { - BOOST_LOG(error) << "Couldn't create Event handle"sv; - - return -1; - } - - HRESULT status; - - status = CoCreateInstance( - CLSID_MMDeviceEnumerator, - nullptr, - CLSCTX_ALL, - IID_IMMDeviceEnumerator, - (void **)&device_enum); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - auto device = default_device(device_enum); - if(!device) { - return -1; - } - - for(auto &format : formats) { - BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; - audio_client = make_audio_client(device, format, sample_rate); - - if(audio_client) { - BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; - channels_in = format.channels; - this->channels_out = channels_out; - - switch(channels_in) { - case 1: - pipe = std::make_unique(); - break; - case 2: - pipe = std::make_unique(); - break; - case 6: - pipe = std::make_unique(); - break; - case 8: - pipe = std::make_unique(); - break; - default: - BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv; - return -1; - } - break; - } - } - - if(!audio_client) { - BOOST_LOG(error) << "Couldn't find supported format for audio"sv; - return -1; - } - - REFERENCE_TIME default_latency; - audio_client->GetDevicePeriod(&default_latency, nullptr); - default_latency_ms = default_latency / 1000; - - std::uint32_t frames; - status = audio_client->GetBufferSize(&frames); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - // *2 --> needs to fit double - sample_buf = util::buffer_t { std::max(frames, frame_size) * 2 * channels_in }; - sample_buf_pos = std::begin(sample_buf); - - status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->SetEventHandle(audio_event.get()); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - status = audio_client->Start(); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~mic_wasapi_t() override { - if(audio_client) { - audio_client->Stop(); - } - } - -private: - capture_e _fill_buffer() { - HRESULT status; - - // Total number of samples - struct sample_aligned_t { - std::uint32_t uninitialized; - std::int16_t *samples; - } sample_aligned; - - // number of samples / number of channels - struct block_aligned_t { - std::uint32_t audio_sample_size; - } block_aligned; - - status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); - switch(status) { - case WAIT_OBJECT_0: - break; - case WAIT_TIMEOUT: - return capture_e::timeout; - default: - BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - std::uint32_t packet_size {}; - for( - status = audio_capture->GetNextPacketSize(&packet_size); - SUCCEEDED(status) && packet_size > 0; - status = audio_capture->GetNextPacketSize(&packet_size)) { - DWORD buffer_flags; - status = audio_capture->GetBuffer( - (BYTE **)&sample_aligned.samples, - &block_aligned.audio_sample_size, - &buffer_flags, - nullptr, nullptr); - - switch(status) { - case S_OK: - break; - case AUDCLNT_E_DEVICE_INVALIDATED: - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; - auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in); - - if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { - std::fill_n(sample_buf_pos, n, 0); - } - else { - std::copy_n(sample_aligned.samples, n, sample_buf_pos); - } - - sample_buf_pos += n; - - audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); - } - - if(status == AUDCLNT_E_DEVICE_INVALIDATED) { - return capture_e::reinit; - } - - if(FAILED(status)) { - return capture_e::error; - } - - return capture_e::ok; - } - -public: - handle_t audio_event; - - device_enum_t device_enum; - device_t device; - audio_client_t audio_client; - audio_capture_t audio_capture; - - REFERENCE_TIME default_latency_ms; - - util::buffer_t sample_buf; - std::int16_t *sample_buf_pos; - - // out --> our audio output - int channels_out; - // in --> our wasapi input - int channels_in; - - std::unique_ptr pipe; -}; - -class audio_control_t : public ::platf::audio_control_t { -public: - std::optional sink_info() override { - auto virtual_adapter_name = L"Steam Streaming Speakers"sv; - - sink_t sink; - - audio::device_enum_t device_enum; - auto status = CoCreateInstance( - CLSID_MMDeviceEnumerator, - nullptr, - CLSCTX_ALL, - IID_IMMDeviceEnumerator, - (void **)&device_enum); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - auto device = default_device(device_enum); - if(!device) { - return std::nullopt; - } - - audio::wstring_t wstring; - device->GetId(&wstring); - - sink.host = converter.to_bytes(wstring.get()); - - collection_t collection; - status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - UINT count; - collection->GetCount(&count); - - std::string virtual_device_id = config::audio.virtual_sink; - for(auto x = 0; x < count; ++x) { - audio::device_t device; - collection->Item(x, &device); - - auto type = validate_device(device, SAMPLE_RATE); - if(type == format_t::none) { - continue; - } - - audio::wstring_t wstring; - device->GetId(&wstring); - - audio::prop_t prop; - device->OpenPropertyStore(STGM_READ, &prop); - - prop_var_t adapter_friendly_name; - prop_var_t device_friendly_name; - prop_var_t device_desc; - - prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); - prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); - prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); - - auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); - BOOST_LOG(verbose) - << L"===== Device ====="sv << std::endl - << L"Device ID : "sv << wstring.get() << std::endl - << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl - << L"Adapter name : "sv << adapter_name << std::endl - << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl - << std::endl; - - if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { - virtual_device_id = converter.to_bytes(wstring.get()); - } - } - - if(!virtual_device_id.empty()) { - sink.null = std::make_optional(sink_t::null_t { - "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, - "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, - }); - } - - return sink; - } - - std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { - auto mic = std::make_unique(); - - if(mic->init(sample_rate, frame_size, channels)) { - return nullptr; - } - - return mic; - } - - /** - * If the requested sink is a virtual sink, meaning no speakers attached to - * the host, then we can seamlessly set the format to stereo and surround sound. - * - * Any virtual sink detected will be prefixed by: - * virtual-(format name) - * If it doesn't contain that prefix, then the format will not be changed - */ - std::optional set_format(const std::string &sink) { - std::string_view sv { sink.c_str(), sink.size() }; - - format_t::type_e type = format_t::none; - // sink format: - // [virtual-(format name)]device_id - auto prefix = "virtual-"sv; - if(sv.find(prefix) == 0) { - sv = sv.substr(prefix.size(), sv.size() - prefix.size()); - - for(auto &format : formats) { - auto &name = format.name; - if(sv.find(name) == 0) { - type = format.type; - sv = sv.substr(name.size(), sv.size() - name.size()); - - break; - } - } - } - - auto wstring_device_id = converter.from_bytes(sv.data()); - - if(type == format_t::none) { - // wstring_device_id does not contain virtual-(format name) - // It's a simple deviceId, just pass it back - return std::make_optional(std::move(wstring_device_id)); - } - - wave_format_t wave_format; - auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - if(init_wave_format(wave_format, SAMPLE_RATE)) { - return std::nullopt; - } - set_wave_format(wave_format, formats[(int)type - 1]); - - WAVEFORMATEXTENSIBLE p {}; - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - - // Surround 5.1 might contain side-{left, right} instead of speaker in the back - // Try again with different speaker mask. - if(status == 0x88890008 && type == format_t::surr51) { - set_wave_format(wave_format, surround_51_side_speakers); - status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); - } - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; - - return std::nullopt; - } - - return std::make_optional(std::move(wstring_device_id)); - } - - int set_sink(const std::string &sink) override { - auto wstring_device_id = set_format(sink); - if(!wstring_device_id) { - return -1; - } - - int failure {}; - for(int x = 0; x < (int)ERole_enum_count; ++x) { - auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); - if(status) { - BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; - - ++failure; - } - } - - return failure; - } - - int init() { - auto status = CoCreateInstance( - CLSID_CPolicyConfigClient, - nullptr, - CLSCTX_ALL, - IID_IPolicyConfig, - (void **)&policy); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - return 0; - } - - ~audio_control_t() override {} - - policy_t policy; -}; -} // namespace platf::audio - -namespace platf { - -// It's not big enough to justify it's own source file :/ -namespace dxgi { -int init(); -} - -std::unique_ptr audio_control() { - auto control = std::make_unique(); - - if(control->init()) { - return nullptr; - } - - return control; -} - -std::unique_ptr init() { - if(dxgi::init()) { - return nullptr; - } - return std::make_unique(); -} -} // namespace platf +// +// Created by loki on 1/12/20. +// + +#include +#include +#include + +#include + +#include + +#define INITGUID +#include +#undef INITGUID + +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" + +// Must be the last included file +// clang-format off +#include "PolicyConfig.h" +// clang-format on + +DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); + +const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); +const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); +const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); + +using namespace std::literals; +namespace platf::audio { +constexpr auto SAMPLE_RATE = 48000; + +template +void Release(T *p) { + p->Release(); +} + +template +void co_task_free(T *p) { + CoTaskMemFree((LPVOID)p); +} + +using device_enum_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using collection_t = util::safe_ptr>; +using audio_client_t = util::safe_ptr>; +using audio_capture_t = util::safe_ptr>; +using wave_format_t = util::safe_ptr>; +using wstring_t = util::safe_ptr>; +using handle_t = util::safe_ptr_v2; +using policy_t = util::safe_ptr>; +using prop_t = util::safe_ptr>; + +class co_init_t : public deinit_t { +public: + co_init_t() { + CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY); + } + + ~co_init_t() override { + CoUninitialize(); + } +}; + +class prop_var_t { +public: + prop_var_t() { + PropVariantInit(&prop); + } + + ~prop_var_t() { + PropVariantClear(&prop); + } + + PROPVARIANT prop; +}; + +class audio_pipe_t { +public: + static constexpr auto stereo = 2; + static constexpr auto channels51 = 6; + static constexpr auto channels71 = 8; + + using samples_t = std::vector; + using buf_t = util::buffer_t; + + virtual void to_stereo(samples_t &out, const buf_t &in) = 0; + virtual void to_51(samples_t &out, const buf_t &in) = 0; + virtual void to_71(samples_t &out, const buf_t &in) = 0; +}; + +class mono_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) override { + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) { + *sample_out_p++ = *sample_in_pos * 7 / 10; + *sample_out_p++ = *sample_in_pos++ * 7 / 10; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + int left = *sample_in_pos++; + + auto fl = (left * 7 / 10); + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fl; + sample_out_p[FRONT_CENTER] = fl * 6; + sample_out_p[LOW_FREQUENCY] = fl / 10; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = left * 4 / 10; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int left = *sample_in_pos++; + + auto fl = (left * 7 / 10); + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fl; + sample_out_p[FRONT_CENTER] = fl * 6; + sample_out_p[LOW_FREQUENCY] = fl / 10; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = left * 4 / 10; + sample_out_p[SIDE_LEFT] = left * 5 / 10; + sample_out_p[SIDE_RIGHT] = left * 5 / 10; + } + } +}; + +class stereo_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + int left = sample_in_pos[speaker::FRONT_LEFT]; + int right = sample_in_pos[speaker::FRONT_RIGHT]; + + sample_in_pos += 2; + + auto fl = (left * 7 / 10); + auto fr = (right * 7 / 10); + + auto mix = (fl + fr) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = mix; + sample_out_p[LOW_FREQUENCY] = mix / 2; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = right * 4 / 10; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int left = sample_in_pos[speaker::FRONT_LEFT]; + int right = sample_in_pos[speaker::FRONT_RIGHT]; + + sample_in_pos += 2; + + auto fl = (left * 7 / 10); + auto fr = (right * 7 / 10); + + auto mix = (fl + fr) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = mix; + sample_out_p[LOW_FREQUENCY] = mix / 2; + sample_out_p[BACK_LEFT] = left * 4 / 10; + sample_out_p[BACK_RIGHT] = right * 4 / 10; + sample_out_p[SIDE_LEFT] = left * 5 / 10; + sample_out_p[SIDE_RIGHT] = right * 5 / 10; + } + } +}; + +class surr51_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { + int left {}, right {}; + + left += sample_in_pos[FRONT_LEFT]; + left += sample_in_pos[FRONT_CENTER] * 9 / 10; + left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + left += sample_in_pos[BACK_LEFT] * 7 / 10; + left += sample_in_pos[BACK_RIGHT] * 3 / 10; + + right += sample_in_pos[FRONT_RIGHT]; + right += sample_in_pos[FRONT_CENTER] * 9 / 10; + right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + right += sample_in_pos[BACK_LEFT] * 3 / 10; + right += sample_in_pos[BACK_RIGHT] * 7 / 10; + + sample_out_p[0] = left; + sample_out_p[1] = right; + + sample_in_pos += channels51; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } + + void to_71(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) { + int fl = sample_in_pos[FRONT_LEFT]; + int fr = sample_in_pos[FRONT_RIGHT]; + int bl = sample_in_pos[BACK_LEFT]; + int br = sample_in_pos[BACK_RIGHT]; + + auto mix_l = (fl + bl) / 2; + auto mix_r = (bl + br) / 2; + + sample_out_p[FRONT_LEFT] = fl; + sample_out_p[FRONT_RIGHT] = fr; + sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; + sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; + sample_out_p[BACK_LEFT] = bl; + sample_out_p[BACK_RIGHT] = br; + sample_out_p[SIDE_LEFT] = mix_l; + sample_out_p[SIDE_RIGHT] = mix_r; + + sample_in_pos += channels51; + } + } +}; + +class surr71_t : public audio_pipe_t { +public: + void to_stereo(samples_t &out, const buf_t &in) { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) { + int left {}, right {}; + + left += sample_in_pos[FRONT_LEFT]; + left += sample_in_pos[FRONT_CENTER] * 9 / 10; + left += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + left += sample_in_pos[BACK_LEFT] * 7 / 10; + left += sample_in_pos[BACK_RIGHT] * 3 / 10; + left += sample_in_pos[SIDE_LEFT]; + + right += sample_in_pos[FRONT_RIGHT]; + right += sample_in_pos[FRONT_CENTER] * 9 / 10; + right += sample_in_pos[LOW_FREQUENCY] * 3 / 10; + right += sample_in_pos[BACK_LEFT] * 3 / 10; + right += sample_in_pos[BACK_RIGHT] * 7 / 10; + right += sample_in_pos[SIDE_RIGHT]; + + sample_out_p[0] = left; + sample_out_p[1] = right; + + sample_in_pos += channels71; + } + } + + void to_51(samples_t &out, const buf_t &in) override { + using namespace speaker; + + auto sample_in_pos = std::begin(in); + auto sample_end = std::begin(out) + out.size(); + + for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) { + auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10; + auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10; + + sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl; + sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr; + sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER]; + sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY]; + sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl; + sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr; + + sample_in_pos += channels71; + } + } + + void to_71(samples_t &out, const buf_t &in) override { + std::copy_n(std::begin(in), out.size(), std::begin(out)); + } +}; + +static std::wstring_convert, wchar_t> converter; +struct format_t { + enum type_e : int { + none, + mono, + stereo, + surr51, + surr71, + } type; + + std::string_view name; + int channels; + int channel_mask; +} formats[] { + { + format_t::mono, + "Mono"sv, + 1, + SPEAKER_FRONT_CENTER, + }, + { + format_t::stereo, + "Stereo"sv, + 2, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + }, + { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT, + }, + { + format_t::surr71, + "Surround 7.1"sv, + 8, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, + }, +}; + +static format_t surround_51_side_speakers { + format_t::surr51, + "Surround 5.1"sv, + 6, + SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT, +}; + +void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) { + wave_format->nChannels = format.channels; + wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8; + wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign; + + if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + ((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask; + } +} + +int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) { + wave_format->wBitsPerSample = 16; + wave_format->nSamplesPerSec = sample_rate; + switch(wave_format->wFormatTag) { + case WAVE_FORMAT_PCM: + break; + case WAVE_FORMAT_IEEE_FLOAT: + break; + case WAVE_FORMAT_EXTENSIBLE: { + auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get(); + if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) { + wave_ex->Samples.wValidBitsPerSample = 16; + wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + } + + BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']'; + } + default: + BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']'; + return -1; + }; + + return 0; +} + +audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) { + audio_client_t audio_client; + auto status = device->Activate( + IID_IAudioClient, + CLSCTX_ALL, + nullptr, + (void **)&audio_client); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + wave_format_t wave_format; + status = audio_client->GetMixFormat(&wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + if(init_wave_format(wave_format, sample_rate)) { + return nullptr; + } + set_wave_format(wave_format, format); + + status = audio_client->Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, 0, + wave_format.get(), + nullptr); + + if(status) { + BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return audio_client; +} + +const wchar_t *no_null(const wchar_t *str) { + return str ? str : L"Unknown"; +} + +format_t::type_e validate_device(device_t &device, int sample_rate) { + for(const auto &format : formats) { + // Ensure WaveFromat is compatible + auto audio_client = make_audio_client(device, format, sample_rate); + + BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv); + + if(audio_client) { + return format.type; + } + } + + return format_t::none; +} + +device_t default_device(device_enum_t &device_enum) { + device_t device; + HRESULT status; + status = device_enum->GetDefaultAudioEndpoint( + eRender, + eConsole, + &device); + + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']'; + + return nullptr; + } + + return device; +} + +class mic_wasapi_t : public mic_t { +public: + capture_e sample(std::vector &sample_out) override { + auto sample_size = sample_out.size() / channels_out * channels_in; + while(sample_buf_pos - std::begin(sample_buf) < sample_size) { + //FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples + auto capture_result = _fill_buffer(); + + if(capture_result != capture_e::ok) { + return capture_result; + } + } + + switch(channels_out) { + case 2: + pipe->to_stereo(sample_out, sample_buf); + break; + case 6: + pipe->to_51(sample_out, sample_buf); + break; + case 8: + pipe->to_71(sample_out, sample_buf); + break; + default: + BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv; + return capture_e::error; + } + + // The excess samples should be in front of the queue + std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf)); + sample_buf_pos -= sample_size; + + return capture_e::ok; + } + + + int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) { + audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); + if(!audio_event) { + BOOST_LOG(error) << "Couldn't create Event handle"sv; + + return -1; + } + + HRESULT status; + + status = CoCreateInstance( + CLSID_MMDeviceEnumerator, + nullptr, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + (void **)&device_enum); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + auto device = default_device(device_enum); + if(!device) { + return -1; + } + + for(auto &format : formats) { + BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']'; + audio_client = make_audio_client(device, format, sample_rate); + + if(audio_client) { + BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']'; + channels_in = format.channels; + this->channels_out = channels_out; + + switch(channels_in) { + case 1: + pipe = std::make_unique(); + break; + case 2: + pipe = std::make_unique(); + break; + case 6: + pipe = std::make_unique(); + break; + case 8: + pipe = std::make_unique(); + break; + default: + BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv; + return -1; + } + break; + } + } + + if(!audio_client) { + BOOST_LOG(error) << "Couldn't find supported format for audio"sv; + return -1; + } + + REFERENCE_TIME default_latency; + audio_client->GetDevicePeriod(&default_latency, nullptr); + default_latency_ms = default_latency / 1000; + + std::uint32_t frames; + status = audio_client->GetBufferSize(&frames); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + // *2 --> needs to fit double + sample_buf = util::buffer_t { std::max(frames, frame_size) * 2 * channels_in }; + sample_buf_pos = std::begin(sample_buf); + + status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->SetEventHandle(audio_event.get()); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + status = audio_client->Start(); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~mic_wasapi_t() override { + if(audio_client) { + audio_client->Stop(); + } + } + +private: + capture_e _fill_buffer() { + HRESULT status; + + // Total number of samples + struct sample_aligned_t { + std::uint32_t uninitialized; + std::int16_t *samples; + } sample_aligned; + + // number of samples / number of channels + struct block_aligned_t { + std::uint32_t audio_sample_size; + } block_aligned; + + status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE); + switch(status) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + return capture_e::timeout; + default: + BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + std::uint32_t packet_size {}; + for( + status = audio_capture->GetNextPacketSize(&packet_size); + SUCCEEDED(status) && packet_size > 0; + status = audio_capture->GetNextPacketSize(&packet_size)) { + DWORD buffer_flags; + status = audio_capture->GetBuffer( + (BYTE **)&sample_aligned.samples, + &block_aligned.audio_sample_size, + &buffer_flags, + nullptr, nullptr); + + switch(status) { + case S_OK: + break; + case AUDCLNT_E_DEVICE_INVALIDATED: + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos; + auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in); + + if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) { + std::fill_n(sample_buf_pos, n, 0); + } + else { + std::copy_n(sample_aligned.samples, n, sample_buf_pos); + } + + sample_buf_pos += n; + + audio_capture->ReleaseBuffer(block_aligned.audio_sample_size); + } + + if(status == AUDCLNT_E_DEVICE_INVALIDATED) { + return capture_e::reinit; + } + + if(FAILED(status)) { + return capture_e::error; + } + + return capture_e::ok; + } + +public: + handle_t audio_event; + + device_enum_t device_enum; + device_t device; + audio_client_t audio_client; + audio_capture_t audio_capture; + + REFERENCE_TIME default_latency_ms; + + util::buffer_t sample_buf; + std::int16_t *sample_buf_pos; + + // out --> our audio output + int channels_out; + // in --> our wasapi input + int channels_in; + + std::unique_ptr pipe; +}; + +class audio_control_t : public ::platf::audio_control_t { +public: + std::optional sink_info() override { + auto virtual_adapter_name = L"Steam Streaming Speakers"sv; + + sink_t sink; + + audio::device_enum_t device_enum; + auto status = CoCreateInstance( + CLSID_MMDeviceEnumerator, + nullptr, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + (void **)&device_enum); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + auto device = default_device(device_enum); + if(!device) { + return std::nullopt; + } + + audio::wstring_t wstring; + device->GetId(&wstring); + + sink.host = converter.to_bytes(wstring.get()); + + collection_t collection; + status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + UINT count; + collection->GetCount(&count); + + std::string virtual_device_id = config::audio.virtual_sink; + for(auto x = 0; x < count; ++x) { + audio::device_t device; + collection->Item(x, &device); + + auto type = validate_device(device, SAMPLE_RATE); + if(type == format_t::none) { + continue; + } + + audio::wstring_t wstring; + device->GetId(&wstring); + + audio::prop_t prop; + device->OpenPropertyStore(STGM_READ, &prop); + + prop_var_t adapter_friendly_name; + prop_var_t device_friendly_name; + prop_var_t device_desc; + + prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop); + prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop); + prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop); + + auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal); + BOOST_LOG(verbose) + << L"===== Device ====="sv << std::endl + << L"Device ID : "sv << wstring.get() << std::endl + << L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl + << L"Adapter name : "sv << adapter_name << std::endl + << L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl + << std::endl; + + if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) { + virtual_device_id = converter.to_bytes(wstring.get()); + } + } + + if(!virtual_device_id.empty()) { + sink.null = std::make_optional(sink_t::null_t { + "virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id, + "virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id, + }); + } + + return sink; + } + + std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override { + auto mic = std::make_unique(); + + if(mic->init(sample_rate, frame_size, channels)) { + return nullptr; + } + + return mic; + } + + /** + * If the requested sink is a virtual sink, meaning no speakers attached to + * the host, then we can seamlessly set the format to stereo and surround sound. + * + * Any virtual sink detected will be prefixed by: + * virtual-(format name) + * If it doesn't contain that prefix, then the format will not be changed + */ + std::optional set_format(const std::string &sink) { + std::string_view sv { sink.c_str(), sink.size() }; + + format_t::type_e type = format_t::none; + // sink format: + // [virtual-(format name)]device_id + auto prefix = "virtual-"sv; + if(sv.find(prefix) == 0) { + sv = sv.substr(prefix.size(), sv.size() - prefix.size()); + + for(auto &format : formats) { + auto &name = format.name; + if(sv.find(name) == 0) { + type = format.type; + sv = sv.substr(name.size(), sv.size() - name.size()); + + break; + } + } + } + + auto wstring_device_id = converter.from_bytes(sv.data()); + + if(type == format_t::none) { + // wstring_device_id does not contain virtual-(format name) + // It's a simple deviceId, just pass it back + return std::make_optional(std::move(wstring_device_id)); + } + + wave_format_t wave_format; + auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format); + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + if(init_wave_format(wave_format, SAMPLE_RATE)) { + return std::nullopt; + } + set_wave_format(wave_format, formats[(int)type - 1]); + + WAVEFORMATEXTENSIBLE p {}; + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + + // Surround 5.1 might contain side-{left, right} instead of speaker in the back + // Try again with different speaker mask. + if(status == 0x88890008 && type == format_t::surr51) { + set_wave_format(wave_format, surround_51_side_speakers); + status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p); + } + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']'; + + return std::nullopt; + } + + return std::make_optional(std::move(wstring_device_id)); + } + + int set_sink(const std::string &sink) override { + auto wstring_device_id = set_format(sink); + if(!wstring_device_id) { + return -1; + } + + int failure {}; + for(int x = 0; x < (int)ERole_enum_count; ++x) { + auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x); + if(status) { + BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']'; + + ++failure; + } + } + + return failure; + } + + int init() { + auto status = CoCreateInstance( + CLSID_CPolicyConfigClient, + nullptr, + CLSCTX_ALL, + IID_IPolicyConfig, + (void **)&policy); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + return 0; + } + + ~audio_control_t() override {} + + policy_t policy; +}; +} // namespace platf::audio + +namespace platf { + +// It's not big enough to justify it's own source file :/ +namespace dxgi { +int init(); +} + +std::unique_ptr audio_control() { + auto control = std::make_unique(); + + if(control->init()) { + return nullptr; + } + + return control; +} + +std::unique_ptr init() { + if(dxgi::init()) { + return nullptr; + } + return std::make_unique(); +} +} // namespace platf diff --git a/sunshine/platform/windows/display.h b/src/platform/windows/display.h similarity index 96% rename from sunshine/platform/windows/display.h rename to src/platform/windows/display.h index 6a150135..52faddb7 100644 --- a/sunshine/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -1,177 +1,177 @@ -// -// Created by loki on 4/23/20. -// - -#ifndef SUNSHINE_DISPLAY_H -#define SUNSHINE_DISPLAY_H - -#include -#include -#include -#include -#include -#include - -#include "sunshine/platform/common.h" -#include "sunshine/utility.h" - -namespace platf::dxgi { -extern const char *format_str[]; - -template -void Release(T *dxgi) { - dxgi->Release(); -} - -using factory1_t = util::safe_ptr>; -using dxgi_t = util::safe_ptr>; -using dxgi1_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using device_ctx_t = util::safe_ptr>; -using adapter_t = util::safe_ptr>; -using output_t = util::safe_ptr>; -using output1_t = util::safe_ptr>; -using dup_t = util::safe_ptr>; -using texture2d_t = util::safe_ptr>; -using texture1d_t = util::safe_ptr>; -using resource_t = util::safe_ptr>; -using multithread_t = util::safe_ptr>; -using vs_t = util::safe_ptr>; -using ps_t = util::safe_ptr>; -using blend_t = util::safe_ptr>; -using input_layout_t = util::safe_ptr>; -using render_target_t = util::safe_ptr>; -using shader_res_t = util::safe_ptr>; -using buf_t = util::safe_ptr>; -using raster_state_t = util::safe_ptr>; -using sampler_state_t = util::safe_ptr>; -using blob_t = util::safe_ptr>; -using depth_stencil_state_t = util::safe_ptr>; -using depth_stencil_view_t = util::safe_ptr>; - -namespace video { -using device_t = util::safe_ptr>; -using ctx_t = util::safe_ptr>; -using processor_t = util::safe_ptr>; -using processor_out_t = util::safe_ptr>; -using processor_in_t = util::safe_ptr>; -using processor_enum_t = util::safe_ptr>; -} // namespace video - -class hwdevice_t; -struct cursor_t { - std::vector img_data; - - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; - int x, y; - bool visible; -}; - -class gpu_cursor_t { -public: - gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; - void set_pos(LONG rel_x, LONG rel_y, bool visible) { - cursor_view.TopLeftX = rel_x; - cursor_view.TopLeftY = rel_y; - - this->visible = visible; - } - - void set_texture(LONG width, LONG height, texture2d_t &&texture) { - cursor_view.Width = width; - cursor_view.Height = height; - - this->texture = std::move(texture); - } - - texture2d_t texture; - shader_res_t input_res; - - D3D11_VIEWPORT cursor_view; - - bool visible; -}; - -class duplication_t { -public: - dup_t dup; - bool has_frame {}; - bool use_dwmflush {}; - - capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); - capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); - capture_e release_frame(); - - ~duplication_t(); -}; - -class display_base_t : public display_t { -public: - int init(int framerate, const std::string &display_name); - - std::chrono::nanoseconds delay; - - factory1_t factory; - adapter_t adapter; - output_t output; - device_t device; - device_ctx_t device_ctx; - duplication_t dup; - - DXGI_FORMAT format; - D3D_FEATURE_LEVEL feature_level; - - typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { - D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, - D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, - D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, - D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME - } D3DKMT_SCHEDULINGPRIORITYCLASS; - - typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); -}; - -class display_ram_t : public display_base_t { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img) override; - - int init(int framerate, const std::string &display_name); - - cursor_t cursor; - D3D11_MAPPED_SUBRESOURCE img_info; - texture2d_t texture; -}; - -class display_vram_t : public display_base_t, public std::enable_shared_from_this { -public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - - std::shared_ptr alloc_img() override; - int dummy_img(img_t *img_base) override; - - int init(int framerate, const std::string &display_name); - - std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; - - sampler_state_t sampler_linear; - - blend_t blend_enable; - blend_t blend_disable; - - ps_t scene_ps; - vs_t scene_vs; - - texture2d_t src; - gpu_cursor_t cursor; -}; -} // namespace platf::dxgi - -#endif +// +// Created by loki on 4/23/20. +// + +#ifndef SUNSHINE_DISPLAY_H +#define SUNSHINE_DISPLAY_H + +#include +#include +#include +#include +#include +#include + +#include "src/platform/common.h" +#include "src/utility.h" + +namespace platf::dxgi { +extern const char *format_str[]; + +template +void Release(T *dxgi) { + dxgi->Release(); +} + +using factory1_t = util::safe_ptr>; +using dxgi_t = util::safe_ptr>; +using dxgi1_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using device_ctx_t = util::safe_ptr>; +using adapter_t = util::safe_ptr>; +using output_t = util::safe_ptr>; +using output1_t = util::safe_ptr>; +using dup_t = util::safe_ptr>; +using texture2d_t = util::safe_ptr>; +using texture1d_t = util::safe_ptr>; +using resource_t = util::safe_ptr>; +using multithread_t = util::safe_ptr>; +using vs_t = util::safe_ptr>; +using ps_t = util::safe_ptr>; +using blend_t = util::safe_ptr>; +using input_layout_t = util::safe_ptr>; +using render_target_t = util::safe_ptr>; +using shader_res_t = util::safe_ptr>; +using buf_t = util::safe_ptr>; +using raster_state_t = util::safe_ptr>; +using sampler_state_t = util::safe_ptr>; +using blob_t = util::safe_ptr>; +using depth_stencil_state_t = util::safe_ptr>; +using depth_stencil_view_t = util::safe_ptr>; + +namespace video { +using device_t = util::safe_ptr>; +using ctx_t = util::safe_ptr>; +using processor_t = util::safe_ptr>; +using processor_out_t = util::safe_ptr>; +using processor_in_t = util::safe_ptr>; +using processor_enum_t = util::safe_ptr>; +} // namespace video + +class hwdevice_t; +struct cursor_t { + std::vector img_data; + + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; + int x, y; + bool visible; +}; + +class gpu_cursor_t { +public: + gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {}; + void set_pos(LONG rel_x, LONG rel_y, bool visible) { + cursor_view.TopLeftX = rel_x; + cursor_view.TopLeftY = rel_y; + + this->visible = visible; + } + + void set_texture(LONG width, LONG height, texture2d_t &&texture) { + cursor_view.Width = width; + cursor_view.Height = height; + + this->texture = std::move(texture); + } + + texture2d_t texture; + shader_res_t input_res; + + D3D11_VIEWPORT cursor_view; + + bool visible; +}; + +class duplication_t { +public: + dup_t dup; + bool has_frame {}; + bool use_dwmflush {}; + + capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); + capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); + capture_e release_frame(); + + ~duplication_t(); +}; + +class display_base_t : public display_t { +public: + int init(int framerate, const std::string &display_name); + + std::chrono::nanoseconds delay; + + factory1_t factory; + adapter_t adapter; + output_t output; + device_t device; + device_ctx_t device_ctx; + duplication_t dup; + + DXGI_FORMAT format; + D3D_FEATURE_LEVEL feature_level; + + typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { + D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, + D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, + D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME + } D3DKMT_SCHEDULINGPRIORITYCLASS; + + typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); +}; + +class display_ram_t : public display_base_t { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img) override; + + int init(int framerate, const std::string &display_name); + + cursor_t cursor; + D3D11_MAPPED_SUBRESOURCE img_info; + texture2d_t texture; +}; + +class display_vram_t : public display_base_t, public std::enable_shared_from_this { +public: + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img_base) override; + + int init(int framerate, const std::string &display_name); + + std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; + + sampler_state_t sampler_linear; + + blend_t blend_enable; + blend_t blend_disable; + + ps_t scene_ps; + vs_t scene_vs; + + texture2d_t src; + gpu_cursor_t cursor; +}; +} // namespace platf::dxgi + +#endif diff --git a/sunshine/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp similarity index 99% rename from sunshine/platform/windows/display_base.cpp rename to src/platform/windows/display_base.cpp index 9ee9ad97..e6951cce 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -7,9 +7,9 @@ #include "display.h" #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp similarity index 96% rename from sunshine/platform/windows/display_ram.cpp rename to src/platform/windows/display_ram.cpp index 285f0bcb..596d4880 100644 --- a/sunshine/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -1,327 +1,327 @@ -#include "display.h" -#include "sunshine/main.h" - -namespace platf { -using namespace std::literals; -} - -namespace platf::dxgi { -struct img_t : public ::platf::img_t { - ~img_t() override { - delete[] data; - data = nullptr; - } -}; - -void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { - int height = cursor.shape_info.Height / 2; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.{x,y} < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto pixels_per_byte = width / pitch; - auto bytes_per_row = delta_width / pixels_per_byte; - - auto img_data = (int *)img.data; - for(int i = 0; i < delta_height; ++i) { - auto and_mask = &cursor_img_data[i * pitch]; - auto xor_mask = &cursor_img_data[(i + height) * pitch]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - - auto skip_x = cursor_skip_x; - for(int x = 0; x < bytes_per_row; ++x) { - for(auto bit = 0u; bit < 8; ++bit) { - if(skip_x > 0) { - --skip_x; - - continue; - } - - int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; - int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; - - *img_pixel_p &= and_; - *img_pixel_p ^= xor_; - - ++img_pixel_p; - } - - ++and_mask; - ++xor_mask; - } - } -} - -void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { - auto colors_out = (std::uint8_t *)&cursor_pixel; - auto colors_in = (std::uint8_t *)img_pixel_p; - - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = colors_out[3]; - if(alpha == 255) { - *img_pixel_p = cursor_pixel; - } - else { - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; - } -} - -void apply_color_masked(int *img_pixel_p, int cursor_pixel) { - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = ((std::uint8_t *)&cursor_pixel)[3]; - if(alpha == 0xFF) { - *img_pixel_p ^= cursor_pixel; - } - else { - *img_pixel_p = cursor_pixel; - } -} - -void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { - int height = cursor.shape_info.Height; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.y < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch]; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto img_data = (int *)img.data; - - for(int i = 0; i < delta_height; ++i) { - auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; - auto cursor_end = &cursor_begin[delta_width]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { - if(masked) { - apply_color_masked(img_pixel_p, cursor_pixel); - } - else { - apply_color_alpha(img_pixel_p, cursor_pixel); - } - ++img_pixel_p; - }); - } -} - -void blend_cursor(const cursor_t &cursor, img_t &img) { - switch(cursor.shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - blend_cursor_color(cursor, img, false); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: - blend_cursor_monochrome(cursor, img); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - blend_cursor_color(cursor, img, true); - break; - default: - BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; - } -} - -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: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return 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; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - if(frame_info.PointerShapeBufferSize > 0) { - auto &img_data = cursor.img_data; - - img_data.resize(frame_info.PointerShapeBufferSize); - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.x = frame_info.PointerPosition.Position.x; - cursor.y = frame_info.PointerPosition.Position.y; - cursor.visible = frame_info.PointerPosition.Visible; - } - - // If frame has been updated - if(frame_info.LastPresentTime.QuadPart != 0) { - { - texture2d_t src {}; - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - //Copy from GPU to CPU - device_ctx->CopyResource(texture.get(), src.get()); - } - - if(img_info.pData) { - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - } - - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - const bool mouse_update = - (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && - (cursor_visible && cursor.visible); - - const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; - - if(!update_flag) { - return capture_e::timeout; - } - - std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); - - if(cursor_visible && cursor.visible) { - blend_cursor(cursor, *img); - } - - return capture_e::ok; -} - -std::shared_ptr display_ram_t::alloc_img() { - auto img = std::make_shared(); - - img->pixel_pitch = 4; - img->row_pitch = img_info.RowPitch; - img->width = width; - img->height = height; - img->data = new std::uint8_t[img->row_pitch * height]; - - return img; -} - -int display_ram_t::dummy_img(platf::img_t *img) { - return 0; -} - -int display_ram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - return -1; - } - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_STAGING; - t.Format = format; - t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - - auto status = device->CreateTexture2D(&t, nullptr, &texture); - - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - // map the texture simply to get the pitch and stride - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; -} -} // namespace platf::dxgi +#include "display.h" +#include "src/main.h" + +namespace platf { +using namespace std::literals; +} + +namespace platf::dxgi { +struct img_t : public ::platf::img_t { + ~img_t() override { + delete[] data; + data = nullptr; + } +}; + +void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { + int height = cursor.shape_info.Height / 2; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.{x,y} < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto pixels_per_byte = width / pitch; + auto bytes_per_row = delta_width / pixels_per_byte; + + auto img_data = (int *)img.data; + for(int i = 0; i < delta_height; ++i) { + auto and_mask = &cursor_img_data[i * pitch]; + auto xor_mask = &cursor_img_data[(i + height) * pitch]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + + auto skip_x = cursor_skip_x; + for(int x = 0; x < bytes_per_row; ++x) { + for(auto bit = 0u; bit < 8; ++bit) { + if(skip_x > 0) { + --skip_x; + + continue; + } + + int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; + int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; + + *img_pixel_p &= and_; + *img_pixel_p ^= xor_; + + ++img_pixel_p; + } + + ++and_mask; + ++xor_mask; + } + } +} + +void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { + auto colors_out = (std::uint8_t *)&cursor_pixel; + auto colors_in = (std::uint8_t *)img_pixel_p; + + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = colors_out[3]; + if(alpha == 255) { + *img_pixel_p = cursor_pixel; + } + else { + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255; + } +} + +void apply_color_masked(int *img_pixel_p, int cursor_pixel) { + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = ((std::uint8_t *)&cursor_pixel)[3]; + if(alpha == 0xFF) { + *img_pixel_p ^= cursor_pixel; + } + else { + *img_pixel_p = cursor_pixel; + } +} + +void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { + int height = cursor.shape_info.Height; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.y < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch]; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto img_data = (int *)img.data; + + for(int i = 0; i < delta_height; ++i) { + auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; + auto cursor_end = &cursor_begin[delta_width]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { + if(masked) { + apply_color_masked(img_pixel_p, cursor_pixel); + } + else { + apply_color_alpha(img_pixel_p, cursor_pixel); + } + ++img_pixel_p; + }); + } +} + +void blend_cursor(const cursor_t &cursor, img_t &img) { + switch(cursor.shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + blend_cursor_color(cursor, img, false); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: + blend_cursor_monochrome(cursor, img); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + blend_cursor_color(cursor, img, true); + break; + default: + BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; + } +} + +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: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return 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; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + if(frame_info.PointerShapeBufferSize > 0) { + auto &img_data = cursor.img_data; + + img_data.resize(frame_info.PointerShapeBufferSize); + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.x = frame_info.PointerPosition.Position.x; + cursor.y = frame_info.PointerPosition.Position.y; + cursor.visible = frame_info.PointerPosition.Visible; + } + + // If frame has been updated + if(frame_info.LastPresentTime.QuadPart != 0) { + { + texture2d_t src {}; + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + //Copy from GPU to CPU + device_ctx->CopyResource(texture.get(), src.get()); + } + + if(img_info.pData) { + device_ctx->Unmap(texture.get(), 0); + img_info.pData = nullptr; + } + + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + const bool mouse_update = + (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && + (cursor_visible && cursor.visible); + + const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; + + if(!update_flag) { + return capture_e::timeout; + } + + std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data); + + if(cursor_visible && cursor.visible) { + blend_cursor(cursor, *img); + } + + return capture_e::ok; +} + +std::shared_ptr display_ram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img_info.RowPitch; + img->width = width; + img->height = height; + img->data = new std::uint8_t[img->row_pitch * height]; + + return img; +} + +int display_ram_t::dummy_img(platf::img_t *img) { + return 0; +} + +int display_ram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + return -1; + } + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_STAGING; + t.Format = format; + t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + auto status = device->CreateTexture2D(&t, nullptr, &texture); + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // map the texture simply to get the pitch and stride + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} +} // namespace platf::dxgi diff --git a/sunshine/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp similarity index 96% rename from sunshine/platform/windows/display_vram.cpp rename to src/platform/windows/display_vram.cpp index 9a932665..3cf7c977 100644 --- a/sunshine/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -1,888 +1,888 @@ -#include - -#include - -#include -#include - -extern "C" { -#include -#include -} - -#include "display.h" -#include "sunshine/main.h" -#include "sunshine/video.h" - - -#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" -namespace platf { -using namespace std::literals; -} - -static void free_frame(AVFrame *frame) { - av_frame_free(&frame); -} - -using frame_t = util::safe_ptr; - -namespace platf::dxgi { - -template -buf_t make_buffer(device_t::pointer device, const T &t) { - static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); - - D3D11_BUFFER_DESC buffer_desc { - sizeof(T), - D3D11_USAGE_IMMUTABLE, - D3D11_BIND_CONSTANT_BUFFER - }; - - D3D11_SUBRESOURCE_DATA init_data { - &t - }; - - buf_t::pointer buf_p; - auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); - if(status) { - BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return buf_t { buf_p }; -} - -blend_t make_blend(device_t::pointer device, bool enable) { - D3D11_BLEND_DESC bdesc {}; - auto &rt = bdesc.RenderTarget[0]; - rt.BlendEnable = enable; - rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - - if(enable) { - rt.BlendOp = D3D11_BLEND_OP_ADD; - rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; - - rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; - rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - - rt.SrcBlendAlpha = D3D11_BLEND_ZERO; - rt.DestBlendAlpha = D3D11_BLEND_ZERO; - } - - blend_t blend; - auto status = device->CreateBlendState(&bdesc, &blend); - if(status) { - BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return blend; -} - -blob_t convert_UV_vs_hlsl; -blob_t convert_UV_ps_hlsl; -blob_t scene_vs_hlsl; -blob_t convert_Y_ps_hlsl; -blob_t scene_ps_hlsl; - -struct img_d3d_t : public platf::img_t { - std::shared_ptr display; - - shader_res_t input_res; - render_target_t scene_rt; - - texture2d_t texture; - - ~img_d3d_t() override = default; -}; - -util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { - constexpr std::uint32_t black = 0xFF000000; - constexpr std::uint32_t white = 0xFFFFFFFF; - constexpr std::uint32_t transparent = 0; - - switch(shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) { - if(pixel & 0xFF000000) { - pixel = transparent; - } - }); - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - return std::move(img_data); - default: - break; - } - - shape_info.Height /= 2; - - util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; - - auto bytes = shape_info.Pitch * shape_info.Height; - auto pixel_begin = (std::uint32_t *)std::begin(cursor_img); - auto pixel_data = pixel_begin; - auto and_mask = std::begin(img_data); - auto xor_mask = std::begin(img_data) + bytes; - - for(auto x = 0; x < bytes; ++x) { - for(auto c = 7; c >= 0; --c) { - auto bit = 1 << c; - auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); - - switch(color_type) { - case 0: //black - *pixel_data = black; - break; - case 2: //white - *pixel_data = white; - break; - case 1: //transparent - { - *pixel_data = transparent; - - break; - } - case 3: //inverse - { - auto top_p = pixel_data - shape_info.Width; - auto left_p = pixel_data - 1; - auto right_p = pixel_data + 1; - auto bottom_p = pixel_data + shape_info.Width; - - // Get the x coordinate of the pixel - auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; - - if(top_p >= pixel_begin && *top_p == transparent) { - *top_p = black; - } - - if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { - *left_p = black; - } - - if(bottom_p < (std::uint32_t *)std::end(cursor_img)) { - *bottom_p = black; - } - - if(column != shape_info.Width - 1) { - *right_p = black; - } - *pixel_data = white; - } - } - - ++pixel_data; - } - ++and_mask; - ++xor_mask; - } - - return cursor_img; -} - -blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { - blob_t::pointer msg_p = nullptr; - blob_t::pointer compiled_p; - - DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS; - -#ifndef NDEBUG - flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; -#endif - std::wstring_convert, wchar_t> converter; - - auto wFile = converter.from_bytes(file); - auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); - - if(msg_p) { - BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 }; - msg_p->Release(); - } - - if(status) { - BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - return blob_t { compiled_p }; -} - -blob_t compile_pixel_shader(LPCSTR file) { - return compile_shader(file, "main_ps", "ps_5_0"); -} - -blob_t compile_vertex_shader(LPCSTR file) { - return compile_shader(file, "main_vs", "vs_5_0"); -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { - D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { - format, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - shader_resource_desc.Texture2D.MipLevels = 1; - - auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { - format, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target); - if(status) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; -} - -int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { - D3D11_TEXTURE2D_DESC desc {}; - - desc.Width = width; - desc.Height = height; - desc.Format = format; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.SampleDesc.Count = 1; - - texture2d_t tex; - auto status = device->CreateTexture2D(&desc, nullptr, &tex); - if(status) { - BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return init_rt(device, shader_res, render_target, width, height, format, tex.get()); -} - -class hwdevice_t : public platf::hwdevice_t { -public: - int convert(platf::img_t &img_base) override { - auto &img = (img_d3d_t &)img_base; - - device_ctx_p->IASetInputLayout(input_layout.get()); - - _init_view_port(this->img.width, this->img.height); - device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); - device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); - device_ctx_p->Draw(3, 0); - - device_ctx_p->RSSetViewports(1, &outY_view); - device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); - device_ctx_p->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_p->Flush(); - - _init_view_port(this->img.width / 2, this->img.height / 2); - device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); - device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); - device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); - device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); - device_ctx_p->Draw(3, 0); - - device_ctx_p->RSSetViewports(1, &outUV_view); - device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); - device_ctx_p->Draw(3, 0); - device_ctx_p->Flush(); - - return 0; - } - - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - switch(colorspace) { - case 5: // SWS_CS_SMPTE170M - color_p = &::video::colors[0]; - break; - case 1: // SWS_CS_ITU709 - color_p = &::video::colors[2]; - break; - case 9: // SWS_CS_BT2020 - default: - BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; - color_p = &::video::colors[0]; - }; - - if(color_range > 1) { - // Full range - ++color_p; - } - - auto color_matrix = make_buffer((device_t::pointer)data, *color_p); - if(!color_matrix) { - BOOST_LOG(warning) << "Failed to create color matrix"sv; - return; - } - - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - this->color_matrix = std::move(color_matrix); - } - - int set_frame(AVFrame *frame) { - this->hwframe.reset(frame); - this->frame = frame; - - auto device_p = (device_t::pointer)data; - - auto out_width = frame->width; - auto out_height = frame->height; - - float in_width = img.display->width; - float in_height = img.display->height; - - // // 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; - - // result is always positive - auto offsetX = (out_width - out_width_f) / 2; - auto offsetY = (out_height - out_height_f) / 2; - - 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_p->CreateTexture2D(&t, nullptr, &img.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.texture.get(); - img.row_pitch = out_width * 4; - img.pixel_pitch = 4; - - float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte - info_scene = make_buffer(device_p, info_in); - - if(!info_in) { - BOOST_LOG(error) << "Failed to create info scene buffer"sv; - return -1; - } - - D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { - DXGI_FORMAT_R8_UNORM, - D3D11_RTV_DIMENSION_TEXTURE2D - }; - - status = device_p->CreateRenderTargetView(img.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; - } - - nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; - - status = device_p->CreateRenderTargetView(img.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; - - return 0; - } - - int init( - std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, - pix_fmt_e pix_fmt) { - - HRESULT status; - - device_p->AddRef(); - data = device_p; - - this->device_ctx_p = device_ctx_p; - - format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); - status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device_p->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_p->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_p->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_p->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; - } - - color_matrix = make_buffer(device_p, ::video::colors[0]); - if(!color_matrix) { - BOOST_LOG(error) << "Failed to create color matrix buffer"sv; - return -1; - } - - D3D11_INPUT_ELEMENT_DESC layout_desc { - "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 - }; - - status = device_p->CreateInputLayout( - &layout_desc, 1, - 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 - if(img.display->dummy_img(&back_img)) { - BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; - return -1; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - device_ctx_p->IASetInputLayout(input_layout.get()); - device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); - device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); - - return 0; - } - - ~hwdevice_t() override { - if(data) { - ((ID3D11Device *)data)->Release(); - } - } - -private: - void _init_view_port(float x, float y, float width, float height) { - D3D11_VIEWPORT view { - x, y, - width, height, - 0.0f, 1.0f - }; - - device_ctx_p->RSSetViewports(1, &view); - } - - void _init_view_port(float width, float height) { - _init_view_port(0.0f, 0.0f, width, height); - } - -public: - frame_t hwframe; - - ::video::color_t *color_p; - - buf_t info_scene; - buf_t color_matrix; - - input_layout_t input_layout; - - render_target_t nv12_Y_rt; - render_target_t nv12_UV_rt; - - // The image referenced by hwframe - // The resulting image is stored here. - img_d3d_t img; - - // Clear nv12 render target to black - img_d3d_t back_img; - - 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; - D3D11_VIEWPORT outUV_view; - - DXGI_FORMAT format; - - device_ctx_t::pointer device_ctx_p; -}; - -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: - std::this_thread::sleep_for(1ms); - continue; - case platf::capture_e::ok: - img = snapshot_cb(img); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - -capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { - auto img = (img_d3d_t *)img_base; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, timeout, &res_p); - resource_t res { res_p }; - - if(capture_status != capture_e::ok) { - return capture_status; - } - - const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; - const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; - const bool update_flag = mouse_update_flag || frame_update_flag; - - if(!update_flag) { - return capture_e::timeout; - } - - if(frame_info.PointerShapeBufferSize > 0) { - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; - - util::buffer_t img_data { frame_info.PointerShapeBufferSize }; - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - - auto cursor_img = make_cursor_image(std::move(img_data), shape_info); - - D3D11_SUBRESOURCE_DATA data { - std::begin(cursor_img), - 4 * shape_info.Width, - 0 - }; - - // Create texture for cursor - D3D11_TEXTURE2D_DESC t {}; - t.Width = shape_info.Width; - t.Height = cursor_img.size() / data.SysMemPitch; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - texture2d_t texture; - auto status = device->CreateTexture2D(&t, &data, &texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - D3D11_SHADER_RESOURCE_VIEW_DESC desc { - DXGI_FORMAT_B8G8R8A8_UNORM, - D3D11_SRV_DIMENSION_TEXTURE2D - }; - desc.Texture2D.MipLevels = 1; - - // Free resources before allocating on the next line. - cursor.input_res.reset(); - status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - cursor.set_texture(t.Width, t.Height, std::move(texture)); - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); - } - - if(frame_update_flag) { - src.reset(); - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - } - - device_ctx->CopyResource(img->texture.get(), src.get()); - if(cursor.visible) { - D3D11_VIEWPORT view { - 0.0f, 0.0f, - (float)width, (float)height, - 0.0f, 1.0f - }; - - device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); - device_ctx->RSSetViewports(1, &view); - device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); - device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); - device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->RSSetViewports(1, &cursor.cursor_view); - device_ctx->Draw(3, 0); - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - } - - return capture_e::ok; -} - -int display_vram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { - return -1; - } - - D3D11_SAMPLER_DESC sampler_desc {}; - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; - sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; - sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; - sampler_desc.MinLOD = 0; - sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; - - auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if(status) { - BOOST_LOG(error) << "Failed to create scene 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; - } - - blend_enable = make_blend(device.get(), true); - blend_disable = make_blend(device.get(), false); - - if(!blend_disable || !blend_enable) { - return -1; - } - - device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); - device_ctx->PSSetSamplers(0, 1, &sampler_linear); - device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - - return 0; -} - -std::shared_ptr display_vram_t::alloc_img() { - auto img = std::make_shared(); - - img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; - img->width = width; - img->height = height; - img->display = shared_from_this(); - - auto dummy_data = std::make_unique(img->row_pitch * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), img->row_pitch * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; - - auto status = device->CreateTexture2D(&t, &data, &img->texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; - return nullptr; - } - - if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { - return nullptr; - } - - img->data = (std::uint8_t *)img->texture.get(); - - return img; -} - -int display_vram_t::dummy_img(platf::img_t *img_base) { - auto img = (img_d3d_t *)img_base; - - if(img->texture) { - return 0; - } - - img->row_pitch = width * 4; - auto dummy_data = std::make_unique(width * height); - D3D11_SUBRESOURCE_DATA data { - dummy_data.get(), - (UINT)img->row_pitch - }; - std::fill_n(dummy_data.get(), width * height, 0); - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - dxgi::texture2d_t tex; - auto status = device->CreateTexture2D(&t, &data, &tex); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - img->texture = std::move(tex); - img->data = (std::uint8_t *)img->texture.get(); - - return 0; -} - -std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { - if(pix_fmt != platf::pix_fmt_e::nv12) { - BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; - - return nullptr; - } - - auto hwdevice = std::make_shared(); - - auto ret = hwdevice->init( - shared_from_this(), - device.get(), - device_ctx.get(), - pix_fmt); - - if(ret) { - return nullptr; - } - - return hwdevice; -} - -int init() { - BOOST_LOG(info) << "Compiling shaders..."sv; - scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); - if(!scene_vs_hlsl) { - return -1; - } - - convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); - if(!convert_Y_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_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); - if(!convert_UV_vs_hlsl) { - return -1; - } - - scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl"); - if(!scene_ps_hlsl) { - return -1; - } - BOOST_LOG(info) << "Compiled shaders"sv; - - return 0; -} +#include + +#include + +#include +#include + +extern "C" { +#include +#include +} + +#include "display.h" +#include "src/main.h" +#include "src/video.h" + + +#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" +namespace platf { +using namespace std::literals; +} + +static void free_frame(AVFrame *frame) { + av_frame_free(&frame); +} + +using frame_t = util::safe_ptr; + +namespace platf::dxgi { + +template +buf_t make_buffer(device_t::pointer device, const T &t) { + static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); + + D3D11_BUFFER_DESC buffer_desc { + sizeof(T), + D3D11_USAGE_IMMUTABLE, + D3D11_BIND_CONSTANT_BUFFER + }; + + D3D11_SUBRESOURCE_DATA init_data { + &t + }; + + buf_t::pointer buf_p; + auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p); + if(status) { + BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return buf_t { buf_p }; +} + +blend_t make_blend(device_t::pointer device, bool enable) { + D3D11_BLEND_DESC bdesc {}; + auto &rt = bdesc.RenderTarget[0]; + rt.BlendEnable = enable; + rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + if(enable) { + rt.BlendOp = D3D11_BLEND_OP_ADD; + rt.BlendOpAlpha = D3D11_BLEND_OP_ADD; + + rt.SrcBlend = D3D11_BLEND_SRC_ALPHA; + rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + + rt.SrcBlendAlpha = D3D11_BLEND_ZERO; + rt.DestBlendAlpha = D3D11_BLEND_ZERO; + } + + blend_t blend; + auto status = device->CreateBlendState(&bdesc, &blend); + if(status) { + BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blend; +} + +blob_t convert_UV_vs_hlsl; +blob_t convert_UV_ps_hlsl; +blob_t scene_vs_hlsl; +blob_t convert_Y_ps_hlsl; +blob_t scene_ps_hlsl; + +struct img_d3d_t : public platf::img_t { + std::shared_ptr display; + + shader_res_t input_res; + render_target_t scene_rt; + + texture2d_t texture; + + ~img_d3d_t() override = default; +}; + +util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { + constexpr std::uint32_t black = 0xFF000000; + constexpr std::uint32_t white = 0xFFFFFFFF; + constexpr std::uint32_t transparent = 0; + + switch(shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) { + if(pixel & 0xFF000000) { + pixel = transparent; + } + }); + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + return std::move(img_data); + default: + break; + } + + shape_info.Height /= 2; + + util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; + + auto bytes = shape_info.Pitch * shape_info.Height; + auto pixel_begin = (std::uint32_t *)std::begin(cursor_img); + auto pixel_data = pixel_begin; + auto and_mask = std::begin(img_data); + auto xor_mask = std::begin(img_data) + bytes; + + for(auto x = 0; x < bytes; ++x) { + for(auto c = 7; c >= 0; --c) { + auto bit = 1 << c; + auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); + + switch(color_type) { + case 0: //black + *pixel_data = black; + break; + case 2: //white + *pixel_data = white; + break; + case 1: //transparent + { + *pixel_data = transparent; + + break; + } + case 3: //inverse + { + auto top_p = pixel_data - shape_info.Width; + auto left_p = pixel_data - 1; + auto right_p = pixel_data + 1; + auto bottom_p = pixel_data + shape_info.Width; + + // Get the x coordinate of the pixel + auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; + + if(top_p >= pixel_begin && *top_p == transparent) { + *top_p = black; + } + + if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { + *left_p = black; + } + + if(bottom_p < (std::uint32_t *)std::end(cursor_img)) { + *bottom_p = black; + } + + if(column != shape_info.Width - 1) { + *right_p = black; + } + *pixel_data = white; + } + } + + ++pixel_data; + } + ++and_mask; + ++xor_mask; + } + + return cursor_img; +} + +blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { + blob_t::pointer msg_p = nullptr; + blob_t::pointer compiled_p; + + DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS; + +#ifndef NDEBUG + flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#endif + std::wstring_convert, wchar_t> converter; + + auto wFile = converter.from_bytes(file); + auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); + + if(msg_p) { + BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 }; + msg_p->Release(); + } + + if(status) { + BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + return blob_t { compiled_p }; +} + +blob_t compile_pixel_shader(LPCSTR file) { + return compile_shader(file, "main_ps", "ps_5_0"); +} + +blob_t compile_vertex_shader(LPCSTR file) { + return compile_shader(file, "main_vs", "vs_5_0"); +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) { + D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc { + format, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + shader_resource_desc.Texture2D.MipLevels = 1; + + auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + D3D11_RENDER_TARGET_VIEW_DESC render_target_desc { + format, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target); + if(status) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} + +int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) { + D3D11_TEXTURE2D_DESC desc {}; + + desc.Width = width; + desc.Height = height; + desc.Format = format; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + + texture2d_t tex; + auto status = device->CreateTexture2D(&desc, nullptr, &tex); + if(status) { + BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return init_rt(device, shader_res, render_target, width, height, format, tex.get()); +} + +class hwdevice_t : public platf::hwdevice_t { +public: + int convert(platf::img_t &img_base) override { + auto &img = (img_d3d_t &)img_base; + + device_ctx_p->IASetInputLayout(input_layout.get()); + + _init_view_port(this->img.width, this->img.height); + device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); + device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->Draw(3, 0); + + device_ctx_p->RSSetViewports(1, &outY_view); + device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); + device_ctx_p->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_p->Flush(); + + _init_view_port(this->img.width / 2, this->img.height / 2); + device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); + device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0); + device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0); + device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res); + device_ctx_p->Draw(3, 0); + + device_ctx_p->RSSetViewports(1, &outUV_view); + device_ctx_p->PSSetShaderResources(0, 1, &img.input_res); + device_ctx_p->Draw(3, 0); + device_ctx_p->Flush(); + + return 0; + } + + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { + switch(colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &::video::colors[0]; + break; + case 1: // SWS_CS_ITU709 + color_p = &::video::colors[2]; + break; + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &::video::colors[0]; + }; + + if(color_range > 1) { + // Full range + ++color_p; + } + + auto color_matrix = make_buffer((device_t::pointer)data, *color_p); + if(!color_matrix) { + BOOST_LOG(warning) << "Failed to create color matrix"sv; + return; + } + + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + this->color_matrix = std::move(color_matrix); + } + + int set_frame(AVFrame *frame) { + this->hwframe.reset(frame); + this->frame = frame; + + auto device_p = (device_t::pointer)data; + + auto out_width = frame->width; + auto out_height = frame->height; + + float in_width = img.display->width; + float in_height = img.display->height; + + // // 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; + + // result is always positive + auto offsetX = (out_width - out_width_f) / 2; + auto offsetY = (out_height - out_height_f) / 2; + + 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_p->CreateTexture2D(&t, nullptr, &img.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.texture.get(); + img.row_pitch = out_width * 4; + img.pixel_pitch = 4; + + float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte + info_scene = make_buffer(device_p, info_in); + + if(!info_in) { + BOOST_LOG(error) << "Failed to create info scene buffer"sv; + return -1; + } + + D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { + DXGI_FORMAT_R8_UNORM, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + status = device_p->CreateRenderTargetView(img.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; + } + + nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM; + + status = device_p->CreateRenderTargetView(img.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; + + return 0; + } + + int init( + std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, + pix_fmt_e pix_fmt) { + + HRESULT status; + + device_p->AddRef(); + data = device_p; + + this->device_ctx_p = device_ctx_p; + + format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); + status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device_p->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_p->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_p->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_p->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; + } + + color_matrix = make_buffer(device_p, ::video::colors[0]); + if(!color_matrix) { + BOOST_LOG(error) << "Failed to create color matrix buffer"sv; + return -1; + } + + D3D11_INPUT_ELEMENT_DESC layout_desc { + "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 + }; + + status = device_p->CreateInputLayout( + &layout_desc, 1, + 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 + if(img.display->dummy_img(&back_img)) { + BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; + return -1; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + device_ctx_p->IASetInputLayout(input_layout.get()); + device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix); + device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene); + + return 0; + } + + ~hwdevice_t() override { + if(data) { + ((ID3D11Device *)data)->Release(); + } + } + +private: + void _init_view_port(float x, float y, float width, float height) { + D3D11_VIEWPORT view { + x, y, + width, height, + 0.0f, 1.0f + }; + + device_ctx_p->RSSetViewports(1, &view); + } + + void _init_view_port(float width, float height) { + _init_view_port(0.0f, 0.0f, width, height); + } + +public: + frame_t hwframe; + + ::video::color_t *color_p; + + buf_t info_scene; + buf_t color_matrix; + + input_layout_t input_layout; + + render_target_t nv12_Y_rt; + render_target_t nv12_UV_rt; + + // The image referenced by hwframe + // The resulting image is stored here. + img_d3d_t img; + + // Clear nv12 render target to black + img_d3d_t back_img; + + 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; + D3D11_VIEWPORT outUV_view; + + DXGI_FORMAT format; + + device_ctx_t::pointer device_ctx_p; +}; + +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: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + +capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_d3d_t *)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res { res_p }; + + if(capture_status != capture_e::ok) { + return capture_status; + } + + const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; + const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; + const bool update_flag = mouse_update_flag || frame_update_flag; + + if(!update_flag) { + return capture_e::timeout; + } + + if(frame_info.PointerShapeBufferSize > 0) { + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; + + util::buffer_t img_data { frame_info.PointerShapeBufferSize }; + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + + auto cursor_img = make_cursor_image(std::move(img_data), shape_info); + + D3D11_SUBRESOURCE_DATA data { + std::begin(cursor_img), + 4 * shape_info.Width, + 0 + }; + + // Create texture for cursor + D3D11_TEXTURE2D_DESC t {}; + t.Width = shape_info.Width; + t.Height = cursor_img.size() / data.SysMemPitch; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + texture2d_t texture; + auto status = device->CreateTexture2D(&t, &data, &texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC desc { + DXGI_FORMAT_B8G8R8A8_UNORM, + D3D11_SRV_DIMENSION_TEXTURE2D + }; + desc.Texture2D.MipLevels = 1; + + // Free resources before allocating on the next line. + cursor.input_res.reset(); + status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + cursor.set_texture(t.Width, t.Height, std::move(texture)); + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); + } + + if(frame_update_flag) { + src.reset(); + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src); + + if(FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + } + + device_ctx->CopyResource(img->texture.get(), src.get()); + if(cursor.visible) { + D3D11_VIEWPORT view { + 0.0f, 0.0f, + (float)width, (float)height, + 0.0f, 1.0f + }; + + device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx->PSSetShader(scene_ps.get(), nullptr, 0); + device_ctx->RSSetViewports(1, &view); + device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr); + device_ctx->PSSetShaderResources(0, 1, &cursor.input_res); + device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->RSSetViewports(1, &cursor.cursor_view); + device_ctx->Draw(3, 0); + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + } + + return capture_e::ok; +} + +int display_vram_t::init(int framerate, const std::string &display_name) { + if(display_base_t::init(framerate, display_name)) { + return -1; + } + + D3D11_SAMPLER_DESC sampler_desc {}; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + sampler_desc.MinLOD = 0; + sampler_desc.MaxLOD = D3D11_FLOAT32_MAX; + + auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); + if(status) { + BOOST_LOG(error) << "Failed to create scene 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; + } + + blend_enable = make_blend(device.get(), true); + blend_disable = make_blend(device.get(), false); + + if(!blend_disable || !blend_enable) { + return -1; + } + + device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); + device_ctx->PSSetSamplers(0, 1, &sampler_linear); + device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + return 0; +} + +std::shared_ptr display_vram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->width = width; + img->height = height; + img->display = shared_from_this(); + + auto dummy_data = std::make_unique(img->row_pitch * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), img->row_pitch * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + + auto status = device->CreateTexture2D(&t, &data, &img->texture); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) { + return nullptr; + } + + img->data = (std::uint8_t *)img->texture.get(); + + return img; +} + +int display_vram_t::dummy_img(platf::img_t *img_base) { + auto img = (img_d3d_t *)img_base; + + if(img->texture) { + return 0; + } + + img->row_pitch = width * 4; + auto dummy_data = std::make_unique(width * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch + }; + std::fill_n(dummy_data.get(), width * height, 0); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + dxgi::texture2d_t tex; + auto status = device->CreateTexture2D(&t, &data, &tex); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + img->texture = std::move(tex); + img->data = (std::uint8_t *)img->texture.get(); + + return 0; +} + +std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { + if(pix_fmt != platf::pix_fmt_e::nv12) { + BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']'; + + return nullptr; + } + + auto hwdevice = std::make_shared(); + + auto ret = hwdevice->init( + shared_from_this(), + device.get(), + device_ctx.get(), + pix_fmt); + + if(ret) { + return nullptr; + } + + return hwdevice; +} + +int init() { + BOOST_LOG(info) << "Compiling shaders..."sv; + scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl"); + if(!scene_vs_hlsl) { + return -1; + } + + convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl"); + if(!convert_Y_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_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); + if(!convert_UV_vs_hlsl) { + return -1; + } + + scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl"); + if(!scene_ps_hlsl) { + return -1; + } + BOOST_LOG(info) << "Compiled shaders"sv; + + return 0; +} } // namespace platf::dxgi \ No newline at end of file diff --git a/sunshine/platform/windows/input.cpp b/src/platform/windows/input.cpp old mode 100755 new mode 100644 similarity index 99% rename from sunshine/platform/windows/input.cpp rename to src/platform/windows/input.cpp index 88183f4b..9ac01a26 --- a/sunshine/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -5,9 +5,9 @@ #include #include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/platform/common.h" +#include "src/config.h" +#include "src/main.h" +#include "src/platform/common.h" namespace platf { using namespace std::literals; diff --git a/sunshine/platform/windows/misc.cpp b/src/platform/windows/misc.cpp similarity index 95% rename from sunshine/platform/windows/misc.cpp rename to src/platform/windows/misc.cpp index 828e0374..b9d08543 100644 --- a/sunshine/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1,123 +1,123 @@ -#include -#include -#include - - -// prevent clang format from "optimizing" the header include order -// clang-format off -#include -#include -#include -#include -#include -// clang-format on - -#include "sunshine/main.h" -#include "sunshine/utility.h" - -using namespace std::literals; -namespace platf { -using adapteraddrs_t = util::c_ptr; - -std::filesystem::path appdata() { - return L"."sv; -} - -std::string from_sockaddr(const sockaddr *const socket_address) { - char data[INET6_ADDRSTRLEN]; - - auto family = socket_address->sa_family; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); - } - - return std::string { data }; -} - -std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { - char data[INET6_ADDRSTRLEN]; - - auto family = ip_addr->sa_family; - std::uint16_t port; - if(family == AF_INET6) { - inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); - port = ((sockaddr_in6 *)ip_addr)->sin6_port; - } - - if(family == AF_INET) { - inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); - port = ((sockaddr_in *)ip_addr)->sin_port; - } - - return { port, std::string { data } }; -} - -adapteraddrs_t get_adapteraddrs() { - adapteraddrs_t info { nullptr }; - ULONG size = 0; - - while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { - info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); - } - - return info; -} - -std::string get_mac_address(const std::string_view &address) { - adapteraddrs_t info = get_adapteraddrs(); - for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { - for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { - if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { - std::stringstream mac_addr; - mac_addr << std::hex; - for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { - if(i > 0) { - mac_addr << ':'; - } - mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; - } - return mac_addr.str(); - } - } - } - BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; - return "00:00:00:00:00:00"s; -} - -HDESK syncThreadDesktop() { - auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); - if(!hDesk) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; - - return nullptr; - } - - if(!SetThreadDesktop(hDesk)) { - auto err = GetLastError(); - BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; - } - - CloseDesktop(hDesk); - - return hDesk; -} - -void print_status(const std::string_view &prefix, HRESULT status) { - char err_string[1024]; - - DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, - status, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - err_string, - sizeof(err_string), - nullptr); - - BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; -} +#include +#include +#include + + +// prevent clang format from "optimizing" the header include order +// clang-format off +#include +#include +#include +#include +#include +// clang-format on + +#include "src/main.h" +#include "src/utility.h" + +using namespace std::literals; +namespace platf { +using adapteraddrs_t = util::c_ptr; + +std::filesystem::path appdata() { + return L"."sv; +} + +std::string from_sockaddr(const sockaddr *const socket_address) { + char data[INET6_ADDRSTRLEN]; + + auto family = socket_address->sa_family; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN); + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN); + } + + return std::string { data }; +} + +std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { + char data[INET6_ADDRSTRLEN]; + + auto family = ip_addr->sa_family; + std::uint16_t port; + if(family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); + port = ((sockaddr_in6 *)ip_addr)->sin6_port; + } + + if(family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); + port = ((sockaddr_in *)ip_addr)->sin_port; + } + + return { port, std::string { data } }; +} + +adapteraddrs_t get_adapteraddrs() { + adapteraddrs_t info { nullptr }; + ULONG size = 0; + + while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) { + info.reset((PIP_ADAPTER_ADDRESSES)malloc(size)); + } + + return info; +} + +std::string get_mac_address(const std::string_view &address) { + adapteraddrs_t info = get_adapteraddrs(); + for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) { + for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) { + if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) { + std::stringstream mac_addr; + mac_addr << std::hex; + for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) { + if(i > 0) { + mac_addr << ':'; + } + mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i]; + } + return mac_addr.str(); + } + } + } + BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; + return "00:00:00:00:00:00"s; +} + +HDESK syncThreadDesktop() { + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if(!hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']'; + + return nullptr; + } + + if(!SetThreadDesktop(hDesk)) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']'; + } + + CloseDesktop(hDesk); + + return hDesk; +} + +void print_status(const std::string_view &prefix, HRESULT status) { + char err_string[1024]; + + DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + err_string, + sizeof(err_string), + nullptr); + + BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes }; +} } // namespace platf \ No newline at end of file diff --git a/sunshine/platform/windows/misc.h b/src/platform/windows/misc.h similarity index 95% rename from sunshine/platform/windows/misc.h rename to src/platform/windows/misc.h index 4cd8791f..a045d23b 100644 --- a/sunshine/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -1,13 +1,13 @@ -#ifndef SUNSHINE_WINDOWS_MISC_H -#define SUNSHINE_WINDOWS_MISC_H - -#include -#include -#include - -namespace platf { -void print_status(const std::string_view &prefix, HRESULT status); -HDESK syncThreadDesktop(); -} // namespace platf - +#ifndef SUNSHINE_WINDOWS_MISC_H +#define SUNSHINE_WINDOWS_MISC_H + +#include +#include +#include + +namespace platf { +void print_status(const std::string_view &prefix, HRESULT status); +HDESK syncThreadDesktop(); +} // namespace platf + #endif \ No newline at end of file diff --git a/sunshine/platform/windows/publish.cpp b/src/platform/windows/publish.cpp similarity index 92% rename from sunshine/platform/windows/publish.cpp rename to src/platform/windows/publish.cpp index 7fdef3fc..92673e1d 100644 --- a/sunshine/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -1,195 +1,195 @@ -#include - -#include - -#include -#include - -#include - -#include "misc.h" -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "sunshine/network.h" -#include "sunshine/nvhttp.h" -#include "sunshine/platform/common.h" -#include "sunshine/thread_safe.h" - -#define _FN(x, ret, args) \ - typedef ret(*x##_fn) args; \ - static x##_fn x - -using namespace std::literals; - -#define __SV(quote) L##quote##sv -#define SV(quote) __SV(quote) - -extern "C" { -#ifndef __MINGW32__ -constexpr auto DNS_REQUEST_PENDING = 9506L; -constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; -constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; -#endif - -#define SERVICE_DOMAIN "local" - -constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); -constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_INSTANCE { - LPWSTR pszInstanceName; - LPWSTR pszHostName; - - IP4_ADDRESS *ip4Address; - IP6_ADDRESS *ip6Address; - - WORD wPort; - WORD wPriority; - WORD wWeight; - - // Property list - DWORD dwPropertyCount; - - PWSTR *keys; - PWSTR *values; - - DWORD dwInterfaceIndex; -} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; -#endif - -typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( - _In_ DWORD Status, - _In_ PVOID pQueryContext, - _In_ PDNS_SERVICE_INSTANCE pInstance); - -typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; - -#ifndef __MINGW32__ -typedef struct _DNS_SERVICE_CANCEL { - PVOID reserved; -} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; - -typedef struct _DNS_SERVICE_REGISTER_REQUEST { - ULONG Version; - ULONG InterfaceIndex; - PDNS_SERVICE_INSTANCE pServiceInstance; - PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; - PVOID pQueryContext; - HANDLE hCredentials; - BOOL unicastEnabled; -} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; -#endif - -_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); -_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); -} /* extern "C" */ - -namespace platf::publish { -VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { - auto alarm = (safe::alarm_t::element_type *)pQueryContext; - - auto fg = util::fail_guard([&]() { - if(pInstance) { - _DnsServiceFreeInstance(pInstance); - } - }); - - if(status) { - print_status("register_cb()"sv, status); - alarm->ring(-1); - - return; - } - - alarm->ring(0); -} - -static int service(bool enable) { - auto alarm = safe::make_alarm(); - - std::wstring_convert, wchar_t> converter; - - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; - std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - - auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); - - DNS_SERVICE_INSTANCE instance {}; - instance.pszInstanceName = name.data(); - instance.wPort = map_port(nvhttp::PORT_HTTP); - instance.pszHostName = host.data(); - - DNS_SERVICE_REGISTER_REQUEST req {}; - req.Version = DNS_QUERY_REQUEST_VERSION1; - req.pQueryContext = alarm.get(); - req.pServiceInstance = &instance; - req.pRegisterCompletionCallback = register_cb; - - DNS_STATUS status {}; - - if(enable) { - status = _DnsServiceRegister(&req, nullptr); - } - else { - status = _DnsServiceDeRegister(&req, nullptr); - } - - alarm->wait(); - - status = *alarm->status(); - if(status) { - BOOST_LOG(error) << "No mDNS service"sv; - return -1; - } - - return 0; -} - -class deinit_t : public ::platf::deinit_t { -public: - ~deinit_t() override { - if(service(false)) { - std::abort(); - } - - BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; - } -}; - -int load_funcs(HMODULE handle) { - auto fg = util::fail_guard([handle]() { - FreeLibrary(handle); - }); - - _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); - _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); - _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); - - if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { - BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; - return -1; - } - - fg.disable(); - return 0; -} - -std::unique_ptr<::platf::deinit_t> start() { - HMODULE handle = LoadLibrary("dnsapi.dll"); - - if(!handle || load_funcs(handle)) { - BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; - return nullptr; - } - - if(service(true)) { - return nullptr; - } - - BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; - - return std::make_unique(); -} -} // namespace platf::publish +#include + +#include + +#include +#include + +#include + +#include "misc.h" +#include "src/config.h" +#include "src/main.h" +#include "src/network.h" +#include "src/nvhttp.h" +#include "src/platform/common.h" +#include "src/thread_safe.h" + +#define _FN(x, ret, args) \ + typedef ret(*x##_fn) args; \ + static x##_fn x + +using namespace std::literals; + +#define __SV(quote) L##quote##sv +#define SV(quote) __SV(quote) + +extern "C" { +#ifndef __MINGW32__ +constexpr auto DNS_REQUEST_PENDING = 9506L; +constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; +constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; +#endif + +#define SERVICE_DOMAIN "local" + +constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN); +constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN); + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_INSTANCE { + LPWSTR pszInstanceName; + LPWSTR pszHostName; + + IP4_ADDRESS *ip4Address; + IP6_ADDRESS *ip6Address; + + WORD wPort; + WORD wPriority; + WORD wWeight; + + // Property list + DWORD dwPropertyCount; + + PWSTR *keys; + PWSTR *values; + + DWORD dwInterfaceIndex; +} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; +#endif + +typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( + _In_ DWORD Status, + _In_ PVOID pQueryContext, + _In_ PDNS_SERVICE_INSTANCE pInstance); + +typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; + +#ifndef __MINGW32__ +typedef struct _DNS_SERVICE_CANCEL { + PVOID reserved; +} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; + +typedef struct _DNS_SERVICE_REGISTER_REQUEST { + ULONG Version; + ULONG InterfaceIndex; + PDNS_SERVICE_INSTANCE pServiceInstance; + PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; + PVOID pQueryContext; + HANDLE hCredentials; + BOOL unicastEnabled; +} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; +#endif + +_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); +_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel)); +} /* extern "C" */ + +namespace platf::publish { +VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { + auto alarm = (safe::alarm_t::element_type *)pQueryContext; + + auto fg = util::fail_guard([&]() { + if(pInstance) { + _DnsServiceFreeInstance(pInstance); + } + }); + + if(status) { + print_status("register_cb()"sv, status); + alarm->ring(-1); + + return; + } + + alarm->ring(0); +} + +static int service(bool enable) { + auto alarm = safe::make_alarm(); + + std::wstring_convert, wchar_t> converter; + + std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; + std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; + + auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); + + DNS_SERVICE_INSTANCE instance {}; + instance.pszInstanceName = name.data(); + instance.wPort = map_port(nvhttp::PORT_HTTP); + instance.pszHostName = host.data(); + + DNS_SERVICE_REGISTER_REQUEST req {}; + req.Version = DNS_QUERY_REQUEST_VERSION1; + req.pQueryContext = alarm.get(); + req.pServiceInstance = &instance; + req.pRegisterCompletionCallback = register_cb; + + DNS_STATUS status {}; + + if(enable) { + status = _DnsServiceRegister(&req, nullptr); + } + else { + status = _DnsServiceDeRegister(&req, nullptr); + } + + alarm->wait(); + + status = *alarm->status(); + if(status) { + BOOST_LOG(error) << "No mDNS service"sv; + return -1; + } + + return 0; +} + +class deinit_t : public ::platf::deinit_t { +public: + ~deinit_t() override { + if(service(false)) { + std::abort(); + } + + BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv; + } +}; + +int load_funcs(HMODULE handle) { + auto fg = util::fail_guard([handle]() { + FreeLibrary(handle); + }); + + _DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance"); + _DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister"); + _DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister"); + + if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) { + BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv; + return -1; + } + + fg.disable(); + return 0; +} + +std::unique_ptr<::platf::deinit_t> start() { + HMODULE handle = LoadLibrary("dnsapi.dll"); + + if(!handle || load_funcs(handle)) { + BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv; + return nullptr; + } + + if(service(true)) { + return nullptr; + } + + BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv; + + return std::make_unique(); +} +} // namespace platf::publish diff --git a/sunshine/platform/windows/windows.rs.in b/src/platform/windows/windows.rs.in similarity index 100% rename from sunshine/platform/windows/windows.rs.in rename to src/platform/windows/windows.rs.in diff --git a/sunshine/process.cpp b/src/process.cpp similarity index 100% rename from sunshine/process.cpp rename to src/process.cpp diff --git a/sunshine/process.h b/src/process.h similarity index 100% rename from sunshine/process.h rename to src/process.h diff --git a/sunshine/round_robin.h b/src/round_robin.h similarity index 100% rename from sunshine/round_robin.h rename to src/round_robin.h diff --git a/sunshine/rtsp.cpp b/src/rtsp.cpp similarity index 100% rename from sunshine/rtsp.cpp rename to src/rtsp.cpp diff --git a/sunshine/rtsp.h b/src/rtsp.h similarity index 100% rename from sunshine/rtsp.h rename to src/rtsp.h diff --git a/sunshine/stream.cpp b/src/stream.cpp similarity index 100% rename from sunshine/stream.cpp rename to src/stream.cpp diff --git a/sunshine/stream.h b/src/stream.h similarity index 100% rename from sunshine/stream.h rename to src/stream.h diff --git a/sunshine/sync.h b/src/sync.h similarity index 100% rename from sunshine/sync.h rename to src/sync.h diff --git a/sunshine/task_pool.h b/src/task_pool.h similarity index 100% rename from sunshine/task_pool.h rename to src/task_pool.h diff --git a/sunshine/thread_pool.h b/src/thread_pool.h similarity index 100% rename from sunshine/thread_pool.h rename to src/thread_pool.h diff --git a/sunshine/thread_safe.h b/src/thread_safe.h similarity index 100% rename from sunshine/thread_safe.h rename to src/thread_safe.h diff --git a/sunshine/upnp.cpp b/src/upnp.cpp similarity index 100% rename from sunshine/upnp.cpp rename to src/upnp.cpp diff --git a/sunshine/upnp.h b/src/upnp.h similarity index 100% rename from sunshine/upnp.h rename to src/upnp.h diff --git a/sunshine/utility.h b/src/utility.h similarity index 100% rename from sunshine/utility.h rename to src/utility.h diff --git a/sunshine/uuid.h b/src/uuid.h similarity index 100% rename from sunshine/uuid.h rename to src/uuid.h diff --git a/sunshine/video.cpp b/src/video.cpp similarity index 100% rename from sunshine/video.cpp rename to src/video.cpp diff --git a/sunshine/video.h b/src/video.h similarity index 100% rename from sunshine/video.h rename to src/video.h diff --git a/sunshine/platform/macos/TPCircularBuffer b/third-party/TPCircularBuffer similarity index 100% rename from sunshine/platform/macos/TPCircularBuffer rename to third-party/TPCircularBuffer diff --git a/tools/audio.cpp b/tools/audio.cpp index a130d384..592170b7 100644 --- a/tools/audio.cpp +++ b/tools/audio.cpp @@ -14,7 +14,7 @@ #include -#include "sunshine/utility.h" +#include "src/utility.h" DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING diff --git a/tools/dxgi.cpp b/tools/dxgi.cpp index 9c5f7307..7ecaaa2f 100644 --- a/tools/dxgi.cpp +++ b/tools/dxgi.cpp @@ -7,7 +7,7 @@ #include -#include "sunshine/utility.h" +#include "src/utility.h" using namespace std::literals; namespace dxgi {