Compare commits

...

130 Commits

Author SHA1 Message Date
loki
381e8bcfaa Prepare for release 2021-05-13 15:26:08 +02:00
loki
1050978246 Add warning to ignore errors during encoder validation 2021-05-12 23:22:13 +02:00
loki
9e48e58221 Allow applications started by sunshine to be detached 2021-05-12 23:01:30 +02:00
loki
5d313b509e fix resetting mouse buttons on end stream 2021-05-12 15:48:39 +02:00
loki
022b2202f6 Absolute mouse coordinates on Windows 2021-05-12 14:42:10 +02:00
loki
2e9a1cfbba absolute mouse coordinates regardless of the number of monitors attached on Linux 2021-05-11 23:51:45 +02:00
loki
1d84c8f9ce Correct dimensions for touchscreen when single monitor attached 2021-05-11 23:30:56 +02:00
loki
92cd8648fa Some refactoring for linux/display.cpp 2021-05-11 20:10:33 +02:00
loki
41cc9a3e80 absolute mouse coordinate support for single monitor on Linux 2021-05-11 18:01:56 +02:00
Loki
b97c902d10 print absolute mouse movement packets 2021-05-10 15:04:41 +02:00
loki
25309f21ee Fix appveyor build 2021-05-10 01:31:08 +02:00
loki
1ba8da9780 Fix error in README file 2021-05-10 01:07:24 +02:00
loki
66bc335d3f Merge branch 'master' of github.com:loki-47-6F-64/sunshine 2021-05-10 01:04:44 +02:00
loki
e834f375fb resolve merge conflict 2021-05-10 01:03:41 +02:00
loki
9c41972b65 minor modification for sunshine.service 2021-05-10 01:02:24 +02:00
loki-47-6F-64
41c30e9cfd Merge pull request #51 from Doomsdayrs/patch-1
Update README.md
2021-05-09 22:32:16 +02:00
loki
377b086882 Fix amd_rc config options 2021-05-09 16:37:40 +02:00
loki
ade2ef3a15 Fix profile values for amfvce 2021-05-09 16:19:05 +02:00
loki
6f428eb316 Ensure no input remains in the task_pool before resetting 2021-05-09 11:56:53 +02:00
loki
2970ad662c No more dumb pointers for initialization 2021-05-09 11:40:12 +02:00
loki
1dfe49e765 Merge branch 'draw' 2021-05-08 14:16:29 +02:00
loki-47-6F-64
7a93a72710 Merge pull request #83 from TheElixZammuto/fix-stuck-keys
Fix Modifiers stuck on Host On Disconnection
2021-05-08 13:53:35 +02:00
Elia Zammuto
661c8260e5 Fix Position for input::reset 2021-05-08 13:46:20 +02:00
loki
13c2da07e8 Move shaders to seperate folder in assets 2021-05-08 13:22:04 +02:00
Elia Zammuto
71b214ca43 Fix Stuck Modifiers on Disconnection 2021-05-08 13:08:07 +02:00
loki
513942c888 Improve colors for nv12 2021-05-08 12:03:58 +02:00
loki
67df04e0a2 Fix cursor position on lower resolution screens 2021-05-06 16:51:59 +02:00
loki
7b45f0d899 Fix cursor height and width 2021-05-06 13:51:29 +02:00
loki
0f661e467e Blend cursor onto the image 2021-05-06 12:36:26 +02:00
loki
0232d8027c Render cursor on duplicated image 2021-05-06 12:00:39 +02:00
loki
3a0377851d Update gitignore 2021-05-05 16:00:27 +02:00
loki
a93bad4cf3 Fix crash when sending SIGINT before starting the http server 2021-05-05 15:53:22 +02:00
loki
88c3828ad3 Fixed not testing for 10bit pixels support 2021-05-05 12:17:25 +02:00
loki
c19853f03f Add color for BT701 colorspace 2021-05-05 11:28:57 +02:00
loki
1b7e103ef6 Allow resizing the image during conversion 2021-05-04 10:21:56 +02:00
loki
900d59b3ac Dynamically set colors during runtime 2021-05-03 22:06:55 +02:00
loki
37a9256587 Render NV12 color format 2021-05-02 22:35:19 +02:00
loki
127b5501d9 Render luma onto nv12 surface 2021-04-30 20:01:15 +02:00
Elia Zammuto
a081a9f5c4 Merge pull request #3 from loki-47-6F-64/master
Updates
2021-04-28 21:06:52 +02:00
loki
fe8c2ceab9 Warn when VideoProcessorSetStreamAlpha isn't supported 2021-04-28 13:44:42 +02:00
loki
1be5b4787f Moved pairInputDesktop() from desktop.h to input.cpp 2021-04-26 19:27:55 +02:00
loki
bb88d6f828 Third attempt to fix linux build for appveyor 2021-04-26 21:08:20 +02:00
loki
a7030641f3 Second attempt to fix linux build 2021-04-26 20:50:37 +02:00
loki
208de3dae9 Fix Linux build 2021-04-26 20:36:54 +02:00
Loki
e81db118d5 Fix windows build 2021-04-26 14:46:57 +02:00
loki
488d8e5fc2 force merge 2021-04-24 23:46:04 +02:00
loki
0049b36471 Use existing config option for selecting monitor 2021-04-24 23:41:56 +02:00
loki
66b4b70023 merge with notentered 2021-04-24 17:12:10 +02:00
loki
438ae6a761 Don't stop streaming when UAC is running 2021-04-24 15:53:48 +02:00
loki
0cfb440cf6 Added config examples to the config file 2021-04-24 14:23:12 +02:00
loki
a613280bbd Merge branch 'master' into TheElixZammuto 2021-04-24 13:09:14 +02:00
loki
6112c81db7 Fix –undefined reference to __memcpy_chk when building on msys64 2021-04-24 12:07:26 +02:00
Elia Zammuto
3f26a4fd21 Merge pull request #2 from psyke83/realtime_gpu
Use realtime GPU priority to avoid stalls during high GPU usage
2021-04-05 15:35:04 +02:00
Elia Zammuto
bb5b003dd5 Disabled key frames when not needed 2021-04-01 20:27:59 +02:00
Elia Zammuto
99777c8e82 Added more params 2021-04-01 14:25:38 +02:00
Elia Zammuto
4daaa1f089 Added some keyframes 2021-03-31 14:11:21 +02:00
Elia Zammuto
0828cc3f83 Started work on AMD Hardware Decoding 2021-03-29 13:16:56 +02:00
Elia Zammuto
790835f6c6 Merge pull request #1 from theofficialgman/amd_encoder
Add amd decoder support
2021-03-21 18:59:09 +01:00
kiralycraft
fe3784454a Fixed unspecified monitor streaming the whole X 2021-02-28 17:56:38 +02:00
kiralycraft
b336bf2fcb Attempt to update GCC to version 8 for appveyor 2021-02-28 16:10:29 +02:00
kiralycraft
87be37293e Accidentally modified stock config file 2021-02-28 15:56:23 +02:00
kiralycraft
7abcfc0390 Added ability to stream specific monitor on Linux 2021-02-28 15:52:47 +02:00
Garrett
020e2069cb Add amd decoder support 2021-01-28 18:38:11 -05:00
Zlatko Zahariev
83dd07469e Initial elevated windows work 2021-01-15 02:07:49 -05:00
Conn O'Griofa
0a883ab651 Use realtime GPU priority to avoid stalls during high GPU usage
Change derived from OBS project commit: ec769ef008
See related discussion: https://obsproject.com/forum/threads/obs-studio-24-0-3-gpu-priority-fix-testing.111669/
2021-01-11 04:22:18 +00:00
Doomsdayrs
7b39b93bb2 Update README.md
- Cleaned things up a bit more
- Simplified instructions
- make instruction has `-j ${nproc}` to ease up the word count
- Use $USER instead of {username}
2020-11-18 15:31:13 -05:00
Jacek Szafarkiewicz
30496c79ab Make systemd script cleaner 2020-09-04 15:51:20 +02:00
loki
415dec37ad update README 2020-05-04 00:24:11 +02:00
loki
fbbe396416 Install properly on ubuntu20.04 2020-05-03 23:58:32 +02:00
loki
c6da7d31d0 Fix typo 2020-05-03 22:04:07 +02:00
loki
1f9c6945ec support ubuntu 20.04 in favor of 19.10 2020-05-03 21:59:36 +02:00
loki
4094aec2eb Fix bug causing video artifects when FEC is used on Moonlight 2020-05-02 23:38:33 +02:00
loki
ff7a5aa1ea Don't use old images when encoding 2020-05-01 22:54:21 +02:00
loki
bfe59f6cf2 Fix stuttering in Minecraft when moving the mouse 2020-05-01 22:04:18 +02:00
loki
d8b4c66d7e Fix black block in place of cursor in Minecraft 2020-05-01 17:13:30 +02:00
loki
6f92c42ad8 Fix -quitappafter 2020-05-01 13:57:24 +02:00
loki
1b1d514d10 Parse apps after parsing environment in app.json 2020-05-01 13:32:26 +02:00
loki
af1135d455 fix row_pitch 2020-04-29 22:01:43 +02:00
loki
e10c9a1fa1 Exit gracefully if a port is bound by another process 2020-04-26 23:37:47 +02:00
loki
1a233ca4aa Remove incorrect warning 2020-04-26 15:24:36 +02:00
loki
6878dcbf37 Improve compilation instructions for Windows static build 2020-04-26 00:26:47 +02:00
loki
8d735e5611 Support keyboard key repeats 2020-04-26 00:23:34 +02:00
loki
1862662b3a Prevent queue from growing to large, eating up all memory 2020-04-24 22:10:08 +02:00
loki
1102ac9f3b Merge branch 'nvenc' 2020-04-23 16:12:21 +02:00
loki
12115a8a75 Merge branch 'master' of github.com:loki-47-6F-64/sunshine 2020-04-23 16:11:56 +02:00
loki
7ee59669da Removed unnecessary header include 2020-04-23 15:49:47 +02:00
loki
22418cb613 Moved linux specific files to folder platform/linux 2020-04-23 15:48:05 +02:00
loki
4bccec1c39 Refactor platorm Windows 2020-04-23 15:41:40 +02:00
loki
8b1a288ef3 Merge branch 'nvenc' of https://github.com/loki-47-6F-64/sunshine into nvenc 2020-04-23 11:14:35 +02:00
loki
fa489531b0 Don't access video device ctx merely for setting cursor invisible 2020-04-23 00:23:40 +03:00
loki
2e52402e27 Correctly truncate cursor image 2020-04-23 00:09:27 +03:00
loki
519f7a8bf1 convert pointer shape monochrome to color 2020-04-22 22:55:33 +03:00
loki
17e9b803db Display cursor type color with nvenc 2020-04-22 00:07:26 +03:00
loki
48b4dbd81c Merge branch 'master' into nvenc 2020-04-19 21:39:32 +02:00
loki
70bf11ec27 Increase accuracy of fps for nvenc and proper pixel format 2020-04-19 00:10:47 +03:00
loki-47-6F-64
8ea9d34729 Add small description for Sunshine 2020-04-18 12:27:26 +02:00
loki-47-6F-64
8c4519a2d0 Merge pull request #15 from Doomsdayrs/master
README update
2020-04-18 12:25:03 +02:00
Doomsdayrs
ea266b979f Update README.md
- added assets
- made a list
2020-04-17 22:46:07 -04:00
loki
2f978b3159 update pre-compiled 2020-04-17 21:57:27 +02:00
loki
d81ba12aa8 Merge branch 'nvenc' of github.com:loki-47-6F-64/sunshine into nvenc 2020-04-17 19:19:12 +02:00
loki
dd13131fe6 Fix video freezing when resizing display with 2 or more sessions 2020-04-17 19:18:55 +02:00
loki
5a4055f313 Pair and connect with Moonlight-iOS 2020-04-17 18:42:55 +02:00
loki
87f3ab0181 Fix nvenc 2020-04-17 12:28:23 +02:00
loki
c7d6e959e0 Fix stream not closing properly when exiting app 2020-04-16 15:35:12 +02:00
loki
0b1a69a067 Ensure it compiles on Linux again 2020-04-15 21:07:00 +02:00
loki
525e8b3c6d Refactor video.cpp 2020-04-15 19:16:20 +02:00
loki
ad7f93c3cb Switch between nvenc and software encoding 2020-04-14 00:59:43 +03:00
loki
c7a72553c4 Configure settings nvenc 2020-04-14 00:15:24 +03:00
loki
679f74e53c Fix multicasting for nvenc 2020-04-12 02:33:17 +03:00
loki
7edaa0cce0 Encode with nvenc smoothly 2020-04-10 15:39:50 +03:00
loki
c21038af88 Encode video with nvenc 2020-04-08 02:15:08 +03:00
loki
65f44cc885 Fix encoder flags not set properly 2020-04-07 18:57:59 +03:00
loki
ceb784c648 Test capabilities of the encoders 2020-04-07 14:54:56 +03:00
loki
8e3df43caf Pass both nvenc and software in validation 2020-04-07 00:34:52 +03:00
loki
afbca0f6cd initialize nvenc 2020-04-06 23:15:03 +03:00
Doomsdayrs
f4e99a1bd6 Update README.md 2020-04-05 14:46:00 -04:00
Doomsdayrs
ed6b919a4d Update README.md 2020-04-05 14:34:11 -04:00
Doomsdayrs
2d2fd77bc1 Update README.md
- Cleaned up credits
2020-04-05 02:11:26 -04:00
Doomsdayrs
15f347c69c Update README.md
- Super simple table of contents
2020-04-05 02:10:20 -04:00
Doomsdayrs
9fdaf2117f Update README.md
- Added links, Moved some parts around for appeal
2020-04-05 02:08:39 -04:00
Doomsdayrs
631be974ee Update README.md
- Initial rework to be legible in a modern fashion
2020-04-05 02:03:40 -04:00
doomsdayrs
bd3d1d6988 Readme, but now MD 2020-04-05 01:49:08 -04:00
loki
f2636b163e General structure complete 2020-04-02 20:13:44 +02:00
loki
df5daa045a Add a timestamp in front of the log 2020-04-01 18:01:13 +02:00
loki
4de547228c Fix mouse format unsupported and incorrect version string 2020-04-01 14:33:05 +02:00
loki
3d595ce927 Re-add capturing mouse for windows 2020-03-31 23:48:07 +02:00
loki
456d33cf77 Add abillity to supply options for specific encoders 2020-03-31 23:46:41 +02:00
loki
3ceb9b37a0 Reinitialize the video encoder in addition to the capturing device 2020-03-31 21:18:33 +02:00
loki
94181fd047 Prevent unnecessary copies of entire frames on Windows 2020-03-27 21:57:29 +01:00
loki
55705af922 Prepare for hardware encoders 2020-03-25 10:51:32 +01:00
49 changed files with 5474 additions and 1974 deletions

7
.gitignore vendored
View File

@@ -1,8 +1,9 @@
build
cmake-build-*
cmake-build*
.DS_Store
.vscode
.vs
*.swp
*.kdev4
.idea
.idea

3
.gitmodules vendored
View File

@@ -7,6 +7,3 @@
[submodule "ViGEmClient"]
path = ViGEmClient
url = https://github.com/ViGEm/ViGEmClient
[submodule "pre-compiled"]
path = pre-compiled
url = https://bitbucket.org/Loki-47-6F-64/pre-compiled.git

View File

@@ -1,77 +1,90 @@
cmake_minimum_required(VERSION 2.8)
cmake_minimum_required(VERSION 3.0)
project(Sunshine)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# On MSYS2, building a stand-alone binary that links with ffmpeg is not possible,
# Therefore, ffmpeg, libx264 and libx265 must be build from source
if(WIN32)
option(SUNSHINE_STANDALONE "Compile stand-alone binary of Sunshine" OFF)
if(SUNSHINE_STANDALONE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
if(NOT DEFINED SUNSHINE_PREPARED_BINARIES)
set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/pre-compiled/windows")
endif()
list(PREPEND PLATFORM_LIBRARIES
C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/${CMAKE_CXX_COMPILER_VERSION}/libstdc++.a
C:/msys64/mingw64/x86_64-w64-mingw32/lib/libwinpthread.a
)
set(FFMPEG_INCLUDE_DIRS
${SUNSHINE_PREPARED_BINARIES}/include)
set(FFMPEG_LIBRARIES
${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a
${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a
z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a)
endif()
else()
set(SUNSHINE_STANDALONE OFF)
endif()
add_subdirectory(Simple-Web-Server)
if(WIN32)
# Ugly hack to compile with #include <qos2.h>
add_compile_definitions(
QOS_FLOWID=UINT32
PQOS_FLOWID=UINT32*
QOS_NON_ADAPTIVE_FLOW=2)
endif()
add_subdirectory(moonlight-common-c/enet)
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
if(NOT SUNSHINE_STANDALONE)
find_package(FFmpeg REQUIRED)
endif()
list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
if(WIN32)
file(
DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip"
TIMEOUT 60
EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6)
file(ARCHIVE_EXTRACT
INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip"
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
if(NOT DEFINED SUNSHINE_PREPARED_BINARIES)
set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows")
endif()
add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json")
include_directories(
ViGEmClient/include)
include_directories(ViGEmClient/include)
set(PLATFORM_TARGET_FILES
sunshine/platform/windows.cpp
sunshine/platform/windows_dxgi.cpp
sunshine/platform/windows_wasapi.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
ViGEmClient/src/ViGEmClient.cpp
ViGEmClient/include/ViGEm/Client.h
ViGEmClient/include/ViGEm/Common.h
ViGEmClient/include/ViGEm/Util.h
ViGEmClient/include/ViGEm/km/BusShared.h)
set(OPENSSL_LIBRARIES
libssl.a
libcrypto.a)
set(FFMPEG_INCLUDE_DIRS
${SUNSHINE_PREPARED_BINARIES}/include)
set(FFMPEG_LIBRARIES
${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a
${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a
${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a
${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a
${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a
${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a
z lzma bcrypt libiconv.a)
list(PREPEND PLATFORM_LIBRARIES
libstdc++.a
libwinpthread.a
libssp.a
Qwave
winmm
ksuser
wsock32
ws2_32
iphlpapi
d3d11 dxgi
d3d11 dxgi D3DCompiler
setupapi
)
@@ -81,9 +94,10 @@ else()
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json")
find_package(X11 REQUIRED)
find_package(FFmpeg REQUIRED)
set(PLATFORM_TARGET_FILES
sunshine/platform/linux.cpp
sunshine/platform/linux_evdev.cpp)
sunshine/platform/linux/display.cpp
sunshine/platform/linux/input.cpp)
set(PLATFORM_LIBRARIES
Xfixes
@@ -91,6 +105,7 @@ else()
xcb
xcb-shm
xcb-xfixes
Xrandr
${X11_LIBRARIES}
evdev
pulse
@@ -148,6 +163,7 @@ set(SUNSHINE_TARGET_FILES
sunshine/thread_pool.h
sunshine/thread_safe.h
sunshine/sync.h
sunshine/round_robin.h
${PLATFORM_TARGET_FILES})
include_directories(
@@ -174,12 +190,6 @@ if(NOT SUNSHINE_ASSETS_DIR)
set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
endif()
if(SUNSHINE_STANDALONE)
set(OPENSSL_LIBRARIES
C:/msys64/mingw64/lib/libssl.a
C:/msys64/mingw64/lib/libcrypto.a)
endif()
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CMAKE_THREAD_LIBS_INIT}
stdc++fs

191
README.md Normal file
View File

@@ -0,0 +1,191 @@
# Introduction
Sunshine is a Gamestream host for Moonlight
- [Building](README.md#building)
- [Credits](README.md#credits)
# Building
- [Linux](README.md#linux)
- [Windows](README.md#windows-10)
## Linux
### Requirements:
Ubuntu 20.04:
Install the following
```
sudo apt install cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
```
### Compilation:
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules`
- `cd sunshine && mkdir build && cd build`
- `cmake ..`
- `make -j ${nproc}`
### Setup:
sunshine needs access to uinput to create mouse and gamepad events:
- Add user to group 'input':
`usermod -a -G input $USER`
- Create udev rules:
- Run the following command:
`nano /etc/udev/rules.d/85-sunshine-input.rules`
- Input the following contents:
`KERNEL=="uinput", GROUP="input", mode="0660"`
- Save the file and exit
1. `CTRL+X` to start exit
2. `Y` to save modifications
- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running:
`sunshine path/to/sunshine.conf`
- Configure autostart service
`path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following:
1. Copy it to the users systemd, `cp sunshine.service ~/.config/systemd/user/`
2. Starting
- Onetime:
`systemctl --user start sunshine`
- Always on boot:
`systemctl --user enable sunshine`
- `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream
### Trouleshooting:
- If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input":
- `groups $USER`
- If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
1. pacmd list-sources | grep "name:"
2. Copy the name to the configuration option "audio_sink"
3. restart sunshine
## Windows 10
### Requirements:
mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make
### Compilation:
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive`
- `cd sunshine && mkdir build && cd build`
- `cmake -G"Unix Makefiles" ..`
- `make`
### Setup:
- **OPTIONAL** Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases]
# Common
## Usage:
- run "sunshine path/to/sunshine.conf"
- In Moonlight: Add PC manually
- When Moonlight request you insert the correct pin on sunshine, either:
- Type in the URL bar of your browser: `xxx.xxx.xxx.xxx:47989/pin/####`
- `wget xxx.xxx.xxx.xxx:47989/pin/####`
- The x's are the IP of your instance, `####` is the pin
- Click on one of the Applications listed
- Have fun :)
## Note:
- The Windows key is not passed through by Moonlight, therefore Sunshine maps Right-Alt key to the Windows key
- If you set Video Bitrate to 0.5Mb/s:
- Sunshine will use CRF or QP to controll the quality of the stream. (See example configuration file for more details)
- This is less CPU intensive and it has lower average bandwith requirements compared to manually setting bitrate to acceptable quality
- However, it has higher peak bitrates, forcing Sunshine to drop entire frames when streaming 1080P due to their size.
- When this happens, the video portion of the stream appears to be frozen.
- This is rare enough that using this for the desktop environment is tolerable (in my opinion), however for gaming not so much.
## Credits:
- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server)
- [Moonlight](https://github.com/moonlight-stream)
- [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :)
## Application List:
- You can use Environment variables in place of values
- $(HOME) will be replaced by the value of $HOME
- $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME)
- env: Adds or overwrites Environment variables for the commands/applications run by Sunshine.
- "Variable name":"Variable value"
- apps: The list of applications
- Example:
```json
{
"name":"An App",
"cmd":"command to open app",
"prep-cmd":[
{
"do":"some-command",
"undo":"undo-that-command"
}
],
"detached":[
"some-command",
"another-command"
]
}
```
- name: Self explanatory
- output <optional>: The file where the output of the command is stored
- If it is not specified, the output is ignored
- detached: A list of commands to be run and forgotten about
- prep-cmd: A list of commands to be run before/after the application
- If any of the prep-commands fail, starting the application is aborted
- do: Run before the application
- If it fails, all 'undo' commands of the previously succeeded 'do' commands are run
- undo <optional>: Run after the application has terminated
- This should not fail considering it is supposed to undo the 'do' commands.
- If it fails, Sunshine is terminated
- cmd <optional>: The main application
- If not specified, a processs is started that sleeps indefinitely
1. When an application is started, if there is an application already running, it will be terminated.
2. When the application has been shutdown, the stream shuts down as well.
3. In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application, instead it simply starts a stream.
Linux
```json
{
"env":{
"DISPLAY":":0",
"DRI_PRIME":"1",
"XAUTHORITY":"$(HOME)/.Xauthority",
"PATH":"$(PATH):$(HOME)/.local/bin"
},
"apps":[
{
"name":"Low Res Desktop",
"prep-cmd":[
{ "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" }
]
},
{
"name":"Steam BigPicture",
"output":"steam.txt",
"cmd":"steam -bigpicture",
"prep-cmd":[]
}
]
}
```
Windows
```json
{
"env":{
"PATH":"$(PATH);C:\\Program Files (x86)\\Steam"
},
"apps":[
{
"name":"Steam BigPicture",
"output":"steam.txt",
"prep-cmd":[
{"do":"steam \"steam://open/bigpicture\""}
]
}
]
}
```

View File

@@ -1,117 +0,0 @@
######### Linux ##############
Requirements:
Ubuntu 19.10: cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
Compilation:
* git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules
* cd sunshine && mkdir build && cd build
* cmake ..
* make
Setup:
* sunshine needs access to uinput to create mouse and gamepad events:
* Add user to group 'input': "usermod -a -G input username
* Create a file: "/etc/udev/rules.d/85-sunshine-input.rules"
* The contents of the file is as follows:
KERNEL=="uinput", GROUP="input", mode="0660"
* assets/sunshine.conf is an example configuration file. Modify it as you see fit and use it by running: "sunshine path/to/sunshine.conf"
* path/to/build/dir/sunshine.service is used to start sunshine in the background:
* cp sunshine.service $HOME/.config/systemd/user/
* Modify $HOME/.config/systemd/user/sunshine.conf to point to the sunshine executable
* systemctl --user start sunshine
* assets/apps.json is an example of a list of applications that are started just before running a stream:
* See below for a detailed explanation
Trouleshooting:
* If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input":
* groups
* If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
* pacmd list-sources | grep "name:"
* Copy the name to the configuration option "audio_sink"
* restart sunshine
######### Windows 10 ############
Requirements:
MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost
Compilation:
* git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules
* cd sunshine && mkdir build && cd build
* cmake -G"Unix Makefiles" ..
* make
Setup:
* <optional> Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases]
== Static build ==
Requirements:
MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost git-lfs
Compilation:
* git lfs install
* git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules
* cd sunshine && mkdir build && cd build
* cmake -DSUNSHINE_STANDALONE=ON -DSUNSHINE_ASSETS_DIR=assets -G"Unix Makefiles" ..
* make
######### Common #############
Usage:
* run "sunshine path/to/sunshine.conf"
* In Moonlight: Add PC manually
* When Moonlight request you insert the correct pin on sunshine:
wget xxx.xxx.xxx.xxx:47989/pin/xxxx -- where the first few x's are substituted by the ip of Sunshine and the final 4 x'es are substituted by the pin
or
Type in the URL bar of your browser: xxx.xxx.xxx.xxx:47989/pin/xxxx -- where the first few x's are substituted by the ip of the final 4 x'es are subsituted by the pin
* Click on one of the Applications listed
* Have fun :)
Note:
* The Windows key is not passed through by Moonlight, therefore Sunshine maps Right-Alt key to the Windows key
* If you set Video Bitrate to 0.5Mb/s:
* Sunshine will use CRF or QP to controll the quality of the stream. (See example configuration file for more details)
* This is less CPU intensive and it has lower average bandwith requirements compared to manually setting bitrate to acceptable quality
* However, it has higher peak bitrates, forcing Sunshine to drop entire frames when streaming 1080P due to their size.
* When this happens, the video portion of the stream appears to be frozen.
* This is rare enough that using this for the desktop environment is tolerable (in my opinion), however for gaming not so much.
Credits:
* Simple-Web-Server [https://gitlab.com/eidheim/Simple-Web-Server]
* Moonlight [https://github.com/moonlight-stream]
* Looking-Glass [https://github.com/gnif/LookingGlass] (For showing me how to properly capture frames on Windows, saving me a lot of time :)
Application List:
* You can use Environment variables in place of values
* $(HOME) will be replaced by the value of $HOME
* $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME)
* env: Adds or overwrites Environment variables for the commands/applications run by Sunshine.
* "Variable name":"Variable value"
* apps: The list of applications
* name: Self explanatory
* output <optional>: The file where the output of the command is stored
* If it is not specified, the output is ignored
* prep-cmd: A list of commands to be run before/after the application
* If any of the prep-commands fail, starting the application is aborted
* do: Run before the application
* If it fails, all 'undo' commands of the previously succeeded 'do' commands are run
* undo <optional>: Run after the application has terminated
* This should not fail considering it is supposed to undo the 'do' commands.
* If it fails, Sunshine is terminated
* cmd <optional>: The main application
* If not specified, a processs is started that sleeps indefinitely
When an application is started, if there is an application already running, it will be terminated.
When the application has been shutdown, the stream shuts down as well.
In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application, instead it simply starts a stream.

View File

@@ -1,5 +1,5 @@
image:
- Ubuntu
- Ubuntu2004
- Visual Studio 2019
environment:
@@ -8,18 +8,8 @@ environment:
- BUILD_TYPE: Release
install:
- sh: sudo add-apt-repository ppa:hnakamur/icu
- sh: sudo add-apt-repository ppa:hnakamur/boost
- sh: sudo add-apt-repository ppa:savoury1/build-tools
- sh: sudo add-apt-repository ppa:savoury1/backports
- sh: sudo add-apt-repository ppa:savoury1/graphics
- sh: sudo add-apt-repository ppa:savoury1/multimedia
- sh: sudo add-apt-repository ppa:savoury1/ffmpeg4
- sh: sudo apt update
- sh: sudo apt install -y fakeroot cmake libssl-dev libavdevice-dev libboost-thread1.67-dev libboost-filesystem1.67-dev libboost-log1.67-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
- sh: sudo update-alternatives --set gcc /usr/bin/gcc-8
- sh: sudo apt update --ignore-missing
- sh: sudo apt install -y build-essential fakeroot gcc-10 g++-10 cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
- cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make"
before_build:
@@ -30,8 +20,8 @@ before_build:
build_script:
- cmd: set OLDPATH=%PATH%
- cmd: set PATH=C:\msys64\mingw64\bin
- sh: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine ..
- cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_STANDALONE=ON -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" ..
- sh: cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine ..
- cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" ..
- sh: make -j$(nproc)
- cmd: mingw32-make -j2
- cmd: set PATH=%OLDPATH%

View File

@@ -1,8 +1,5 @@
{
"env":{
"DISPLAY":":0",
"DRI_PRIME":"1",
"XAUTHORITY":"$(HOME)/.Xauthority",
"PATH":"$(PATH):$(HOME)/.local/bin"
},
"apps":[
@@ -16,8 +13,7 @@
"name":"Steam BigPicture",
"output":"steam.txt",
"cmd":"steam -bigpicture",
"prep-cmd":[]
"detached":["setsid steam steam://open/bigpicture"]
}
]
}

View File

@@ -7,9 +7,7 @@
"name":"Steam BigPicture",
"output":"steam.txt",
"prep-cmd":[
{"do":"steam \"steam://open/bigpicture\""}
]
"detached":["steam steam://open/bigpicture"]
}
]
}

View File

@@ -0,0 +1,33 @@
Texture2D image : register(t0);
SamplerState def_sampler : register(s0);
struct FragTexWide {
float3 uuv : TEXCOORD0;
};
cbuffer ColorMatrix : register(b0) {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float2 main_ps(FragTexWide input) : SV_Target
{
float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb;
float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb;
float3 rgb = (rgb_left + rgb_right) * 0.5;
float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w;
float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w;
u = u * range_uv.x + range_uv.y;
v = v * range_uv.x + range_uv.y;
return float2(u, v * 224.0f/256.0f + 0.0625);
}

View File

@@ -0,0 +1,29 @@
struct VertTexPosWide {
float3 uuv : TEXCOORD;
float4 pos : SV_POSITION;
};
cbuffer info : register(b0) {
float width_i;
};
//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
VertTexPosWide main_vs(uint vI : SV_VERTEXID)
{
float idHigh = float(vI >> 1);
float idLow = float(vI & uint(1));
float x = idHigh * 4.0 - 1.0;
float y = idLow * 4.0 - 1.0;
float u_right = idHigh * 2.0;
float u_left = u_right - width_i;
float v = 1.0 - idLow * 2.0;
VertTexPosWide vert_out;
vert_out.uuv = float3(u_left, u_right, v);
vert_out.pos = float4(x, y, 0.0, 1.0);
return vert_out;
}

View File

@@ -0,0 +1,25 @@
Texture2D image : register(t0);
SamplerState def_sampler : register(s0);
cbuffer ColorMatrix : register(b0) {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
struct PS_INPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
float main_ps(PS_INPUT frag_in) : SV_Target
{
float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb;
float y = dot(color_vec_y.xyz, rgb);
return y * range_y.x + range_y.y;
}

View File

@@ -0,0 +1,14 @@
Texture2D image : register(t0);
SamplerState def_sampler : register(s0);
struct PS_INPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
float4 main_ps(PS_INPUT frag_in) : SV_Target
{
return image.Sample(def_sampler, frag_in.tex, 0);
}

View File

@@ -0,0 +1,22 @@
struct PS_INPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
PS_INPUT main_vs(uint vI : SV_VERTEXID)
{
float idHigh = float(vI >> 1);
float idLow = float(vI & uint(1));
float x = idHigh * 4.0 - 1.0;
float y = idLow * 4.0 - 1.0;
float u = idHigh * 2.0;
float v = 1.0 - idLow * 2.0;
PS_INPUT vert_out;
vert_out.pos = float4(x, y, 0.0, 1.0);
vert_out.tex = float2(u, v);
return vert_out;
}

View File

@@ -65,6 +65,14 @@
# If back_button_timeout < 0, then the Home/Guide button will not be emulated
# back_button_timeout = 2000
# !! Windows only !!
# Control how fast keys will repeat themselves
# The initial delay in milliseconds before repeating keys
# key_repeat_delay = 500
#
# How often keys repeat every second
# This configurable option supports decimals
# key_repeat_frequency = 24.9
# The name of the audio sink used for Audio Loopback
# If you do not specify this variable, pulseaudio will select the default monitor device.
@@ -85,6 +93,9 @@
# adapter_name = Radeon RX 580 Series
# output_name = \\.\DISPLAY1
# !! Linux only !!
# Set the display number to stream. I have no idea how they are numbered. They start from 1, usually.
# output_name = 1
###############################################
# FFmpeg software encoding parameters
@@ -105,20 +116,88 @@
# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
# value that can reliably encode at your desired streaming settings on your hardware.
# min_threads = 2
# min_threads = 1
# Allows the client to request HEVC Main or HEVC Main10 video streams.
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance.
# If set to 0 (default), Sunshine will not advertise support for HEVC
# If set to 1, Sunshine will advertise support for HEVC Main profile
# If set to 2, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
# If set to 0 (default), Sunshine will specify support for HEVC based on encoder
# If set to 1, Sunshine will not advertise support for HEVC
# If set to 2, Sunshine will advertise support for HEVC Main profile
# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
# hevc_mode = 2
# Force a specific encoder, otherwise Sunshine will use the first encoder that is available
# supported encoders:
# nvenc
# amdvce # NOTE: alpha stage. The cursor is not yet displayed
# software
#
# encoder = nvenc
##################################### Software #####################################
# See x264 --fullhelp for the different presets
# preset = superfast
# tune = zerolatency
# sw_preset = superfast
# sw_tune = zerolatency
#
##################################### NVENC #####################################
###### presets ###########
# default
# hp -- high performance
# hq -- high quality
# slow -- hq 2 passes
# medium -- hq 1 pass
# fast -- hp 1 pass
# bd
# ll -- low latency
# llhq
# llhp
# lossless
# losslesshp
##########################
# nv_preset = llhq
#
####### rate control #####
# auto -- let ffmpeg decide rate control
# constqp -- constant QP mode
# vbr -- variable bitrate
# cbr -- constant bitrate
# cbr_hq -- cbr high quality
# cbr_ld_hq -- cbr low delay high quality
# vbr_hq -- vbr high quality
##########################
# nv_rc = auto
###### h264 entropy ######
# auto -- let ffmpeg nvenc decide the entropy encoding
# cabac
# cavlc
##########################
# nv_coder = auto
##################################### AMD #####################################
###### presets ###########
# default
# speed
# balanced
##########################
# amd_preset = balanced
#
####### rate control #####
# auto -- let ffmpeg decide rate control
# constqp -- constant QP mode
# vbr_latency -- Latency Constrained Variable Bitrate
# vbr_peak -- Peak Contrained Variable Bitrate
# cbr -- constant bitrate
##########################
# amd_rc = auto
###### h264 entropy ######
# auto -- let ffmpeg nvenc decide the entropy encoding
# cabac
# cavlc
##########################
# amd_coder = auto
##############################################
# Some configurable parameters, are merely toggles for specific features
# The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc

View File

@@ -35,8 +35,8 @@ Package: sunshine
Architecture: amd64
Maintainer: @loki
Priority: optional
Version: 0.1.1
Depends: libssl | libavdevice | libboost-thread (>= 1.67) | libboost-filesystem (>= 1.67) | libboost-log (>= 1.67) | libpulse | libopus | libxtst | libx11 | libxfixes | libevdev | libxcb1 | libxcb-shm0 | libxcb-xfixes0
Version: 0.3.1
Depends: libssl1.1, libavdevice58, libboost-thread1.71.0, libboost-filesystem1.71.0, libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0
Description: Gamestream host for Moonlight
EOF
@@ -54,10 +54,6 @@ if [ -f /etc/group ]; then
else
echo "Warning: /etc/group not found"
fi
# Prevent necessity of rebooting system
chmod 0660 /dev/uinput
chown root:$GROUP_INPUT /dev/uinput
EOF
cat << 'EOF' > $RULES/85-sunshine-rules.rules

Submodule pre-compiled deleted from 51f776dbd4

View File

@@ -2,12 +2,7 @@
Description=Sunshine Gamestream Server for Moonlight
[Service]
WorkingDirectory=/home/%u
Environment="DISPLAY=:0"
Type=simple
# wait for Xorg
ExecStartPre=/bin/sh -c 'while ! pgrep Xorg; do sleep 2; done'
ExecStart=@SUNSHINE_EXECUTABLE_PATH@
[Install]
WantedBy=default.target
WantedBy=graphical-session.target

View File

@@ -53,15 +53,15 @@ static opus_stream_config_t HighSurround51 = {
void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) {
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stereo;
opus_t opus { opus_multistream_encoder_create(
stream->sampleRate,
stream->channelCount,
stream->streams,
stream->coupledStreams,
stream->mapping,
OPUS_APPLICATION_AUDIO,
nullptr)
};
opus_t opus { opus_multistream_encoder_create(
stream->sampleRate,
stream->channelCount,
stream->streams,
stream->coupledStreams,
stream->mapping,
OPUS_APPLICATION_AUDIO,
nullptr)
};
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while(auto sample = samples->pop()) {
@@ -76,17 +76,19 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi
}
packet.fake_resize(bytes);
packets->raise(std::make_pair(channel_data, std::move(packet)));
packets->raise(channel_data, std::move(packet));
}
}
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) {
auto samples = std::make_shared<sample_queue_t::element_type>();
auto samples = std::make_shared<sample_queue_t::element_type>(30);
std::thread thread { encodeThread, packets, samples, config, channel_data };
auto fg = util::fail_guard([&]() {
samples->stop();
thread.join();
shutdown_event->view();
});
//FIXME: Pick correct opus_stream_config_t based on config.channels

View File

@@ -15,17 +15,153 @@
#define APPS_JSON_PATH SUNSHINE_ASSETS_DIR "/" APPS_JSON
namespace config {
using namespace std::literals;
namespace nv {
enum preset_e : int {
_default = 0,
slow,
medium,
fast,
hp,
hq,
bd,
ll_default,
llhq,
llhp,
lossless_default, // lossless presets must be the last ones
lossless_hp,
};
enum rc_e : int {
constqp = 0x0, /**< Constant QP mode */
vbr = 0x1, /**< Variable bitrate mode */
cbr = 0x2, /**< Constant bitrate mode */
cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */
cbr_hq = 0x10, /**< CBR, high quality (slower) */
vbr_hq = 0x20 /**< VBR, high quality (slower) */
};
enum coder_e : int {
_auto = 0,
cabac,
cavlc
};
std::optional<preset_e> preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x) if(preset == #x##sv) return x
_CONVERT_(slow);
_CONVERT_(medium);
_CONVERT_(fast);
_CONVERT_(hp);
_CONVERT_(bd);
_CONVERT_(ll_default);
_CONVERT_(llhq);
_CONVERT_(llhp);
_CONVERT_(lossless_default);
_CONVERT_(lossless_hp);
if(preset == "default"sv) return _default;
#undef _CONVERT_
return std::nullopt;
}
std::optional<rc_e> rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) if(rc == #x##sv) return x
_CONVERT_(constqp);
_CONVERT_(vbr);
_CONVERT_(cbr);
_CONVERT_(cbr_hq);
_CONVERT_(vbr_hq);
_CONVERT_(cbr_ld_hq);
#undef _CONVERT_
return std::nullopt;
}
int coder_from_view(const std::string_view &coder) {
if(coder == "auto"sv) return _auto;
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
return -1;
}
}
namespace amd {
enum quality_e : int {
_default = 0,
speed,
balanced,
//quality2,
};
enum rc_e : int {
constqp, /**< Constant QP mode */
vbr_latency, /**< Latency Constrained Variable Bitrate */
vbr_peak, /**< Peak Contrained Variable Bitrate */
cbr, /**< Constant bitrate mode */
};
enum coder_e : int {
_auto = 0,
cabac,
cavlc
};
std::optional<quality_e> quality_from_view(const std::string_view &quality) {
#define _CONVERT_(x) if(quality == #x##sv) return x
_CONVERT_(speed);
_CONVERT_(balanced);
//_CONVERT_(quality2);
if(quality == "default"sv) return _default;
#undef _CONVERT_
return std::nullopt;
}
std::optional<rc_e> rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) if(rc == #x##sv) return x
_CONVERT_(constqp);
_CONVERT_(vbr_latency);
_CONVERT_(vbr_peak);
_CONVERT_(cbr);
#undef _CONVERT_
return std::nullopt;
}
int coder_from_view(const std::string_view &coder) {
if(coder == "auto"sv) return _auto;
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
return -1;
}
}
video_t video {
0, // crf
28, // qp
2, // min_threads
0, // hevc_mode
"superfast"s, // preset
"zerolatency"s, // tune
1, // min_threads
{
"superfast"s, // preset
"zerolatency"s, // tune
}, // software
{
nv::llhq,
std::nullopt,
-1
}, // nv
{
amd::balanced,
std::nullopt,
-1
}, // amd
{}, // encoder
{}, // adapter_name
{} // output_name
{}, // output_name
};
audio_t audio {};
@@ -49,7 +185,9 @@ nvhttp_t nvhttp {
};
input_t input {
2s
2s, // back_button_timeout
500ms, // key_repeat_delay
std::chrono::duration<double> { 1 / 24.9 } // key_repeat_period
};
sunshine_t sunshine {
@@ -138,6 +276,37 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
vars.erase(it);
}
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
auto it = vars.find(name);
if(it == std::end(vars)) {
return;
}
auto &val = it->second;
input = util::from_chars(&val[0], &val[0] + val.size());
vars.erase(it);
}
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if(!tmp.empty()) {
input = f(tmp);
}
}
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
std::string tmp;
string_f(vars, name, tmp);
if(!tmp.empty()) {
input = f(tmp);
}
}
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
int temp = input;
@@ -149,6 +318,55 @@ void int_between_f(std::unordered_map<std::string, std::string> &vars, const std
}
}
bool to_bool(std::string &boolean) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); });
return
boolean == "true"sv ||
boolean == "yes"sv ||
boolean == "enable"sv ||
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
}
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
std::string tmp;
string_f(vars, name, tmp);
if(tmp.empty()) {
return;
}
input = to_bool(tmp) ? 1 : 0;
}
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
std::string tmp;
string_f(vars, name, tmp);
if(tmp.empty()) {
return;
}
char *c_str_p;
auto val = std::strtod(tmp.c_str(), &c_str_p);
if(c_str_p == tmp.c_str()) {
return;
}
input = val;
}
void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
double temp = input;
double_f(vars, name, temp);
TUPLE_2D_REF(lower, upper, range);
if(temp >= lower && temp <= upper) {
input = temp;
}
}
void print_help(const char *name) {
std::cout <<
"Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl <<
@@ -170,6 +388,9 @@ int apply_flags(const char *line) {
case '1':
config::sunshine.flags[config::flag::FRESH_STATE].flip();
break;
case 'p':
config::sunshine.flags[config::flag::CONST_PIN].flip();
break;
default:
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl;
ret = -1;
@@ -190,10 +411,19 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
int_f(vars, "qp", video.qp);
int_f(vars, "min_threads", video.min_threads);
int_between_f(vars, "hevc_mode", video.hevc_mode, {
0, 2
0, 3
});
string_f(vars, "preset", video.preset);
string_f(vars, "tune", video.tune);
string_f(vars, "sw_preset", video.sw.preset);
string_f(vars, "sw_tune", video.sw.tune);
int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view);
int_f(vars, "nv_rc", video.nv.rc, nv::rc_from_view);
int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view);
int_f(vars, "amd_quality", video.amd.quality, amd::quality_from_view);
int_f(vars, "amd_rc", video.amd.rc, amd::rc_from_view);
int_f(vars, "amd_coder", video.amd.coder, amd::coder_from_view);
string_f(vars, "encoder", video.encoder);
string_f(vars, "adapter_name", video.adapter_name);
string_f(vars, "output_name", video.output_name);
@@ -233,6 +463,21 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
input.back_button_timeout = std::chrono::milliseconds { to };
}
double repeat_frequency { 0 };
double_between_f(vars, "key_repeat_frequency", repeat_frequency, {
0, std::numeric_limits<double>::max()
});
if(repeat_frequency > 0) {
config::input.key_repeat_period = std::chrono::duration<double> {1 / repeat_frequency };
}
to = -1;
int_f(vars, "key_repeat_delay", to);
if(to >= 0) {
input.key_repeat_delay = std::chrono::milliseconds { to };
}
std::string log_level_string;
string_restricted_f(vars, "min_log_level", log_level_string, {
"verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv
@@ -327,8 +572,8 @@ int parse(int argc, char *argv[]) {
std::istreambuf_iterator<char>()
});
for(auto &var : cmd_vars) {
vars.emplace(std::move(var));
for(auto &[name,value] : cmd_vars) {
vars.insert_or_assign(std::move(name), std::move(value));
}
apply_config(std::move(vars));

View File

@@ -4,6 +4,7 @@
#include <chrono>
#include <string>
#include <bitset>
#include <optional>
namespace config {
struct video_t {
@@ -11,12 +12,27 @@ struct video_t {
int crf; // higher == more compression and less quality
int qp; // higher == more compression and less quality, ignored if crf != 0
int min_threads; // Minimum number of threads/slices for CPU encoding
int hevc_mode;
std::string preset;
std::string tune;
int min_threads; // Minimum number of threads/slices for CPU encoding
struct {
std::string preset;
std::string tune;
} sw;
struct {
std::optional<int> preset;
std::optional<int> rc;
int coder;
} nv;
struct {
std::optional<int> quality;
std::optional<int> rc;
int coder;
} amd;
std::string encoder;
std::string adapter_name;
std::string output_name;
};
@@ -53,13 +69,16 @@ struct nvhttp_t {
struct input_t {
std::chrono::milliseconds back_button_timeout;
std::chrono::milliseconds key_repeat_delay;
std::chrono::duration<double> key_repeat_period;
};
namespace flag {
enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
FLAG_SIZE
FLAG_SIZE,
CONST_PIN= 4 // Use "universal" pin
};
}

View File

@@ -187,10 +187,10 @@ x509_t x509(const std::string_view &x) {
BIO_write(io.get(), x.data(), x.size());
X509 *p = nullptr;
x509_t p;
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
return x509_t { p };
return p;
}
pkey_t pkey(const std::string_view &k) {
@@ -198,10 +198,10 @@ pkey_t pkey(const std::string_view &k) {
BIO_write(io.get(), k.data(), k.size());
EVP_PKEY *p = nullptr;
pkey_t p = nullptr;
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
return pkey_t { p };
return p;
}
std::string pem(x509_t &x509) {

View File

@@ -2,6 +2,8 @@
// Created by loki on 6/20/19.
//
// define uint32_t for <moonlight-common-c/src/Input.h>
#include <cstdint>
extern "C" {
#include <moonlight-common-c/src/Input.h>
}
@@ -11,12 +13,16 @@ extern "C" {
#include "main.h"
#include "config.h"
#include "utility.h"
#include "platform/common.h"
#include "thread_pool.h"
#include "input.h"
#include "platform/common.h"
namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t)*8);
#define DISABLE_LEFT_BUTTON_DELAY ((util::ThreadPool::task_id_t)0x01)
#define ENABLE_LEFT_BUTTON_DELAY nullptr
enum class button_state_e {
NONE,
DOWN,
@@ -40,6 +46,12 @@ void free_id(std::bitset<N> &gamepad_mask, int id) {
gamepad_mask[id] = false;
}
touch_port_event_t touch_port_event;
platf::touch_port_t touch_port {
0, 0, 0, 0
};
static util::TaskPool::task_id_t task_id {};
static std::unordered_map<short, bool> key_press {};
static std::array<std::uint8_t, 5> mouse_press {};
@@ -77,20 +89,32 @@ struct gamepad_t {
};
struct input_t {
input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS) { }
input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS), mouse_left_button_timeout {} { }
std::uint16_t active_gamepad_state;
std::vector<gamepad_t> gamepads;
util::ThreadPool::task_id_t mouse_left_button_timeout;
};
using namespace std::literals;
void print(PNV_MOUSE_MOVE_PACKET packet) {
void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse move packet--"sv << std::endl
<< "--begin relative mouse move packet--"sv << std::endl
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
<< "deltaY ["sv << util::endian::big(packet->deltaY) << ']' << std::endl
<< "--end mouse move packet--"sv;
<< "--end relative mouse move packet--"sv;
}
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin absolute mouse move packet--"sv << std::endl
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl
<< "y ["sv << util::endian::big(packet->y) << ']' << std::endl
<< "width ["sv << util::endian::big(packet->width) << ']' << std::endl
<< "height ["sv << util::endian::big(packet->height) << ']' << std::endl
<< "--end absolute mouse move packet--"sv;
}
void print(PNV_MOUSE_BUTTON_PACKET packet) {
@@ -137,8 +161,11 @@ void print(void *input) {
int input_type = util::endian::big(*(int*)input);
switch(input_type) {
case PACKET_TYPE_MOUSE_MOVE:
print((PNV_MOUSE_MOVE_PACKET)input);
case PACKET_TYPE_REL_MOUSE_MOVE:
print((PNV_REL_MOUSE_MOVE_PACKET)input);
break;
case PACKET_TYPE_ABS_MOUSE_MOVE:
print((PNV_ABS_MOUSE_MOVE_PACKET)input);
break;
case PACKET_TYPE_MOUSE_BUTTON:
print((PNV_MOUSE_BUTTON_PACKET)input);
@@ -161,36 +188,155 @@ void print(void *input) {
}
}
void passthrough(platf::input_t &input, PNV_MOUSE_MOVE_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
display_cursor = true;
platf::move_mouse(input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
}
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
display_cursor = true;
if(input->mouse_left_button_timeout == DISABLE_LEFT_BUTTON_DELAY) {
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
}
if(touch_port_event->peek()) {
touch_port = *touch_port_event->pop();
}
float x = util::endian::big(packet->x);
float y = util::endian::big(packet->y);
// Prevent divide by zero
// Don't expect it to happen, but just in case
if(!packet->width || !packet->height) {
BOOST_LOG(warning) << "Moonlight passed invalid dimensions"sv;
return;
}
float width = util::endian::big(packet->width);
float height = util::endian::big(packet->height);
auto scale_x = (float)touch_port.width / width;
auto scale_y = (float)touch_port.height / height;
platf::abs_mouse(platf_input, touch_port, x * scale_x, y * scale_y);
}
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
auto constexpr BUTTON_RELEASED = 0x09;
auto constexpr BUTTON_LEFT = 0x01;
auto constexpr BUTTON_RIGHT = 0x03;
display_cursor = true;
auto release = packet->action == BUTTON_RELEASED;
auto button = util::endian::big(packet->button);
if(button > 0 && button < mouse_press.size()) {
mouse_press[button] = packet->action != BUTTON_RELEASED;
if(mouse_press[button] != release) {
// button state is already what we want
return;
}
mouse_press[button] = !release;
}
///////////////////////////////////
/*/
* When Moonlight sends mouse input through absolute coordinates,
* it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT.
* As a result, Sunshine will left click on hyperlinks in the browser before right clicking
*
* This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming
* As a compromise, Sunshine will only put delays on BUTTON_LEFT when
* absolute mouse coordinates have been send.
*
* Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released.
*
* input->mouse_left_button_timeout can only be nullptr
* when the last mouse coordinates were absolute
/*/
if(button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) {
input->mouse_left_button_timeout = task_pool.pushDelayed([=]() {
auto left_released = mouse_press[BUTTON_LEFT];
if(left_released) {
// Already released left button
return;
}
platf::button_mouse(platf_input, BUTTON_LEFT, release);
mouse_press[BUTTON_LEFT] = false;
input->mouse_left_button_timeout = nullptr;
}, 10ms).task_id;
return;
}
if(
button == BUTTON_RIGHT && !release &&
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY
) {
platf::button_mouse(platf_input, BUTTON_RIGHT, false);
platf::button_mouse(platf_input, BUTTON_RIGHT, true);
mouse_press[BUTTON_RIGHT] = false;
return;
}
///////////////////////////////////
platf::button_mouse(platf_input, button, release);
}
void repeat_key(short key_code) {
// If key no longer pressed, stop repeating
if(!key_press[key_code]) {
task_id = nullptr;
return;
}
platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED);
platf::keyboard(platf_input, key_code & 0x00FF, false);
task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id;
}
void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
auto constexpr BUTTON_RELEASED = 0x04;
key_press[packet->keyCode] = packet->keyAction != BUTTON_RELEASED;
platf::keyboard(platf_input, packet->keyCode & 0x00FF, packet->keyAction == BUTTON_RELEASED);
auto release = packet->keyAction == BUTTON_RELEASED;
auto &pressed = key_press[packet->keyCode];
if(!pressed) {
if(!release) {
if(task_id) {
task_pool.cancel(task_id);
}
if(config::input.key_repeat_delay.count() > 0) {
task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, packet->keyCode).task_id;
}
}
else {
// Already released
return;
}
}
else if(!release) {
// Already pressed down key
return;
}
pressed = !release;
platf::keyboard(platf_input, packet->keyCode & 0x00FF, release);
}
void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) {
void passthrough(PNV_SCROLL_PACKET packet) {
display_cursor = true;
platf::scroll(input, util::endian::big(packet->scrollAmt1));
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
}
int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std::int16_t new_state) {
@@ -338,8 +484,11 @@ void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t
int input_type = util::endian::big(*(int*)payload);
switch(input_type) {
case PACKET_TYPE_MOUSE_MOVE:
passthrough(platf_input, (PNV_MOUSE_MOVE_PACKET)payload);
case PACKET_TYPE_REL_MOUSE_MOVE:
passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_ABS_MOUSE_MOVE:
passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_MOUSE_BUTTON:
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
@@ -348,7 +497,7 @@ void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t
{
char *tmp_input = (char*)payload + 4;
if(tmp_input[0] == 0x0A) {
passthrough(platf_input, (PNV_SCROLL_PACKET)payload);
passthrough((PNV_SCROLL_PACKET)payload);
}
else {
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
@@ -366,7 +515,28 @@ void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&in
task_pool.push(passthrough_helper, input, util::cmove(input_data));
}
void reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(task_id);
task_pool.cancel(input->mouse_left_button_timeout);
// Ensure input is synchronous, by using the task_pool
task_pool.push([]() {
for(int x = 0; x < mouse_press.size(); ++x) {
if(mouse_press[x]) {
platf::button_mouse(platf_input, x, true);
mouse_press[x] = false;
}
}
for(auto& kp : key_press) {
platf::keyboard(platf_input, kp.first & 0x00FF, true);
key_press[kp.first] = false;
}
});
}
void init() {
touch_port_event = std::make_unique<touch_port_event_t::element_type>();
platf_input = platf::input();
}

View File

@@ -5,17 +5,23 @@
#ifndef SUNSHINE_INPUT_H
#define SUNSHINE_INPUT_H
#include "thread_safe.h"
#include "platform/common.h"
namespace input {
struct input_t;
void print(void *input);
void reset(std::shared_ptr<input_t> &input);
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
void init();
std::shared_ptr<input_t> alloc();
using touch_port_event_t = std::unique_ptr<safe::event_t<platf::touch_port_t>>;
extern touch_port_event_t touch_port_event;
}
#endif //SUNSHINE_INPUT_H

View File

@@ -5,7 +5,6 @@
#include "process.h"
#include <thread>
#include <filesystem>
#include <iostream>
#include <csignal>
@@ -13,8 +12,9 @@
#include <boost/log/sinks.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/attributes/clock.hpp>
#include "input.h"
#include "video.h"
#include "nvhttp.h"
#include "rtsp.h"
#include "config.h"
@@ -23,6 +23,7 @@
#include "platform/common.h"
extern "C" {
#include <rs.h>
#include <libavutil/log.h>
}
using namespace std::literals;
@@ -68,13 +69,19 @@ int main(int argc, char *argv[]) {
return 0;
}
if(config::sunshine.min_log_level >= 2) {
av_log_set_level(AV_LOG_QUIET);
}
sink = boost::make_shared<text_sink>();
boost::shared_ptr<std::ostream> stream { &std::cout, NoDelete {} };
sink->locked_backend()->add_stream(stream);
sink->set_filter(severity >= config::sunshine.min_log_level);
sink->set_formatter([severity="Severity"s](const bl::record_view &view, bl::formatting_ostream &os) {
sink->set_formatter([message="Message"s, severity="Severity"s](const bl::record_view &view, bl::formatting_ostream &os) {
constexpr int DATE_BUFFER_SIZE = 21 +2 +1; // Full string plus ": \0"
auto log_level = view.attribute_values()[severity].extract<int>().get();
std::string_view log_type;
@@ -99,7 +106,11 @@ int main(int argc, char *argv[]) {
break;
};
os << log_type << view.attribute_values()["Message"].extract<std::string>();
char _date[DATE_BUFFER_SIZE];
std::time_t t = std::time(nullptr);
strftime(_date, DATE_BUFFER_SIZE, "[%Y:%m:%d:%H:%M:%S]: ", std::localtime(&t));
os << _date << log_type << view.attribute_values()[message].extract<std::string>();
});
bl::core::get()->add_sink(sink);
@@ -126,8 +137,14 @@ int main(int argc, char *argv[]) {
proc::proc = std::move(*proc_opt);
auto deinit_guard = platf::init();
input::init();
if(!deinit_guard) {
return 4;
}
reed_solomon_init();
if(video::init()) {
return 2;
}
task_pool.start(1);
@@ -135,6 +152,8 @@ int main(int argc, char *argv[]) {
stream::rtpThread(shutdown_event);
httpThread.join();
task_pool.stop();
task_pool.join();
return 0;
}

View File

@@ -33,7 +33,7 @@ constexpr auto PORT_HTTP = 47989;
constexpr auto PORT_HTTPS = 47984;
constexpr auto VERSION = "7.1.400.0";
constexpr auto GFE_VERSION = "2.0.0.1";
constexpr auto GFE_VERSION = "3.12.0.1";
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
@@ -168,7 +168,15 @@ void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op)
}
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
auto salt = util::from_hex<std::array<uint8_t, 16>>(sess.async_insert_pin.salt, true);
if(sess.async_insert_pin.salt.size() < 32) {
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
return;
}
std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 };
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
auto key = crypto::gen_aes_key(*salt, pin);
sess.cipher_key = std::make_unique<crypto::aes_t>(key);
@@ -352,7 +360,11 @@ void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_
ptr->second.async_insert_pin.salt = std::move(args.at("salt"s));
if(config::sunshine.flags[config::flag::PIN_STDIN]) {
if(config::sunshine.flags[config::flag::CONST_PIN]) {
std::string pin("6174");
getservercert(ptr->second, tree, pin);
}
else if(config::sunshine.flags[config::flag::PIN_STDIN]) {
std::string pin;
std::cout << "Please insert pin: "sv;
@@ -464,13 +476,13 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
tree.put("root.GfeVersion", GFE_VERSION);
tree.put("root.uniqueid", unique_id);
tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address()));
tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 0 ? "1869449984" : "0");
tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0");
tree.put("root.LocalIP", request->local_endpoint_address());
if(config::video.hevc_mode == 2) {
if(config::video.hevc_mode == 3) {
tree.put("root.ServerCodecModeSupport", "3843");
}
else if(config::video.hevc_mode == 1) {
else if(config::video.hevc_mode == 2) {
tree.put("root.ServerCodecModeSupport", "259");
}
else {
@@ -484,7 +496,7 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
auto current_appid = proc::proc.running();
tree.put("root.PairStatus", pair_status);
tree.put("root.currentgame", current_appid >= 0 ? current_appid + 1 : 0);
tree.put("root.state", "_SERVER_BUSY");
tree.put("root.state", current_appid >= 0 ? "_SERVER_BUSY" : "_SERVER_FREE");
std::ostringstream data;
@@ -522,7 +534,7 @@ void applist(resp_https_t response, req_https_t request) {
for(auto &proc : proc::proc.get_apps()) {
pt::ptree app;
app.put("IsHdrSupported"s, config::video.hevc_mode == 2 ? 1 : 0);
app.put("IsHdrSupported"s, config::video.hevc_mode == 3 ? 1 : 0);
app.put("AppTitle"s, proc.name);
app.put("ID"s, ++x);
@@ -541,8 +553,6 @@ void launch(resp_https_t response, req_https_t request) {
response->write(data.str());
});
BOOST_LOG(fatal) << stream::session_count();
if(stream::session_count() == config::stream.channels) {
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 503);
@@ -601,7 +611,6 @@ void resume(resp_https_t response, req_https_t request) {
// It is possible that due a race condition that this if-statement gives a false negative,
// that is automatically resolved in rtsp_server_t
if(stream::session_count() == config::stream.channels) {
BOOST_LOG(fatal) << stream::session_count();
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 503);
@@ -762,7 +771,7 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
}
}
auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>();
auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>(30);
// Ugly hack for verifying certificates, see crypto::cert_chain_t::verify() for details
ctx->set_verify_callback([&cert_chain, add_cert](int verified, boost::asio::ssl::verify_context &ctx) {
@@ -827,8 +836,32 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
http_server.config.address = "0.0.0.0"s;
http_server.config.port = PORT_HTTP;
std::thread ssl { &https_server_t::start, &https_server };
std::thread tcp { &http_server_t::start, &http_server };
try {
https_server.bind();
http_server.bind();
} catch(boost::system::system_error &err) {
BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what();
shutdown_event->raise(true);
return;
}
auto accept_and_run = [&](auto *http_server) {
try {
http_server->accept_and_run();
} catch(boost::system::system_error &err) {
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
if(shutdown_event->peek()) {
return;
}
BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what();
shutdown_event->raise(true);
return;
}
};
std::thread ssl { accept_and_run, &https_server };
std::thread tcp { accept_and_run, &http_server };
// Wait for any event
shutdown_event->view();

View File

@@ -6,6 +6,7 @@
#define SUNSHINE_COMMON_H
#include <string>
#include <mutex>
#include "sunshine/utility.h"
struct sockaddr;
@@ -28,6 +29,47 @@ constexpr std::uint16_t B = 0x2000;
constexpr std::uint16_t X = 0x4000;
constexpr std::uint16_t Y = 0x8000;
enum class dev_type_e {
none,
dxgi,
unknown
};
enum class pix_fmt_e {
yuv420p,
yuv420p10,
nv12,
p010,
unknown
};
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals;
#define _CONVERT(x) case pix_fmt_e:: x : return #x ## sv
switch(pix_fmt) {
_CONVERT(yuv420p);
_CONVERT(yuv420p10);
_CONVERT(nv12);
_CONVERT(p010);
_CONVERT(unknown);
}
#undef _CONVERT
return "unknown"sv;
}
// Dimensions for touchscreen input
struct touch_port_t {
std::uint32_t offset_x, offset_y;
std::uint32_t width, height;
constexpr touch_port_t(
std::uint32_t offset_x, std::uint32_t offset_y,
std::uint32_t width, std::uint32_t height) noexcept :
offset_x { offset_x }, offset_y { offset_y },
width { width }, height { height } {};
};
struct gamepad_state_t {
std::uint16_t buttonFlags;
std::uint8_t lt;
@@ -58,6 +100,19 @@ public:
virtual ~img_t() = default;
};
struct hwdevice_t {
void *data {};
platf::img_t *img {};
virtual int convert(platf::img_t &img) {
return -1;
}
virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {};
virtual ~hwdevice_t() = default;
};
enum class capture_e : int {
ok,
reinit,
@@ -67,10 +122,22 @@ enum class capture_e : int {
class display_t {
public:
virtual capture_e snapshot(img_t *img, bool cursor) = 0;
virtual std::unique_ptr<img_t> alloc_img() = 0;
display_t() noexcept : offset_x { 0 }, offset_y { 0 } {}
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) = 0;
virtual std::shared_ptr<img_t> alloc_img() = 0;
virtual int dummy_img(img_t *img) = 0;
virtual std::shared_ptr<hwdevice_t> make_hwdevice(int width, int height, pix_fmt_e pix_fmt) {
return std::make_shared<hwdevice_t>();
}
virtual ~display_t() = default;
// Offsets for when streaming a specific monitor. By default, they are 0.
int offset_x, offset_y;
int width, height;
};
class mic_t {
@@ -91,10 +158,11 @@ std::string from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate);
std::unique_ptr<display_t> display();
std::shared_ptr<display_t> display(dev_type_e hwdevice_type);
input_t input();
void move_mouse(input_t &input, int deltaX, int deltaY);
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void button_mouse(input_t &input, int button, bool release);
void scroll(input_t &input, int distance);
void keyboard(input_t &input, uint16_t modcode, bool release);

View File

@@ -1,429 +0,0 @@
//
// Created by loki on 6/21/19.
//
#include "common.h"
#include <fstream>
#include <bitset>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#include <xcb/shm.h>
#include <xcb/xfixes.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include "sunshine/task_pool.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
namespace platf {
using namespace std::literals;
void freeImage(XImage *);
void freeX(XFixesCursorImage *);
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>;
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
using xcb_cursor_img = util::c_ptr<xcb_xfixes_get_cursor_image_reply_t>;
using xdisplay_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
using ximg_t = util::safe_ptr<XImage, freeImage>;
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
class shm_id_t {
public:
shm_id_t() : id { -1 } {}
shm_id_t(int id) : id {id } {}
shm_id_t(shm_id_t &&other) noexcept : id(other.id) {
other.id = -1;
}
~shm_id_t() {
if(id != -1) {
shmctl(id, IPC_RMID, nullptr);
id = -1;
}
}
int id;
};
class shm_data_t {
public:
shm_data_t() : data {(void*)-1 } {}
shm_data_t(void *data) : data {data } {}
shm_data_t(shm_data_t &&other) noexcept : data(other.data) {
other.data = (void*)-1;
}
~shm_data_t() {
if((std::uintptr_t)data != -1) {
shmdt(data);
data = (void*)-1;
}
}
void *data;
};
struct x11_img_t : public img_t {
ximg_t img;
};
struct shm_img_t : public img_t {
~shm_img_t() override {
delete[] data;
data = nullptr;
}
};
void blend_cursor(Display *display, img_t &img) {
xcursor_t overlay { XFixesGetCursorImage(display) };
if(!overlay) {
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
return;
}
overlay->x -= overlay->xhot;
overlay->y -= overlay->yhot;
overlay->x = std::max((short)0, overlay->x);
overlay->y = std::max((short)0, overlay->y);
auto pixels = (int*)img.data;
auto screen_height = img.height;
auto screen_width = img.width;
auto delta_height = std::min<uint16_t>(overlay->height, std::max(0, screen_height - overlay->y));
auto delta_width = std::min<uint16_t>(overlay->width, std::max(0, screen_width - overlay->x));
for(auto y = 0; y < delta_height; ++y) {
auto overlay_begin = &overlay->pixels[y * overlay->width];
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x];
std::for_each(overlay_begin, overlay_end, [&](long pixel) {
int *pixel_p = (int*)&pixel;
auto colors_in = (uint8_t*)pixels_begin;
auto alpha = (*(uint*)pixel_p) >> 24u;
if(alpha == 255) {
*pixels_begin = *pixel_p;
}
else {
auto colors_out = (uint8_t*)pixel_p;
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;
}
++pixels_begin;
});
}
}
struct x11_attr_t : public display_t {
x11_attr_t() : xdisplay {XOpenDisplay(nullptr) }, xwindow { }, xattr {} {
if(!xdisplay) {
BOOST_LOG(fatal) << "Could not open x11 display"sv;
log_flush();
std::abort();
}
xwindow = DefaultRootWindow(xdisplay.get());
refresh();
}
void refresh() {
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr);
}
capture_e snapshot(img_t *img_out_base, bool cursor) override {
refresh();
XImage *img { XGetImage(
xdisplay.get(),
xwindow,
0, 0,
xattr.width, xattr.height,
AllPlanes, ZPixmap)
};
auto img_out = (x11_img_t*)img_out_base;
img_out->width = img->width;
img_out->height = img->height;
img_out->data = (uint8_t*)img->data;
img_out->row_pitch = img->bytes_per_line;
img_out->pixel_pitch = img->bits_per_pixel / 8;
img_out->img.reset(img);
if(cursor) {
blend_cursor(xdisplay.get(), *img_out_base);
}
return capture_e::ok;
}
std::unique_ptr<img_t> alloc_img() override {
return std::make_unique<x11_img_t>();
}
xdisplay_t xdisplay;
Window xwindow;
XWindowAttributes xattr;
};
struct shm_attr_t : public x11_attr_t {
xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
xcb_connect_t xcb;
xcb_screen_t *display;
std::uint32_t seg;
shm_id_t shm_id;
shm_data_t data;
util::TaskPool::task_id_t refresh_task_id;
void delayed_refresh() {
refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
shm_attr_t() : x11_attr_t(), shm_xdisplay {XOpenDisplay(nullptr) } {
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
~shm_attr_t() override {
while(!task_pool.cancel(refresh_task_id));
}
capture_e snapshot(img_t *img, bool cursor) override {
if(display->width_in_pixels != xattr.width || display->height_in_pixels != xattr.height) {
return capture_e::reinit;
}
auto img_cookie = xcb_shm_get_image_unchecked(
xcb.get(),
display->root,
0, 0,
display->width_in_pixels, display->height_in_pixels,
~0,
XCB_IMAGE_FORMAT_Z_PIXMAP,
seg,
0
);
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
if(!img_reply) {
BOOST_LOG(error) << "Could not get image reply"sv;
return capture_e::reinit;
}
if(img->width != display->width_in_pixels || img->height != display->height_in_pixels) {
delete[] img->data;
img->data = new std::uint8_t[frame_size()];
img->width = display->width_in_pixels;
img->height = display->height_in_pixels;
img->pixel_pitch = 4;
img->row_pitch = img->width * img->pixel_pitch;
}
std::copy_n((std::uint8_t*)data.data, frame_size(), img->data);
if(cursor) {
blend_cursor(shm_xdisplay.get(), *img);
}
return capture_e::ok;
}
std::unique_ptr<img_t> alloc_img() override {
return std::make_unique<shm_img_t>();
}
int init() {
shm_xdisplay.reset(XOpenDisplay(nullptr));
xcb.reset(xcb_connect(nullptr, nullptr));
if(xcb_connection_has_error(xcb.get())) {
return -1;
}
if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) {
BOOST_LOG(error) << "Missing SHM extension"sv;
return -1;
}
auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get()));
display = iter.data;
seg = xcb_generate_id(xcb.get());
shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777);
if(shm_id.id == -1) {
BOOST_LOG(error) << "shmget failed"sv;
return -1;
}
xcb_shm_attach(xcb.get(), seg, shm_id.id, false);
data.data = shmat(shm_id.id, nullptr, 0);
if ((uintptr_t)data.data == -1) {
BOOST_LOG(error) << "shmat failed"sv;
return -1;
}
return 0;
}
std::uint32_t frame_size() {
return display->height_in_pixels * display->width_in_pixels * 4;
}
};
struct mic_attr_t : public mic_t {
pa_sample_spec ss;
util::safe_ptr<pa_simple, pa_simple_free> mic;
explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {}
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
int status;
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error;
}
return capture_e::ok;
}
};
std::unique_ptr<display_t> shm_display() {
auto shm = std::make_unique<shm_attr_t>();
if(shm->init()) {
return nullptr;
}
return shm;
}
std::unique_ptr<display_t> display() {
auto shm_disp = shm_display();
if(!shm_disp) {
return std::unique_ptr<display_t> { new x11_attr_t {} };
}
return shm_disp;
}
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
int status;
const char *audio_sink = nullptr;
if(!config::audio.sink.empty()) {
audio_sink = config::audio.sink.c_str();
}
else {
audio_sink = "@DEFAULT_MONITOR@";
}
mic->mic.reset(
pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, "sunshine-record", &mic->ss, nullptr, nullptr, &status)
);
if(!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
log_flush();
std::abort();
}
return mic;
}
ifaddr_t get_ifaddrs() {
ifaddrs *p { nullptr };
getifaddrs(&p);
return ifaddr_t { p };
}
std::string from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
}
return std::string { data };
}
std::pair<std::uint16_t, std::string> 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 } };
}
std::string get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs();
for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address");
if(mac_file.good()) {
std::string mac_address;
std::getline(mac_file, mac_address);
return mac_address;
}
}
}
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
void freeImage(XImage *p) {
XDestroyImage(p);
}
void freeX(XFixesCursorImage *p) {
XFree(p);
}
}

View File

@@ -0,0 +1,507 @@
//
// Created by loki on 6/21/19.
//
#include "sunshine/platform/common.h"
#include <fstream>
#include <bitset>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <xcb/shm.h>
#include <xcb/xfixes.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include "sunshine/task_pool.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
namespace platf
{
using namespace std::literals;
void freeImage(XImage*);
void freeX(XFixesCursorImage*);
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>;
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
using xdisplay_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
using ximg_t = util::safe_ptr<XImage, freeImage>;
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
using crtc_info_t = util::safe_ptr<_XRRCrtcInfo, XRRFreeCrtcInfo>;
using output_info_t = util::safe_ptr<_XRROutputInfo, XRRFreeOutputInfo>;
using screen_res_t = util::safe_ptr<_XRRScreenResources, XRRFreeScreenResources>;
class shm_id_t {
public:
shm_id_t() : id { -1 } {}
shm_id_t(int id) : id { id } {}
shm_id_t(shm_id_t &&other) noexcept : id(other.id) {
other.id = -1;
}
~shm_id_t() {
if (id != -1) {
shmctl(id, IPC_RMID, nullptr);
id = -1;
}
}
int id;
};
class shm_data_t {
public:
shm_data_t() : data { (void*) -1 } {}
shm_data_t(void *data) : data { data } {}
shm_data_t(shm_data_t &&other) noexcept : data(other.data) {
other.data = (void*)-1;
}
~shm_data_t() {
if((std::uintptr_t) data != -1) {
shmdt(data);
}
}
void *data;
};
struct x11_img_t: public img_t {
ximg_t img;
};
struct shm_img_t: public img_t {
~shm_img_t() override {
delete[] data;
data = nullptr;
}
};
void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
xcursor_t overlay { XFixesGetCursorImage(display) };
if (!overlay) {
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
return;
}
overlay->x -= overlay->xhot;
overlay->y -= overlay->yhot;
overlay->x -= offsetX;
overlay->y -= offsetY;
overlay->x = std::max((short)0, overlay->x);
overlay->y = std::max((short)0, overlay->y);
auto pixels = (int*)img.data;
auto screen_height = img.height;
auto screen_width = img.width;
auto delta_height = std::min<uint16_t>(overlay->height,std::max(0, screen_height - overlay->y));
auto delta_width = std::min<uint16_t>(overlay->width,std::max(0, screen_width - overlay->x));
for (auto y = 0; y < delta_height; ++y) {
auto overlay_begin = &overlay->pixels[y * overlay->width];
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
auto pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x];
std::for_each(overlay_begin, overlay_end,[&](long pixel) {
int *pixel_p = (int*) &pixel;
auto colors_in = (uint8_t*) pixels_begin;
auto alpha = (*(uint*) pixel_p) >> 24u;
if (alpha == 255) {
*pixels_begin = *pixel_p;
}
else {
auto colors_out = (uint8_t*) pixel_p;
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;
}
++pixels_begin;
});
}
}
struct x11_attr_t: public display_t
{
xdisplay_t xdisplay;
Window xwindow;
XWindowAttributes xattr;
/*
* Last X (NOT the streamed monitor!) size.
* This way we can trigger reinitialization if the dimensions changed while streaming
*/
int lastWidth, lastHeight;
x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow { }, xattr { } {
XInitThreads();
}
int init() {
if(!xdisplay) {
BOOST_LOG(error) << "Could not open X11 display"sv;
return -1;
}
xwindow = DefaultRootWindow(xdisplay.get());
refresh();
int streamedMonitor = -1;
if(!config::video.output_name.empty()) {
streamedMonitor = (int)util::from_view(config::video.output_name);
}
if(streamedMonitor != -1) {
BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv;
screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) };
int output = screenr->noutput;
if(streamedMonitor >= output) {
BOOST_LOG(error) << "Could not stream display number ["sv << streamedMonitor << "], there are only ["sv << output << "] displays."sv;
return -1;
}
output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[streamedMonitor]) };
if(!out_info || out_info->connection != RR_Connected) {
BOOST_LOG(error) << "Could not stream selected display because it doesn't seem to be connected"sv;
return -1;
}
crtc_info_t crt_info { XRRGetCrtcInfo(xdisplay.get(), screenr.get(), out_info->crtc) };
BOOST_LOG(info)
<< "Streaming display: "sv << out_info->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;
width = crt_info->width;
height = crt_info->height;
offset_x = crt_info->x;
offset_y = crt_info->y;
}
else {
width = xattr.width;
height = xattr.height;
}
lastWidth = xattr.width;
lastHeight = xattr.height;
return 0;
}
/**
* Called when the display attributes should change.
*/
void refresh() {
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
}
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override {
refresh();
//The whole X server changed, so we gotta reinit everything
if (xattr.width != lastWidth || xattr.height != lastHeight) {
BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv;
return capture_e::reinit;
}
XImage *img { XGetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
auto img_out = (x11_img_t*) img_out_base;
img_out->width = img->width;
img_out->height = img->height;
img_out->data = (uint8_t*) img->data;
img_out->row_pitch = img->bytes_per_line;
img_out->pixel_pitch = img->bits_per_pixel / 8;
img_out->img.reset(img);
if (cursor) {
blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
}
return capture_e::ok;
}
std::shared_ptr<img_t> alloc_img() override {
return std::make_shared<x11_img_t>();
}
int dummy_img(img_t *img) override {
snapshot(img, 0s, true);
return 0;
}
};
struct shm_attr_t: public x11_attr_t {
xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
xcb_connect_t xcb;
xcb_screen_t *display;
std::uint32_t seg;
shm_id_t shm_id;
shm_data_t data;
util::TaskPool::task_id_t refresh_task_id;
void delayed_refresh() {
refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id;
}
shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) } {
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
~shm_attr_t() override {
while(!task_pool.cancel(refresh_task_id));
}
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override {
//The whole X server changed, so we gotta reinit everything
if(xattr.width != lastWidth || xattr.height != lastHeight) {
BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv;
return capture_e::reinit;
}
else {
auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie,nullptr) };
if(!img_reply) {
BOOST_LOG(error) << "Could not get image reply"sv;
return capture_e::reinit;
}
std::copy_n((std::uint8_t*)data.data, frame_size(), img->data);
if(cursor) {
blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y);
}
return capture_e::ok;
}
}
std::shared_ptr<img_t> alloc_img() override {
auto img = std::make_shared<shm_img_t>();
img->width = width;
img->height = height;
img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch];
return img;
}
int dummy_img(platf::img_t *img) override {
return 0;
}
int init() {
if(x11_attr_t::init()) {
return 1;
}
shm_xdisplay.reset(XOpenDisplay(nullptr));
xcb.reset(xcb_connect(nullptr, nullptr));
if(xcb_connection_has_error(xcb.get())) {
return -1;
}
if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) {
BOOST_LOG(error) << "Missing SHM extension"sv;
return -1;
}
auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get()));
display = iter.data;
seg = xcb_generate_id(xcb.get());
shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777);
if(shm_id.id == -1) {
BOOST_LOG(error) << "shmget failed"sv;
return -1;
}
xcb_shm_attach(xcb.get(), seg, shm_id.id, false);
data.data = shmat(shm_id.id, nullptr, 0);
if((uintptr_t)data.data == -1) {
BOOST_LOG(error) << "shmat failed"sv;
return -1;
}
return 0;
}
std::uint32_t frame_size() {
return width * height * 4;
}
};
struct mic_attr_t: public mic_t {
pa_sample_spec ss;
util::safe_ptr<pa_simple, pa_simple_free> mic;
explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate,
std::uint8_t channels) : ss { format, sample_rate, channels }, mic { } { }
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
int status;
if(pa_simple_read(mic.get(), buf, sample_size *2, &status)) {
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error;
}
return capture_e::ok;
}
};
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) {
if(hwdevice_type != platf::dev_type_e::none) {
BOOST_LOG(error)<< "Could not initialize display with the given hw device type."sv;
return nullptr;
}
// Attempt to use shared memory X11 to avoid copying the frame
auto shm_disp = std::make_shared<shm_attr_t>();
auto status = shm_disp->init();
if(status > 0) {
// x11_attr_t::init() failed, don't bother trying again.
return nullptr;
}
if(status == 0) {
return shm_disp;
}
// Fallback
auto x11_disp = std::make_shared<x11_attr_t>();
if(x11_disp->init()) {
return nullptr;
}
return x11_disp;
}
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
int status;
const char *audio_sink = "@DEFAULT_MONITOR@";
if (!config::audio.sink.empty()) {
audio_sink = config::audio.sink.c_str();
}
mic->mic.reset(
pa_simple_new(nullptr, "sunshine",
pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
"sunshine-record", &mic->ss, nullptr, nullptr, &status));
if (!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
log_flush();
std::abort();
}
return mic;
}
ifaddr_t get_ifaddrs() {
ifaddrs *p { nullptr };
getifaddrs(&p);
return ifaddr_t { p };
}
std::string from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
}
if (family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
}
return std::string { data };
}
std::pair<std::uint16_t, std::string> 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} };
}
std::string get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs();
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address");
if (mac_file.good()) {
std::string mac_address;
std::getline(mac_file, mac_address);
return mac_address;
}
}
}
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
void freeImage(XImage *p) {
XDestroyImage(p);
}
void freeX(XFixesCursorImage *p) {
XFree(p);
}
}

View File

@@ -6,10 +6,11 @@
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <cmath>
#include <cstring>
#include <filesystem>
#include "common.h"
#include "sunshine/platform/common.h"
#include "sunshine/main.h"
#include "sunshine/utility.h"
@@ -29,8 +30,22 @@ using uinput_t = util::safe_ptr<libevdev_uinput, libevdev_uinput_destroy>;
using keyboard_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
constexpr touch_port_t target_touch_port {
0, 0,
19200, 12000
};
struct input_raw_t {
public:
void clear_touchscreen() {
std::filesystem::path touch_path { "sunshine_touchscreen"sv };
if(std::filesystem::is_symlink(touch_path)) {
std::filesystem::remove(touch_path);
}
touch_input.reset();
}
void clear_mouse() {
std::filesystem::path mouse_path { "sunshine_mouse"sv };
@@ -55,9 +70,7 @@ public:
}
int create_mouse() {
libevdev_uinput *buf;
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
mouse_input.reset(buf);
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &mouse_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Mouse: "sv << strerror(-err);
@@ -69,13 +82,24 @@ public:
return 0;
}
int create_touchscreen() {
int err = libevdev_uinput_create_from_device(touch_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &touch_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Touchscreen: "sv << strerror(-err);
return -1;
}
std::filesystem::create_symlink(libevdev_uinput_get_devnode(touch_input.get()), "sunshine_touchscreen"sv);
return 0;
}
int alloc_gamepad(int nr) {
TUPLE_2D_REF(input, gamepad_state, gamepads[nr]);
libevdev_uinput *buf;
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input);
input.reset(buf);
gamepad_state = gamepad_state_t {};
if(err) {
@@ -96,6 +120,7 @@ public:
}
void clear() {
clear_touchscreen();
clear_mouse();
for(int x = 0; x < gamepads.size(); ++x) {
clear_gamepad(x);
@@ -106,16 +131,31 @@ public:
clear();
}
evdev_t gamepad_dev;
std::vector<std::pair<uinput_t, gamepad_state_t>> gamepads;
evdev_t mouse_dev;
uinput_t mouse_input;
uinput_t touch_input;
evdev_t gamepad_dev;
evdev_t touch_dev;
evdev_t mouse_dev;
keyboard_t keyboard;
};
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto touchscreen = ((input_raw_t*)input.get())->touch_input.get();
auto scaled_x = (int)std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
auto scaled_y = (int)std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, scaled_x);
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, scaled_y);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 1);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 0);
libevdev_uinput_write_event(touchscreen, EV_SYN, SYN_REPORT, 0);
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
auto mouse = ((input_raw_t*)input.get())->mouse_input.get();
@@ -409,6 +449,48 @@ evdev_t mouse() {
return dev;
}
evdev_t touchscreen() {
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Touch");
libevdev_set_id_product(dev.get(), 0xDEAD);
libevdev_set_id_vendor(dev.get(), 0xBEEF);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x111);
libevdev_set_name(dev.get(), "Touchscreen passthrough");
libevdev_enable_property(dev.get(), INPUT_PROP_DIRECT);
libevdev_enable_event_type(dev.get(), EV_KEY);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOUCH, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_PEN, nullptr); // Needed to be enabled for BTN_TOOL_FINGER to work.
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_FINGER, nullptr);
input_absinfo absx {
0,
0,
target_touch_port.width,
1,
0,
28
};
input_absinfo absy {
0,
0,
target_touch_port.height,
1,
0,
28
};
libevdev_enable_event_type(dev.get(), EV_ABS);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_X, &absx);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Y, &absy);
return dev;
}
evdev_t x360() {
evdev_t dev { libevdev_new() };
@@ -486,10 +568,11 @@ input_t input() {
// Ensure starting from clean slate
gp.clear();
gp.touch_dev = touchscreen();
gp.mouse_dev = mouse();
gp.gamepad_dev = x360();
if(gp.create_mouse()) {
if(gp.create_mouse() || gp.create_touchscreen()) {
log_flush();
std::abort();
}
@@ -502,5 +585,5 @@ void freeInput(void *p) {
delete input;
}
std::unique_ptr<deinit_t> init() { return nullptr; }
std::unique_ptr<deinit_t> init() { return std::make_unique<deinit_t>(); }
}

View File

@@ -12,7 +12,7 @@
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "common.h"
#include "sunshine/platform/common.h"
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
@@ -81,50 +81,43 @@ public:
HRESULT status;
device_enum_t::pointer device_enum_p{};
status = CoCreateInstance(
CLSID_MMDeviceEnumerator,
nullptr,
CLSCTX_ALL,
IID_IMMDeviceEnumerator,
(void **) &device_enum_p);
device_enum.reset(device_enum_p);
(void **) &device_enum);
if (FAILED(status)) {
if(FAILED(status)) {
BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
device_t::pointer device_p{};
if(config::audio.sink.empty()) {
status = device_enum->GetDefaultAudioEndpoint(
eRender,
eConsole,
&device_p);
&device);
}
else {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
auto wstring_device_id = converter.from_bytes(config::audio.sink);
status = device_enum->GetDevice(wstring_device_id.c_str(), &device_p);
status = device_enum->GetDevice(wstring_device_id.c_str(), &device);
}
device.reset(device_p);
if (FAILED(status)) {
if(FAILED(status)) {
BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
audio_client_t::pointer audio_client_p{};
status = device->Activate(
IID_IAudioClient,
CLSCTX_ALL,
nullptr,
(void **) &audio_client_p);
audio_client.reset(audio_client_p);
(void **) &audio_client);
if (FAILED(status)) {
BOOST_LOG(error) << "Couldn't activate audio Device [0x"sv << util::hex(status).to_string_view() << ']';
@@ -132,11 +125,8 @@ public:
return -1;
}
wave_format_t::pointer wave_format_p{};
status = audio_client->GetMixFormat(&wave_format_p);
wave_format.reset(wave_format_p);
if (FAILED(status)) {
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 -1;
@@ -198,9 +188,7 @@ public:
sample_buf = util::buffer_t<std::int16_t> { frames };
sample_buf_pos = std::begin(sample_buf);
audio_capture_t::pointer audio_capture_p {};
status = audio_client->GetService(IID_IAudioCaptureClient, (void**)&audio_capture_p);
audio_capture.reset(audio_capture_p);
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() << ']';
@@ -319,24 +307,26 @@ public:
}
namespace platf {
class dummy_mic_t : public mic_t {
public:
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
return capture_e::ok;
}
};
// It's not big enough to justify it's own source file :/
namespace dxgi {
int init();
}
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
auto mic = std::make_unique<audio::mic_wasapi_t>();
if(mic->init(sample_rate)) {
return std::make_unique<dummy_mic_t>();
return nullptr;
}
return mic;
}
std::unique_ptr<deinit_t> init() {
if(dxgi::init()) {
return nullptr;
}
return std::make_unique<platf::audio::co_init_t>();
}
}

View File

@@ -0,0 +1,130 @@
//
// Created by loki on 4/23/20.
//
#ifndef SUNSHINE_DISPLAY_H
#define SUNSHINE_DISPLAY_H
#include <dxgi.h>
#include <d3d11.h>
#include <d3d11_4.h>
#include <d3dcommon.h>
#include <dxgi1_2.h>
#include "sunshine/utility.h"
#include "sunshine/platform/common.h"
namespace platf::dxgi {
extern const char *format_str[];
template<class T>
void Release(T *dxgi) {
dxgi->Release();
}
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11DeviceContext>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>;
namespace video {
using device_t = util::safe_ptr<ID3D11VideoDevice, Release<ID3D11VideoDevice>>;
using ctx_t = util::safe_ptr<ID3D11VideoContext, Release<ID3D11VideoContext>>;
using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11VideoProcessor>>;
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
}
class hwdevice_t;
struct cursor_t {
std::vector<std::uint8_t> img_data;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
int x, y;
bool visible;
};
struct gpu_cursor_t {
texture2d_t texture;
LONG width, height;
};
class duplication_t {
public:
dup_t dup;
bool has_frame {};
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();
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 snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img) override;
int init();
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<display_vram_t> {
public:
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img_base) override;
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, pix_fmt_e pix_fmt) override;
gpu_cursor_t cursor;
std::vector<hwdevice_t*> hwdevices;
};
}
#endif

View File

@@ -0,0 +1,438 @@
//
// Created by loki on 1/12/20.
//
#include <codecvt>
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "sunshine/platform/common.h"
#include "display.h"
namespace platf {
using namespace std::literals;
}
namespace platf::dxgi {
capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
auto capture_status = release_frame();
if(capture_status != capture_e::ok) {
return capture_status;
}
auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p);
switch(status) {
case S_OK:
has_frame = true;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
}
}
capture_e duplication_t::reset(dup_t::pointer dup_p) {
auto capture_status = release_frame();
dup.reset(dup_p);
return capture_status;
}
capture_e duplication_t::release_frame() {
if(!has_frame) {
return capture_e::ok;
}
auto status = dup->ReleaseFrame();
switch (status) {
case S_OK:
has_frame = false;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
has_frame = false;
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
}
}
duplication_t::~duplication_t() {
release_frame();
}
int display_base_t::init() {
/* Uncomment when use of IDXGIOutput5 is implemented
std::call_once(windows_cpp_once_flag, []() {
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4);
typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value);
auto user32 = LoadLibraryA("user32.dll");
auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext");
if(f) {
f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
FreeLibrary(user32);
});
*/
HRESULT status;
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
auto adapter_name = converter.from_bytes(config::video.adapter_name);
auto output_name = converter.from_bytes(config::video.output_name);
adapter_t::pointer adapter_p;
for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
dxgi::adapter_t adapter_tmp { adapter_p };
DXGI_ADAPTER_DESC1 adapter_desc;
adapter_tmp->GetDesc1(&adapter_desc);
if(!adapter_name.empty() && adapter_desc.Description != adapter_name) {
continue;
}
dxgi::output_t::pointer output_p;
for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
dxgi::output_t output_tmp { output_p };
DXGI_OUTPUT_DESC desc;
output_tmp->GetDesc(&desc);
if(!output_name.empty() && desc.DeviceName != output_name) {
continue;
}
if(desc.AttachedToDesktop) {
output = std::move(output_tmp);
offset_x = desc.DesktopCoordinates.left;
offset_y = desc.DesktopCoordinates.top;
width = desc.DesktopCoordinates.right - offset_x;
height = desc.DesktopCoordinates.bottom - offset_y;
}
}
if(output) {
adapter = std::move(adapter_tmp);
break;
}
}
if(!output) {
BOOST_LOG(error) << "Failed to locate an output device"sv;
return -1;
}
D3D_FEATURE_LEVEL featureLevels[] {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv;
return -1;
}
status = D3D11CreateDevice(
adapter_p,
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION,
&device,
&feature_level,
&device_ctx);
adapter_p->Release();
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
DXGI_ADAPTER_DESC adapter_desc;
adapter->GetDesc(&adapter_desc);
auto description = converter.to_bytes(adapter_desc.Description);
BOOST_LOG(info)
<< std::endl
<< "Device Description : " << description << std::endl
<< "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl
<< "Capture size : "sv << width << 'x' << height;
// Bump up thread priority
{
const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY;
TOKEN_PRIVILEGES tp;
HANDLE token;
LUID val;
if (OpenProcessToken(GetCurrentProcess(), flags, &token) &&
!!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) {
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = val;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) {
BOOST_LOG(warning) << "Could not set privilege to increase GPU priority";
}
}
CloseHandle(token);
HMODULE gdi32 = GetModuleHandleA("GDI32");
if (gdi32) {
PD3DKMTSetProcessSchedulingPriorityClass fn =
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
if (fn) {
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if (FAILED(status)) {
BOOST_LOG(warning) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance.";
}
}
}
dxgi::dxgi_t dxgi;
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi);
if(FAILED(status)) {
BOOST_LOG(warning) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
dxgi->SetGPUThreadPriority(7);
}
// Try to reduce latency
{
dxgi::dxgi1_t dxgi {};
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
status = dxgi->SetMaximumFrameLatency(1);
if(FAILED(status)) {
BOOST_LOG(warning) << "Failed to set maximum frame latency [0x"sv << util::hex(status).to_string_view() << ']';
}
}
//FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD
//TODO: Use IDXGIOutput5 for improved performance
{
dxgi::output1_t output1 {};
status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
return -1;
}
// We try this twice, in case we still get an error on reinitialization
for(int x = 0; x < 2; ++x) {
status = output1->DuplicateOutput((IUnknown*)device.get(), &dup.dup);
if(SUCCEEDED(status)) {
break;
}
std::this_thread::sleep_for(200ms);
}
if(FAILED(status)) {
BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
DXGI_OUTDUPL_DESC dup_desc;
dup.dup->GetDesc(&dup_desc);
format = dup_desc.ModeDesc.Format;
BOOST_LOG(debug) << "Source format ["sv << format_str[dup_desc.ModeDesc.Format] << ']';
return 0;
}
const char *format_str[] = {
"DXGI_FORMAT_UNKNOWN",
"DXGI_FORMAT_R32G32B32A32_TYPELESS",
"DXGI_FORMAT_R32G32B32A32_FLOAT",
"DXGI_FORMAT_R32G32B32A32_UINT",
"DXGI_FORMAT_R32G32B32A32_SINT",
"DXGI_FORMAT_R32G32B32_TYPELESS",
"DXGI_FORMAT_R32G32B32_FLOAT",
"DXGI_FORMAT_R32G32B32_UINT",
"DXGI_FORMAT_R32G32B32_SINT",
"DXGI_FORMAT_R16G16B16A16_TYPELESS",
"DXGI_FORMAT_R16G16B16A16_FLOAT",
"DXGI_FORMAT_R16G16B16A16_UNORM",
"DXGI_FORMAT_R16G16B16A16_UINT",
"DXGI_FORMAT_R16G16B16A16_SNORM",
"DXGI_FORMAT_R16G16B16A16_SINT",
"DXGI_FORMAT_R32G32_TYPELESS",
"DXGI_FORMAT_R32G32_FLOAT",
"DXGI_FORMAT_R32G32_UINT",
"DXGI_FORMAT_R32G32_SINT",
"DXGI_FORMAT_R32G8X24_TYPELESS",
"DXGI_FORMAT_D32_FLOAT_S8X24_UINT",
"DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS",
"DXGI_FORMAT_X32_TYPELESS_G8X24_UINT",
"DXGI_FORMAT_R10G10B10A2_TYPELESS",
"DXGI_FORMAT_R10G10B10A2_UNORM",
"DXGI_FORMAT_R10G10B10A2_UINT",
"DXGI_FORMAT_R11G11B10_FLOAT",
"DXGI_FORMAT_R8G8B8A8_TYPELESS",
"DXGI_FORMAT_R8G8B8A8_UNORM",
"DXGI_FORMAT_R8G8B8A8_UNORM_SRGB",
"DXGI_FORMAT_R8G8B8A8_UINT",
"DXGI_FORMAT_R8G8B8A8_SNORM",
"DXGI_FORMAT_R8G8B8A8_SINT",
"DXGI_FORMAT_R16G16_TYPELESS",
"DXGI_FORMAT_R16G16_FLOAT",
"DXGI_FORMAT_R16G16_UNORM",
"DXGI_FORMAT_R16G16_UINT",
"DXGI_FORMAT_R16G16_SNORM",
"DXGI_FORMAT_R16G16_SINT",
"DXGI_FORMAT_R32_TYPELESS",
"DXGI_FORMAT_D32_FLOAT",
"DXGI_FORMAT_R32_FLOAT",
"DXGI_FORMAT_R32_UINT",
"DXGI_FORMAT_R32_SINT",
"DXGI_FORMAT_R24G8_TYPELESS",
"DXGI_FORMAT_D24_UNORM_S8_UINT",
"DXGI_FORMAT_R24_UNORM_X8_TYPELESS",
"DXGI_FORMAT_X24_TYPELESS_G8_UINT",
"DXGI_FORMAT_R8G8_TYPELESS",
"DXGI_FORMAT_R8G8_UNORM",
"DXGI_FORMAT_R8G8_UINT",
"DXGI_FORMAT_R8G8_SNORM",
"DXGI_FORMAT_R8G8_SINT",
"DXGI_FORMAT_R16_TYPELESS",
"DXGI_FORMAT_R16_FLOAT",
"DXGI_FORMAT_D16_UNORM",
"DXGI_FORMAT_R16_UNORM",
"DXGI_FORMAT_R16_UINT",
"DXGI_FORMAT_R16_SNORM",
"DXGI_FORMAT_R16_SINT",
"DXGI_FORMAT_R8_TYPELESS",
"DXGI_FORMAT_R8_UNORM",
"DXGI_FORMAT_R8_UINT",
"DXGI_FORMAT_R8_SNORM",
"DXGI_FORMAT_R8_SINT",
"DXGI_FORMAT_A8_UNORM",
"DXGI_FORMAT_R1_UNORM",
"DXGI_FORMAT_R9G9B9E5_SHAREDEXP",
"DXGI_FORMAT_R8G8_B8G8_UNORM",
"DXGI_FORMAT_G8R8_G8B8_UNORM",
"DXGI_FORMAT_BC1_TYPELESS",
"DXGI_FORMAT_BC1_UNORM",
"DXGI_FORMAT_BC1_UNORM_SRGB",
"DXGI_FORMAT_BC2_TYPELESS",
"DXGI_FORMAT_BC2_UNORM",
"DXGI_FORMAT_BC2_UNORM_SRGB",
"DXGI_FORMAT_BC3_TYPELESS",
"DXGI_FORMAT_BC3_UNORM",
"DXGI_FORMAT_BC3_UNORM_SRGB",
"DXGI_FORMAT_BC4_TYPELESS",
"DXGI_FORMAT_BC4_UNORM",
"DXGI_FORMAT_BC4_SNORM",
"DXGI_FORMAT_BC5_TYPELESS",
"DXGI_FORMAT_BC5_UNORM",
"DXGI_FORMAT_BC5_SNORM",
"DXGI_FORMAT_B5G6R5_UNORM",
"DXGI_FORMAT_B5G5R5A1_UNORM",
"DXGI_FORMAT_B8G8R8A8_UNORM",
"DXGI_FORMAT_B8G8R8X8_UNORM",
"DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM",
"DXGI_FORMAT_B8G8R8A8_TYPELESS",
"DXGI_FORMAT_B8G8R8A8_UNORM_SRGB",
"DXGI_FORMAT_B8G8R8X8_TYPELESS",
"DXGI_FORMAT_B8G8R8X8_UNORM_SRGB",
"DXGI_FORMAT_BC6H_TYPELESS",
"DXGI_FORMAT_BC6H_UF16",
"DXGI_FORMAT_BC6H_SF16",
"DXGI_FORMAT_BC7_TYPELESS",
"DXGI_FORMAT_BC7_UNORM",
"DXGI_FORMAT_BC7_UNORM_SRGB",
"DXGI_FORMAT_AYUV",
"DXGI_FORMAT_Y410",
"DXGI_FORMAT_Y416",
"DXGI_FORMAT_NV12",
"DXGI_FORMAT_P010",
"DXGI_FORMAT_P016",
"DXGI_FORMAT_420_OPAQUE",
"DXGI_FORMAT_YUY2",
"DXGI_FORMAT_Y210",
"DXGI_FORMAT_Y216",
"DXGI_FORMAT_NV11",
"DXGI_FORMAT_AI44",
"DXGI_FORMAT_IA44",
"DXGI_FORMAT_P8",
"DXGI_FORMAT_A8P8",
"DXGI_FORMAT_B4G4R4A4_UNORM",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
"DXGI_FORMAT_P208",
"DXGI_FORMAT_V208",
"DXGI_FORMAT_V408"
};
}
namespace platf {
std::shared_ptr<display_t> display(dev_type_e hwdevice_type) {
if(hwdevice_type == dev_type_e::dxgi) {
auto disp = std::make_shared<dxgi::display_vram_t>();
if(!disp->init()) {
return disp;
}
}
else if(hwdevice_type == dev_type_e::none) {
auto disp = std::make_shared<dxgi::display_ram_t>();
if(!disp->init()) {
return disp;
}
}
return nullptr;
}
}

View File

@@ -0,0 +1,297 @@
#include "sunshine/main.h"
#include "display.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::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<platf::img_t> display_ram_t::alloc_img() {
auto img = std::make_shared<img_t>();
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() {
if(display_base_t::init()) {
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;
}
}

View File

@@ -0,0 +1,867 @@
#include <codecvt>
#include <d3dcompiler.h>
#include <directxmath.h>
#include "sunshine/main.h"
#include "display.h"
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders"
namespace platf {
using namespace std::literals;
}
namespace platf::dxgi {
constexpr float aquamarine[] { 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f };
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
using buf_t = util::safe_ptr<ID3D11Buffer, Release<ID3D11Buffer>>;
using blend_t = util::safe_ptr<ID3D11BlendState, Release<ID3D11BlendState>>;
using raster_state_t = util::safe_ptr<ID3D11RasterizerState, Release<ID3D11RasterizerState>>;
using sampler_state_t = util::safe_ptr<ID3D11SamplerState, Release<ID3D11SamplerState>>;
using vs_t = util::safe_ptr<ID3D11VertexShader, Release<ID3D11VertexShader>>;
using ps_t = util::safe_ptr<ID3D11PixelShader, Release<ID3D11PixelShader>>;
using blob_t = util::safe_ptr<ID3DBlob, Release<ID3DBlob>>;
using depth_stencil_state_t = util::safe_ptr<ID3D11DepthStencilState, Release<ID3D11DepthStencilState>>;
using depth_stencil_view_t = util::safe_ptr<ID3D11DepthStencilView, Release<ID3D11DepthStencilView>>;
using float4 = DirectX::XMFLOAT4;
using float3 = DirectX::XMFLOAT3;
using float2 = DirectX::XMFLOAT2;
struct __attribute__ ((__aligned__ (16))) color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
float2 range_y;
float2 range_uv;
};
color_t make_color_matrix(float Cr, float Cb, float U_max, float V_max, float add_Y, float add_UV, float2 range_Y, float2 range_UV) {
float Cg = 1.0f - Cr - Cb;
float Cr_i = 1.0f - Cr;
float Cb_i = 1.0f - Cb;
float shift_y = range_Y.x / 256.0f;
float shift_uv = range_UV.x / 256.0f;
float scale_y = (range_Y.y - range_Y.x) / 256.0f;
float scale_uv = (range_UV.y - range_UV.x) / 256.0f;
return {
{ Cr, Cg, Cb, add_Y },
{ -(Cr * U_max / Cb_i), -(Cg * U_max / Cb_i), U_max, add_UV },
{ V_max, -(Cg * V_max / Cr_i), -(Cb * V_max / Cr_i), add_UV },
{ scale_y, shift_y },
{ scale_uv, shift_uv },
};
}
color_t colors[] {
make_color_matrix(0.299f, 0.114f, 0.436f, 0.615f, 0.0625, 0.5f, { 16.0f, 235.0f }, { 16.0f, 240.0f }), // BT601 MPEG
make_color_matrix(0.299f, 0.114f, 0.5f, 0.5f, 0.0f, 0.5f, { 0.0f, 255.0f }, { 0.0f, 255.0f }), // BT601 JPEG
make_color_matrix(0.2126f, 0.0722f, 0.436f, 0.615f, 0.0625, 0.5f, { 16.0f, 235.0f }, { 16.0f, 240.0f }), //BT701 MPEG
make_color_matrix(0.2126f, 0.0722f, 0.5f, 0.5f, 0.0f, 0.5f, { 0.0f, 255.0f }, { 0.0f, 255.0f }), //BT701 JPEG
};
template<class T>
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 {
shader_res_t input_res;
texture2d_t texture;
std::shared_ptr<platf::display_t> display;
~img_d3d_t() override = default;
};
util::buffer_t<std::uint8_t> make_cursor_image(util::buffer_t<std::uint8_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<std::uint8_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<std::codecvt_utf8_utf16<wchar_t>, 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");
}
class hwdevice_t : public platf::hwdevice_t {
public:
hwdevice_t(std::vector<hwdevice_t*> *hwdevices_p) : hwdevices_p { hwdevices_p } {}
hwdevice_t() = delete;
void set_cursor_pos(LONG rel_x, LONG rel_y, bool visible) {
cursor_visible = visible;
if(!visible) {
return;
}
auto x = ((float)rel_x);
auto y = ((float)rel_y);
cursor_view.TopLeftX = x;
cursor_view.TopLeftY = y;
}
int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) {
auto device = (device_t::pointer)data;
cursor_view.Width = width;
cursor_view.Height = height;
D3D11_SHADER_RESOURCE_VIEW_DESC desc {
DXGI_FORMAT_B8G8R8A8_UNORM,
D3D11_SRV_DIMENSION_TEXTURE2D
};
desc.Texture2D.MipLevels = 1;
auto status = device->CreateShaderResourceView(texture, &desc, &img.input_res);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
return 0;
}
int convert(platf::img_t &img_base) override {
auto &img = (img_d3d_t&)img_base;
if(!img.input_res) {
auto device = (device_t::pointer)data;
D3D11_SHADER_RESOURCE_VIEW_DESC desc {
DXGI_FORMAT_B8G8R8A8_UNORM,
D3D11_SRV_DIMENSION_TEXTURE2D
};
desc.Texture2D.MipLevels = 1;
auto status = device->CreateShaderResourceView(img.texture.get(), &desc, &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;
}
}
auto input_res_p = img.input_res.get();
if(cursor_visible) {
_init_view_port(img.width, img.height);
device_ctx_p->OMSetRenderTargets(1, &scene_rt, nullptr);
device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0);
device_ctx_p->PSSetShader(scene_ps.get(), nullptr, 0);
device_ctx_p->PSSetShaderResources(0, 1, &input_res_p);
device_ctx_p->Draw(3, 0);
device_ctx_p->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu);
device_ctx_p->RSSetViewports(1, &cursor_view);
device_ctx_p->PSSetShaderResources(0, 1, &this->img.input_res);
device_ctx_p->Draw(3, 0);
device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
input_res_p = scene_sr.get();
}
_init_view_port(out_width, out_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, &input_res_p);
device_ctx_p->Draw(3, 0);
_init_view_port(out_width / 2, out_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, &input_res_p);
device_ctx_p->Draw(3, 0);
return 0;
}
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override {
switch (colorspace) {
case 5: // SWS_CS_SMPTE170M
color_p = &colors[0];
break;
case 1: // SWS_CS_ITU709
color_p = &colors[2];
break;
case 9: // SWS_CS_BT2020
default:
BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv;
color_p = &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 init(
std::shared_ptr<platf::display_t> display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p,
int in_width, int in_height, int out_width, int out_height,
pix_fmt_e pix_fmt
) {
HRESULT status;
device_p->AddRef();
data = device_p;
this->device_ctx_p = device_ctx_p;
cursor_visible = false;
cursor_view.MinDepth = 0.0f;
cursor_view.MaxDepth = 1.0f;
platf::hwdevice_t::img = &img;
this->out_width = out_width;
this->out_height = out_height;
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;
}
blend_disable = make_blend(device_p, false);
blend_enable = make_blend(device_p, true);
if(!blend_disable || !blend_enable) {
return -1;
}
if(_init_rt(scene_sr, scene_rt, in_width, in_height, DXGI_FORMAT_B8G8R8A8_UNORM)) {
return -1;
}
color_matrix = make_buffer(device_p, colors[0]);
if(!color_matrix) {
BOOST_LOG(error) << "Failed to create color matrix buffer"sv;
return -1;
}
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_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);
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 = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010;
t.BindFlags = D3D11_BIND_RENDER_TARGET;
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.display = std::move(display);
img.width = out_width;
img.height = out_height;
img.data = (std::uint8_t*)img.texture.get();
img.row_pitch = out_width;
img.pixel_pitch = 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;
}
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;
status = device_p->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;
}
device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
device_ctx_p->PSSetSamplers(0, 1, &sampler_linear);
device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix);
device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene);
device_ctx_p->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
device_ctx_p->IASetInputLayout(input_layout.get());
return 0;
}
~hwdevice_t() override {
if(data) {
((ID3D11Device*)data)->Release();
}
auto it = std::find(std::begin(*hwdevices_p), std::end(*hwdevices_p), this);
if(it != std::end(*hwdevices_p)) {
hwdevices_p->erase(it);
}
}
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);
}
int _init_rt(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;
auto device = (device_t::pointer)data;
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;
}
D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc {
format,
D3D11_SRV_DIMENSION_TEXTURE2D
};
shader_resource_desc.Texture2D.MipLevels = 1;
device->CreateShaderResourceView(tex.get(), &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
};
device->CreateRenderTargetView(tex.get(), &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;
}
public:
color_t *color_p;
blend_t blend_enable;
blend_t blend_disable;
buf_t info_scene;
buf_t color_matrix;
sampler_state_t sampler_linear;
input_layout_t input_layout;
render_target_t nv12_Y_rt;
render_target_t nv12_UV_rt;
render_target_t scene_rt;
shader_res_t scene_sr;
img_d3d_t 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 cursor_view;
bool cursor_visible;
float out_width, out_height;
device_ctx_t::pointer device_ctx_p;
// The destructor will remove itself from the list of hardware devices, this is done synchronously
std::vector<hwdevice_t*> *hwdevices_p;
};
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<std::uint8_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;
}
for(auto *hwdevice : hwdevices) {
if(hwdevice->set_cursor_texture(texture.get(), t.Width, t.Height)) {
return capture_e::error;
}
}
cursor.texture = std::move(texture);
cursor.width = t.Width;
cursor.height = t.Height;
}
if(frame_info.LastMouseUpdateTime.QuadPart) {
for(auto *hwdevice : hwdevices) {
hwdevice->set_cursor_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible);
}
}
if(frame_update_flag) {
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;
}
device_ctx->CopyResource(img->texture.get(), src.get());
}
return capture_e::ok;
}
std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
auto img = std::make_shared<img_d3d_t>();
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;
auto status = device->CreateTexture2D(&t, nullptr, &img->texture);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']';
return nullptr;
}
img->data = (std::uint8_t*)img->texture.get();
img->row_pitch = 0;
img->pixel_pitch = 4;
img->width = 0;
img->height = 0;
img->display = shared_from_this();
return img;
}
int display_vram_t::dummy_img(platf::img_t *img_base) {
auto img = (img_d3d_t*)img_base;
img->row_pitch = width * 4;
auto dummy_data = std::make_unique<int[]>(width * height);
D3D11_SUBRESOURCE_DATA data {
dummy_data.get(),
(UINT)img->row_pitch
};
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();
img->height = height;
img->width = width;
img->pixel_pitch = 4;
return 0;
}
std::shared_ptr<platf::hwdevice_t> display_vram_t::make_hwdevice(int width, int height, 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<hwdevice_t>(&hwdevices);
auto ret = hwdevice->init(
shared_from_this(),
device.get(),
device_ctx.get(),
this->width, this->height,
width, height,
pix_fmt);
if(ret) {
return nullptr;
}
if(cursor.texture && hwdevice->set_cursor_texture(cursor.texture.get(), cursor.width, cursor.height)) {
return nullptr;
}
hwdevices.emplace_back(hwdevice.get());
return hwdevice;
}
int init() {
for(auto &color : colors) {
BOOST_LOG(debug) << "Color Matrix"sv;
BOOST_LOG(debug) << "Y ["sv << color.color_vec_y.x << ", "sv << color.color_vec_y.y << ", "sv << color.color_vec_y.z << ", "sv << color.color_vec_y.w << ']';
BOOST_LOG(debug) << "U ["sv << color.color_vec_u.x << ", "sv << color.color_vec_u.y << ", "sv << color.color_vec_u.z << ", "sv << color.color_vec_u.w << ']';
BOOST_LOG(debug) << "V ["sv << color.color_vec_v.x << ", "sv << color.color_vec_v.y << ", "sv << color.color_vec_v.z << ", "sv << color.color_vec_v.w << ']';
BOOST_LOG(debug) << "range Y ["sv << color.range_y.x << ", "sv << color.range_y.y << ']';
BOOST_LOG(debug) << "range UV ["sv << color.range_uv.x << ", "sv << color.range_uv.y << ']';
}
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;
}
}

View File

@@ -1,5 +1,6 @@
#include <sstream>
#include <iomanip>
#include <cmath>
#include <ws2tcpip.h>
#include <winsock2.h>
@@ -10,13 +11,21 @@
#include <ViGEm/Client.h>
#include "sunshine/main.h"
#include "common.h"
#include "sunshine/platform/common.h"
namespace platf {
using namespace std::literals;
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
volatile HDESK _lastKnownInputDesktop = NULL;
constexpr touch_port_t target_touch_port {
0, 0,
65535, 65535
};
HDESK pairInputDesktop();
class vigem_t {
public:
using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>;
@@ -162,6 +171,40 @@ input_t input() {
return result;
}
void send_input(INPUT &i) {
retry:
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
auto hDesk = pairInputDesktop();
if (_lastKnownInputDesktop != hDesk) {
_lastKnownInputDesktop = hDesk;
goto retry;
}
BOOST_LOG(warning) << "Couldn't send input"sv;
}
}
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
INPUT i {};
i.type = INPUT_MOUSE;
auto &mi = i.mi;
mi.dwFlags =
MOUSEEVENTF_MOVE |
MOUSEEVENTF_ABSOLUTE |
// MOUSEEVENTF_VIRTUALDESK maps to the entirety of the desktop rather than the primary desktop
MOUSEEVENTF_VIRTUALDESK;
auto scaled_x = std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
auto scaled_y = std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
mi.dx = scaled_x;
mi.dy = scaled_y;
send_input(i);
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
INPUT i {};
@@ -171,15 +214,12 @@ void move_mouse(input_t &input, int deltaX, int deltaY) {
mi.dwFlags = MOUSEEVENTF_MOVE;
mi.dx = deltaX;
mi.dy = deltaY;
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send mouse movement input"sv;
}
send_input(i);
}
void button_mouse(input_t &input, int button, bool release) {
constexpr SHORT KEY_STATE_DOWN = 0x8000;
constexpr auto KEY_STATE_DOWN = (SHORT)0x8000;
INPUT i {};
@@ -218,10 +258,7 @@ void button_mouse(input_t &input, int button, bool release) {
return;
}
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send mouse button input"sv;
}
send_input(i);
}
void scroll(input_t &input, int distance) {
@@ -233,27 +270,14 @@ void scroll(input_t &input, int distance) {
mi.dwFlags = MOUSEEVENTF_WHEEL;
mi.mouseData = distance;
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send moue movement input"sv;
}
send_input(i);
}
void keyboard(input_t &input, uint16_t modcode, bool release) {
constexpr SHORT KEY_STATE_DOWN = 0x8000;
if(modcode == VK_RMENU) {
modcode = VK_LWIN;
}
auto key_state = GetAsyncKeyState(modcode);
bool key_state_down = (key_state & KEY_STATE_DOWN) != 0;
if(key_state_down != release) {
BOOST_LOG(warning) << "Key state of vkey ["sv << util::hex(modcode).to_string_view() << "] does not match the desired state ["sv << (release ? "on]"sv : "off]"sv);
return;
}
INPUT i {};
i.type = INPUT_KEYBOARD;
auto &ki = i.ki;
@@ -292,10 +316,7 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
ki.dwFlags |= KEYEVENTF_KEYUP;
}
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
BOOST_LOG(warning) << "Couldn't send moue movement input"sv;
}
send_input(i);
}
int alloc_gamepad(input_t &input, int nr) {
@@ -333,6 +354,28 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
}
}
int thread_priority() {
return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1;
}
HDESK pairInputDesktop() {
auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);
if (NULL == hDesk) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']';
}
else {
BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']';
if (!SetThreadDesktop(hDesk) ) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']';
}
CloseDesktop(hDesk);
}
return hDesk;
}
void freeInput(void *p) {
auto vigem = (vigem_t*)p;

View File

@@ -1,741 +0,0 @@
//
// Created by loki on 1/12/20.
//
#include <dxgi.h>
#include <d3d11.h>
#include <d3dcommon.h>
#include <dxgi1_2.h>
#include <codecvt>
#include <sunshine/config.h>
#include "sunshine/main.h"
#include "common.h"
namespace platf {
using namespace std::literals;
}
namespace platf::dxgi {
template<class T>
void Release(T *dxgi) {
dxgi->Release();
}
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11DeviceContext>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
extern const char *format_str[];
class duplication_t {
public:
dup_t dup;
bool has_frame {};
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, resource_t::pointer *res_p) {
auto capture_status = release_frame();
if(capture_status != capture_e::ok) {
return capture_status;
}
auto status = dup->AcquireNextFrame(1000, &frame_info, res_p);
switch(status) {
case S_OK:
has_frame = true;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
}
}
capture_e reset(dup_t::pointer dup_p = dup_t::pointer()) {
auto capture_status = release_frame();
dup.reset(dup_p);
return capture_status;
}
capture_e release_frame() {
if(!has_frame) {
return capture_e::ok;
}
auto status = dup->ReleaseFrame();
switch (status) {
case S_OK:
has_frame = false;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
has_frame = false;
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
}
}
~duplication_t() {
release_frame();
}
};
class display_t;
struct img_t : public ::platf::img_t {
~img_t() override {
delete[] data;
data = nullptr;
}
};
struct cursor_t {
std::vector<std::uint8_t> img_data;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
int x, y;
bool visible;
};
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 blend_cursor_color(const cursor_t &cursor, img_t &img) {
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) {
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;
}
++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);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
blend_cursor_monochrome(cursor, img);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
default:
BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']';
}
}
class display_t : public ::platf::display_t {
public:
capture_e snapshot(::platf::img_t *img_base, bool cursor_visible) override {
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, &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::pointer src_p {};
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p);
texture2d_t src{src_p};
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(current_img.pData) {
device_ctx->Unmap(texture.get(), 0);
current_img.pData = nullptr;
}
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &current_img);
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
}
const bool update_flag =
frame_info.LastMouseUpdateTime.QuadPart ||
frame_info.LastPresentTime.QuadPart != 0 ||
frame_info.PointerShapeBufferSize > 0;
if(!update_flag) {
return capture_e::timeout;
}
if(img->width != width || img->height != height) {
delete[] img->data;
img->data = new std::uint8_t[height * current_img.RowPitch];
img->width = width;
img->height = height;
img->row_pitch = current_img.RowPitch;
}
std::copy_n((std::uint8_t*)current_img.pData, height * current_img.RowPitch, (std::uint8_t*)img->data);
if(cursor_visible && cursor.visible) {
blend_cursor(cursor, *img);
}
return capture_e::ok;
}
std::unique_ptr<::platf::img_t> alloc_img() override {
auto img = std::make_unique<img_t>();
img->data = nullptr;
img->height = 0;
img->width = 0;
img->row_pitch = 0;
img->pixel_pitch = 4;
return img;
}
int init() {
/* Uncomment when use of IDXGIOutput5 is implemented
std::call_once(windows_cpp_once_flag, []() {
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4);
typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value);
auto user32 = LoadLibraryA("user32.dll");
auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext");
if(f) {
f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
FreeLibrary(user32);
});
*/
current_img.pData = nullptr; // current_img is not yet mapped
dxgi::factory1_t::pointer factory_p {};
dxgi::adapter_t::pointer adapter_p {};
dxgi::output_t::pointer output_p {};
dxgi::device_t::pointer device_p {};
dxgi::device_ctx_t::pointer device_ctx_p {};
HRESULT status;
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p);
factory.reset(factory_p);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
auto adapter_name = converter.from_bytes(config::video.adapter_name);
auto output_name = converter.from_bytes(config::video.output_name);
for(int x = 0; factory_p->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
dxgi::adapter_t adapter_tmp { adapter_p };
DXGI_ADAPTER_DESC1 adapter_desc;
adapter_tmp->GetDesc1(&adapter_desc);
if(!adapter_name.empty() && adapter_desc.Description != adapter_name) {
continue;
}
for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
dxgi::output_t output_tmp {output_p };
DXGI_OUTPUT_DESC desc;
output_tmp->GetDesc(&desc);
if(!output_name.empty() && desc.DeviceName != output_name) {
continue;
}
if(desc.AttachedToDesktop) {
output = std::move(output_tmp);
width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
}
}
if(output) {
adapter = std::move(adapter_tmp);
break;
}
}
if(!output) {
BOOST_LOG(error) << "Failed to locate an output device"sv;
return -1;
}
D3D_FEATURE_LEVEL featureLevels[] {
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv;
return -1;
}
status = D3D11CreateDevice(
adapter_p,
D3D_DRIVER_TYPE_UNKNOWN,
nullptr,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION,
&device_p,
&feature_level,
&device_ctx_p);
adapter_p->Release();
device.reset(device_p);
device_ctx.reset(device_ctx_p);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
DXGI_ADAPTER_DESC adapter_desc;
adapter->GetDesc(&adapter_desc);
auto description = converter.to_bytes(adapter_desc.Description);
BOOST_LOG(info) << std::endl
<< "Device Description : " << description << std::endl
<< "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl
<< "Capture size : "sv << width << 'x' << height;
// Bump up thread priority
{
dxgi::dxgi_t::pointer dxgi_p {};
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p);
dxgi::dxgi_t dxgi { dxgi_p };
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
dxgi->SetGPUThreadPriority(7);
}
// Try to reduce latency
{
dxgi::dxgi1_t::pointer dxgi_p {};
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p);
dxgi::dxgi1_t dxgi { dxgi_p };
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
dxgi->SetMaximumFrameLatency(1);
}
//FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD
//TODO: Use IDXGIOutput5 for improved performance
{
dxgi::output1_t::pointer output1_p {};
status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1_p);
dxgi::output1_t output1 {output1_p };
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
return -1;
}
// We try this twice, in case we still get an error on reinitialization
for(int x = 0; x < 2; ++x) {
dxgi::dup_t::pointer dup_p {};
status = output1->DuplicateOutput((IUnknown*)device.get(), &dup_p);
if(SUCCEEDED(status)) {
dup.reset(dup_p);
break;
}
std::this_thread::sleep_for(200ms);
}
if(FAILED(status)) {
BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
DXGI_OUTDUPL_DESC dup_desc;
dup.dup->GetDesc(&dup_desc);
format = dup_desc.ModeDesc.Format;
BOOST_LOG(debug) << "Source format ["sv << format_str[dup_desc.ModeDesc.Format] << ']';
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;
dxgi::texture2d_t::pointer tex_p {};
status = device->CreateTexture2D(&t, nullptr, &tex_p);
texture.reset(tex_p);
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, &current_img);
if(FAILED(status)) {
BOOST_LOG(error) << "Error: Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
return 0;
}
~display_t() override {
if(current_img.pData) {
device_ctx->Unmap(texture.get(), 0);
current_img.pData = nullptr;
}
}
factory1_t factory;
adapter_t adapter;
output_t output;
device_t device;
device_ctx_t device_ctx;
duplication_t dup;
cursor_t cursor;
texture2d_t texture;
int width, height;
DXGI_FORMAT format;
D3D_FEATURE_LEVEL feature_level;
D3D11_MAPPED_SUBRESOURCE current_img;
};
const char *format_str[] = {
"DXGI_FORMAT_UNKNOWN",
"DXGI_FORMAT_R32G32B32A32_TYPELESS",
"DXGI_FORMAT_R32G32B32A32_FLOAT",
"DXGI_FORMAT_R32G32B32A32_UINT",
"DXGI_FORMAT_R32G32B32A32_SINT",
"DXGI_FORMAT_R32G32B32_TYPELESS",
"DXGI_FORMAT_R32G32B32_FLOAT",
"DXGI_FORMAT_R32G32B32_UINT",
"DXGI_FORMAT_R32G32B32_SINT",
"DXGI_FORMAT_R16G16B16A16_TYPELESS",
"DXGI_FORMAT_R16G16B16A16_FLOAT",
"DXGI_FORMAT_R16G16B16A16_UNORM",
"DXGI_FORMAT_R16G16B16A16_UINT",
"DXGI_FORMAT_R16G16B16A16_SNORM",
"DXGI_FORMAT_R16G16B16A16_SINT",
"DXGI_FORMAT_R32G32_TYPELESS",
"DXGI_FORMAT_R32G32_FLOAT",
"DXGI_FORMAT_R32G32_UINT",
"DXGI_FORMAT_R32G32_SINT",
"DXGI_FORMAT_R32G8X24_TYPELESS",
"DXGI_FORMAT_D32_FLOAT_S8X24_UINT",
"DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS",
"DXGI_FORMAT_X32_TYPELESS_G8X24_UINT",
"DXGI_FORMAT_R10G10B10A2_TYPELESS",
"DXGI_FORMAT_R10G10B10A2_UNORM",
"DXGI_FORMAT_R10G10B10A2_UINT",
"DXGI_FORMAT_R11G11B10_FLOAT",
"DXGI_FORMAT_R8G8B8A8_TYPELESS",
"DXGI_FORMAT_R8G8B8A8_UNORM",
"DXGI_FORMAT_R8G8B8A8_UNORM_SRGB",
"DXGI_FORMAT_R8G8B8A8_UINT",
"DXGI_FORMAT_R8G8B8A8_SNORM",
"DXGI_FORMAT_R8G8B8A8_SINT",
"DXGI_FORMAT_R16G16_TYPELESS",
"DXGI_FORMAT_R16G16_FLOAT",
"DXGI_FORMAT_R16G16_UNORM",
"DXGI_FORMAT_R16G16_UINT",
"DXGI_FORMAT_R16G16_SNORM",
"DXGI_FORMAT_R16G16_SINT",
"DXGI_FORMAT_R32_TYPELESS",
"DXGI_FORMAT_D32_FLOAT",
"DXGI_FORMAT_R32_FLOAT",
"DXGI_FORMAT_R32_UINT",
"DXGI_FORMAT_R32_SINT",
"DXGI_FORMAT_R24G8_TYPELESS",
"DXGI_FORMAT_D24_UNORM_S8_UINT",
"DXGI_FORMAT_R24_UNORM_X8_TYPELESS",
"DXGI_FORMAT_X24_TYPELESS_G8_UINT",
"DXGI_FORMAT_R8G8_TYPELESS",
"DXGI_FORMAT_R8G8_UNORM",
"DXGI_FORMAT_R8G8_UINT",
"DXGI_FORMAT_R8G8_SNORM",
"DXGI_FORMAT_R8G8_SINT",
"DXGI_FORMAT_R16_TYPELESS",
"DXGI_FORMAT_R16_FLOAT",
"DXGI_FORMAT_D16_UNORM",
"DXGI_FORMAT_R16_UNORM",
"DXGI_FORMAT_R16_UINT",
"DXGI_FORMAT_R16_SNORM",
"DXGI_FORMAT_R16_SINT",
"DXGI_FORMAT_R8_TYPELESS",
"DXGI_FORMAT_R8_UNORM",
"DXGI_FORMAT_R8_UINT",
"DXGI_FORMAT_R8_SNORM",
"DXGI_FORMAT_R8_SINT",
"DXGI_FORMAT_A8_UNORM",
"DXGI_FORMAT_R1_UNORM",
"DXGI_FORMAT_R9G9B9E5_SHAREDEXP",
"DXGI_FORMAT_R8G8_B8G8_UNORM",
"DXGI_FORMAT_G8R8_G8B8_UNORM",
"DXGI_FORMAT_BC1_TYPELESS",
"DXGI_FORMAT_BC1_UNORM",
"DXGI_FORMAT_BC1_UNORM_SRGB",
"DXGI_FORMAT_BC2_TYPELESS",
"DXGI_FORMAT_BC2_UNORM",
"DXGI_FORMAT_BC2_UNORM_SRGB",
"DXGI_FORMAT_BC3_TYPELESS",
"DXGI_FORMAT_BC3_UNORM",
"DXGI_FORMAT_BC3_UNORM_SRGB",
"DXGI_FORMAT_BC4_TYPELESS",
"DXGI_FORMAT_BC4_UNORM",
"DXGI_FORMAT_BC4_SNORM",
"DXGI_FORMAT_BC5_TYPELESS",
"DXGI_FORMAT_BC5_UNORM",
"DXGI_FORMAT_BC5_SNORM",
"DXGI_FORMAT_B5G6R5_UNORM",
"DXGI_FORMAT_B5G5R5A1_UNORM",
"DXGI_FORMAT_B8G8R8A8_UNORM",
"DXGI_FORMAT_B8G8R8X8_UNORM",
"DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM",
"DXGI_FORMAT_B8G8R8A8_TYPELESS",
"DXGI_FORMAT_B8G8R8A8_UNORM_SRGB",
"DXGI_FORMAT_B8G8R8X8_TYPELESS",
"DXGI_FORMAT_B8G8R8X8_UNORM_SRGB",
"DXGI_FORMAT_BC6H_TYPELESS",
"DXGI_FORMAT_BC6H_UF16",
"DXGI_FORMAT_BC6H_SF16",
"DXGI_FORMAT_BC7_TYPELESS",
"DXGI_FORMAT_BC7_UNORM",
"DXGI_FORMAT_BC7_UNORM_SRGB",
"DXGI_FORMAT_AYUV",
"DXGI_FORMAT_Y410",
"DXGI_FORMAT_Y416",
"DXGI_FORMAT_NV12",
"DXGI_FORMAT_P010",
"DXGI_FORMAT_P016",
"DXGI_FORMAT_420_OPAQUE",
"DXGI_FORMAT_YUY2",
"DXGI_FORMAT_Y210",
"DXGI_FORMAT_Y216",
"DXGI_FORMAT_NV11",
"DXGI_FORMAT_AI44",
"DXGI_FORMAT_IA44",
"DXGI_FORMAT_P8",
"DXGI_FORMAT_A8P8",
"DXGI_FORMAT_B4G4R4A4_UNORM",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
"DXGI_FORMAT_P208",
"DXGI_FORMAT_V208",
"DXGI_FORMAT_V408"
};
}
namespace platf {
std::unique_ptr<display_t> display() {
auto disp = std::make_unique<dxgi::display_t>();
if (disp->init()) {
return nullptr;
}
return disp;
}
}

View File

@@ -80,17 +80,31 @@ int proc_t::execute(int app_id) {
auto ret = exe(cmd, _env, _pipe, ec);
if(ec) {
BOOST_LOG(error) << "System: "sv << ec.message();
BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message();
return -1;
}
if(ret != 0) {
BOOST_LOG(error) << "Return code ["sv << ret << ']';
BOOST_LOG(error) << '[' << cmd << "] failed with code ["sv << ret << ']';
return -1;
}
}
BOOST_LOG(debug) << "Starting ["sv << proc.cmd << ']';
for(auto &cmd : proc.detached) {
BOOST_LOG(info) << "Spawning ["sv << cmd << ']';
if(proc.output.empty()) {
bp::spawn(cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
else {
bp::spawn(cmd, _env, bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
}
if(ec) {
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
}
}
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
if(proc.cmd.empty()) {
placebo = true;
}
@@ -102,7 +116,7 @@ int proc_t::execute(int app_id) {
}
if(ec) {
BOOST_LOG(info) << "System: "sv << ec.message();
BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message();
return -1;
}
@@ -242,26 +256,45 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
auto this_env = boost::this_process::environment();
for(auto &[name,val] : env_vars) {
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
}
std::vector<proc::ctx_t> apps;
for(auto &[_,app_node] : apps_node) {
proc::ctx_t ctx;
auto &prep_nodes = app_node.get_child("prep-cmd"s);
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
auto detached_nodes_opt = app_node.get_child_optional("detached"s);
auto output = app_node.get_optional<std::string>("output"s);
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
auto cmd = app_node.get_optional<std::string>("cmd"s);
std::vector<proc::cmd_t> prep_cmds;
prep_cmds.reserve(prep_nodes.size());
for(auto &[_, prep_node] : prep_nodes) {
auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s));
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
if(prep_nodes_opt) {
auto &prep_nodes = *prep_nodes_opt;
if(undo_cmd) {
prep_cmds.emplace_back(std::move(do_cmd), parse_env_val(this_env, *undo_cmd));
prep_cmds.reserve(prep_nodes.size());
for(auto &[_, prep_node] : prep_nodes) {
auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s));
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
if(undo_cmd) {
prep_cmds.emplace_back(std::move(do_cmd), parse_env_val(this_env, *undo_cmd));
}
else {
prep_cmds.emplace_back(std::move(do_cmd));
}
}
else {
prep_cmds.emplace_back(std::move(do_cmd));
}
std::vector<std::string> detached;
if(detached_nodes_opt) {
auto &detached_nodes = *detached_nodes_opt;
detached.reserve(detached_nodes.size());
for(auto &[_, detached_val] : detached_nodes) {
detached.emplace_back(parse_env_val(this_env, detached_val.get_value<std::string>()));
}
}
@@ -275,15 +308,11 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
ctx.name = std::move(name);
ctx.prep_cmds = std::move(prep_cmds);
ctx.detached = std::move(detached);
apps.emplace_back(std::move(ctx));
}
bp::environment env = boost::this_process::environment();
for(auto &[name,val] : env_vars) {
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
}
return proc::proc_t {
std::move(this_env), std::move(apps)
};

View File

@@ -30,6 +30,7 @@ struct cmd_t {
};
/*
* pre_cmds -- guaranteed to be executed unless any of the commands fail.
* detached -- commands detached from Sunshine
* cmd -- Runs indefinitely until:
* No session is running and a different set of commands it to be executed
* Command exits
@@ -41,6 +42,14 @@ struct cmd_t {
struct ctx_t {
std::vector<cmd_t> prep_cmds;
/**
* Some applications, such as Steam,
* either exit quickly, or keep running indefinitely.
* Steam.exe is one such application.
* That is why some applications need be run and forgotten about
*/
std::vector<std::string> detached;
std::string name;
std::string cmd;
std::string output;

149
sunshine/round_robin.h Normal file
View File

@@ -0,0 +1,149 @@
#ifndef KITTY_UTIL_ITERATOR_H
#define KITTY_UTIL_ITERATOR_H
#include <iterator>
namespace util {
template<class V, class T>
class it_wrap_t : public std::iterator<std::random_access_iterator_tag, V> {
public:
typedef T iterator;
typedef typename std::iterator<std::random_access_iterator_tag, V>::value_type class_t;
typedef class_t& reference;
typedef class_t* pointer;
typedef std::ptrdiff_t diff_t;
iterator operator += (diff_t step) {
while(step-- > 0) {
++_this();
}
return _this();
}
iterator operator -= (diff_t step) {
while(step-- > 0) {
--_this();
}
return _this();
}
iterator operator +(diff_t step) {
iterator new_ = _this();
return new_ += step;
}
iterator operator -(diff_t step) {
iterator new_ = _this();
return new_ -= step;
}
diff_t operator -(iterator first) {
diff_t step = 0;
while(first != _this()) {
++step;
++first;
}
return step;
}
iterator operator++() { _this().inc(); return _this(); }
iterator operator--() { _this().dec(); return _this(); }
iterator operator++(int) {
iterator new_ = _this();
++_this();
return new_;
}
iterator operator--(int) {
iterator new_ = _this();
--_this();
return new_;
}
reference operator*() { return *_this().get(); }
const reference operator*() const { return *_this().get(); }
pointer operator->() { return &*_this(); }
const pointer operator->() const { return &*_this(); }
bool operator != (const iterator &other) const {
return !(_this() == other);
}
bool operator < (const iterator &other) const {
return !(_this() >= other);
}
bool operator >= (const iterator &other) const {
return _this() == other || _this() > other;
}
bool operator <= (const iterator &other) const {
return _this() == other || _this() < other;
}
bool operator == (const iterator &other) const { return _this().eq(other); };
bool operator > (const iterator &other) const { return _this().gt(other); }
private:
iterator &_this() { return *static_cast<iterator*>(this); }
const iterator &_this() const { return *static_cast<const iterator*>(this); }
};
template<class V, class It>
class round_robin_t : public it_wrap_t<V, round_robin_t<V, It>> {
public:
using iterator = It;
using pointer = V*;
round_robin_t(iterator begin, iterator end) : _begin(begin), _end(end), _pos(begin) {}
void inc() {
++_pos;
if(_pos == _end) {
_pos = _begin;
}
}
void dec() {
if(_pos == _begin) {
_pos = _end;
}
--_pos;
}
bool eq(const round_robin_t &other) const {
return *_pos == *other._pos;
}
pointer get() const {
return &*_pos;
}
private:
It _begin;
It _end;
It _pos;
};
template<class V, class It>
round_robin_t<V, It> make_round_robin(It begin, It end) {
return round_robin_t<V, It>(begin, end);
}
}
#endif

View File

@@ -12,6 +12,7 @@ extern "C" {
#include "rtsp.h"
#include "input.h"
#include "stream.h"
#include "sync.h"
namespace asio = boost::asio;
@@ -54,11 +55,17 @@ public:
}
}
void bind(std::uint16_t port) {
_session_slots.resize(config::stream.channels);
_slot_count = config::stream.channels;
int bind(std::uint16_t port) {
{
auto lg = _session_slots.lock();
_session_slots->resize(config::stream.channels);
_slot_count = config::stream.channels;
}
_host = net::host_create(_addr, 1, port);
return !(bool)_host;
}
void session_raise(launch_session_t launch_session) {
@@ -97,7 +104,7 @@ public:
std::vector<char> full_payload;
auto old_msg = std::move(_queue_packet);
TUPLE_2D_REF(_, old_packet, old_msg);
auto &old_packet = old_msg.second;
std::string_view new_payload{(char *) packet->data, packet->dataLength};
std::string_view old_payload{(char *) old_packet->data, old_packet->dataLength};
@@ -139,7 +146,9 @@ public:
}
void clear(bool all = true) {
for(auto &slot : _session_slots) {
auto lg = _session_slots.lock();
for(auto &slot : *_session_slots) {
if (slot && (all || session::state(*slot) == session::state_e::STOPPING)) {
session::stop(*slot);
session::join(*slot);
@@ -157,16 +166,25 @@ public:
}
}
bool accept(const std::shared_ptr<session_t> &session) {
for(auto &slot : _session_slots) {
void clear(std::shared_ptr<session_t> *session_p) {
auto lg = _session_slots.lock();
session_p->reset();
++_slot_count;
}
std::shared_ptr<session_t> *accept(std::shared_ptr<session_t> &session) {
auto lg = _session_slots.lock();
for(auto &slot : *_session_slots) {
if(!slot) {
slot = session;
return true;
return &slot;
}
}
return false;
return nullptr;
}
net::host_t::pointer host() const {
@@ -183,7 +201,7 @@ private:
std::unordered_map<std::string_view, cmd_func_t> _map_cmd_cb;
std::vector<std::shared_ptr<session_t>> _session_slots;
util::sync_t<std::vector<std::shared_ptr<session_t>>> _session_slots;
int _slot_count;
ENetAddress _addr;
@@ -197,6 +215,9 @@ void launch_session_raise(launch_session_t launch_session) {
}
int session_count() {
// Ensure session_count is up to date
server.clear(false);
return server.session_count();
}
@@ -274,7 +295,7 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t&& req) {
option.content = const_cast<char*>(seqn_str.c_str());
std::string_view payload;
if(config::video.hevc_mode == 0) {
if(config::video.hevc_mode == 1) {
payload = "surround-params=NONE"sv;
}
else {
@@ -404,7 +425,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
return;
}
if(config.monitor.videoFormat != 0 && config::video.hevc_mode == 0) {
if(config.monitor.videoFormat != 0 && config::video.hevc_mode == 1) {
BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv;
respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
@@ -412,14 +433,22 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
}
auto session = session::alloc(config, launch_session->gcm_key, launch_session->iv);
if(!server->accept(session)) {
auto slot = server->accept(session);
if(!slot) {
BOOST_LOG(info) << "Ran out of slots for client from ["sv << ']';
respond(server->host(), peer, &option, 503, "Service Unavailable", req->sequenceNumber, {});
return;
}
session::start(*session, platf::from_sockaddr((sockaddr*)&peer->address.address));
if(session::start(*session, platf::from_sockaddr((sockaddr*)&peer->address.address))) {
BOOST_LOG(error) << "Failed to start a streaming session"sv;
server->clear(slot);
respond(server->host(), peer, &option, 500, "Internal Server Error", req->sequenceNumber, {});
return;
}
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {});
}
@@ -444,7 +473,13 @@ void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event) {
server.map("PLAY"sv, &cmd_play);
server.bind(RTSP_SETUP_PORT);
if(server.bind(RTSP_SETUP_PORT)) {
BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << RTSP_SETUP_PORT << "], likely another process already bound to the port"sv;
shutdown_event->raise(true);
return;
}
while(!shutdown_event->peek()) {
server.iterate(std::min(500ms, config::stream.ping_timeout));

View File

@@ -71,6 +71,7 @@ struct video_packet_raw_t {
}
RTP_PACKET rtp;
char reserved[4];
NV_VIDEO_PACKET packet;
};
@@ -99,10 +100,11 @@ static inline void while_starting_do_nothing(std::atomic<session::state_e> &stat
class control_server_t {
public:
control_server_t(control_server_t &&) noexcept = default;
control_server_t &operator=(control_server_t &&) noexcept = default;
int bind(std::uint16_t port) {
_host = net::host_create(_addr, config::stream.channels, port);
explicit control_server_t(std::uint16_t port) : _host { net::host_create(_addr, config::stream.channels, port) } {}
return !(bool)_host;
}
void emplace_addr_to_session(const std::string &addr, session_t &session) {
auto lg = _map_addr_session.lock();
@@ -160,9 +162,9 @@ struct broadcast_ctx_t {
asio::io_service io;
udp::socket video_sock { io, udp::endpoint(udp::v4(), VIDEO_STREAM_PORT) };
udp::socket audio_sock { io, udp::endpoint(udp::v4(), AUDIO_STREAM_PORT) };
control_server_t control_server { CONTROL_PORT };
udp::socket video_sock { io };
udp::socket audio_sock { io };
control_server_t control_server;
};
struct session_t {
@@ -299,6 +301,10 @@ struct fec_t {
size_t blocksize;
util::buffer_t<char> shards;
char *data(size_t el) {
return &shards[el*blocksize];
}
std::string_view operator[](size_t el) const {
return { &shards[el*blocksize], blocksize };
}
@@ -494,8 +500,11 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
server->send(std::string_view {(char*)payload.data(), payload.size()});
shutdown_event->raise(true);
continue;
auto lg = server->_map_addr_session.lock();
for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) {
auto session = pos->second.second;
session->shutdown_event.raise(true);
}
}
server->iterate(500ms);
@@ -614,8 +623,6 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
frame_new = "\000\000\000\001("sv;
}
assert(std::search(std::begin(payload), std::end(payload), std::begin(hevc_i_frame), std::end(hevc_i_frame)) ==
std::end(payload));
payload_new = replace(payload, frame_old, frame_new);
payload = {(char *) payload_new.data(), payload_new.size()};
}
@@ -647,6 +654,7 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
video_packet->packet.flags |= FLAG_EOF;
}
video_packet->rtp.header = FLAG_EXTENSION;
video_packet->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + fecIndex);
});
@@ -659,7 +667,7 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
}
for (auto x = shards.data_shards; x < shards.size(); ++x) {
video_packet_raw_t *inspect = (video_packet_raw_t *)shards[x].data();
auto *inspect = (video_packet_raw_t *)shards.data(x);
inspect->packet.frameIndex = packet->pts;
inspect->packet.fecInfo = (
@@ -668,10 +676,11 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
fecPercentage << 4
);
inspect->rtp.header = FLAG_EXTENSION;
inspect->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + x);
}
for (auto x = 0; x < shards.size(); ++x) {
for(auto x = 0; x < shards.size(); ++x) {
sock.send_to(asio::buffer(shards[x]), session->video.peer);
}
@@ -717,9 +726,44 @@ void audioBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, aud
}
int start_broadcast(broadcast_ctx_t &ctx) {
ctx.video_packets = std::make_shared<video::packet_queue_t::element_type>();
ctx.audio_packets = std::make_shared<audio::packet_queue_t::element_type>();
ctx.message_queue_queue = std::make_shared<message_queue_queue_t::element_type>();
if(ctx.control_server.bind(CONTROL_PORT)) {
BOOST_LOG(error) << "Couldn't bind Control server to port ["sv << CONTROL_PORT << "], likely another process already bound to the port"sv;
return -1;
}
boost::system::error_code ec;
ctx.video_sock.open(udp::v4(), ec);
if(ec) {
BOOST_LOG(fatal) << "Couldn't open socket for Video server: "sv << ec.message();
return -1;
}
ctx.video_sock.bind(udp::endpoint(udp::v4(), VIDEO_STREAM_PORT), ec);
if(ec) {
BOOST_LOG(fatal) << "Couldn't bind Video server to port ["sv << VIDEO_STREAM_PORT << "]: "sv << ec.message();
return -1;
}
ctx.audio_sock.open(udp::v4(), ec);
if(ec) {
BOOST_LOG(fatal) << "Couldn't open socket for Audio server: "sv << ec.message();
return -1;
}
ctx.audio_sock.bind(udp::endpoint(udp::v4(), AUDIO_STREAM_PORT), ec);
if(ec) {
BOOST_LOG(fatal) << "Couldn't bind Audio server to port ["sv << AUDIO_STREAM_PORT << "]: "sv << ec.message();
return -1;
}
ctx.video_packets = std::make_shared<video::packet_queue_t::element_type>(30);
ctx.audio_packets = std::make_shared<audio::packet_queue_t::element_type>(30);
ctx.message_queue_queue = std::make_shared<message_queue_queue_t::element_type>(30);
ctx.video_thread = std::thread { videoBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.video_sock), ctx.video_packets };
ctx.audio_thread = std::thread { audioBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.audio_sock), ctx.audio_packets };
@@ -762,7 +806,7 @@ void end_broadcast(broadcast_ctx_t &ctx) {
int recv_ping(decltype(broadcast)::ptr_t ref, socket_e type, asio::ip::address &addr, std::chrono::milliseconds timeout) {
auto constexpr ping = "PING"sv;
auto messages = std::make_shared<message_queue_t::element_type>();
auto messages = std::make_shared<message_queue_t::element_type>(30);
ref->message_queue_queue->raise(type, addr, messages);
auto fg = util::fail_guard([&]() {
@@ -840,7 +884,6 @@ state_e state(session_t &session) {
void stop(session_t &session) {
while_starting_do_nothing(session.state);
auto expected = state_e::RUNNING;
auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING);
if(already_stopping) {
@@ -857,13 +900,20 @@ void join(session_t &session) {
session.audioThread.join();
BOOST_LOG(debug) << "Waiting for control to end..."sv;
session.controlEnd.view();
//Reset input on session stop to avoid stuck repeated keys
BOOST_LOG(debug) << "Resetting Input..."sv;
input::reset(session.input);
BOOST_LOG(debug) << "Session ended"sv;
}
void start(session_t &session, const std::string &addr_string) {
int start(session_t &session, const std::string &addr_string) {
session.input = input::alloc();
session.broadcast_ref = broadcast.ref();
if(!session.broadcast_ref) {
return -1;
}
session.broadcast_ref->control_server.emplace_addr_to_session(addr_string, session);
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
@@ -872,6 +922,8 @@ void start(session_t &session, const std::string &addr_string) {
session.videoThread = std::thread {videoThread, &session, addr_string};
session.state.store(state_e::RUNNING, std::memory_order_relaxed);
return 0;
}
std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv) {

View File

@@ -31,7 +31,7 @@ enum class state_e : int {
};
std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv);
void start(session_t &session, const std::string &addr_string);
int start(session_t &session, const std::string &addr_string);
void stop(session_t &session);
void join(session_t &session);
state_e state(session_t &session);

View File

@@ -46,6 +46,15 @@ public:
return *this;
}
template<class V>
sync_t &operator=(V &&val) {
auto lg = lock();
raw = val;
return *this;
}
sync_t &operator=(const value_t &val) noexcept {
auto lg = lock();

View File

@@ -112,8 +112,14 @@ public:
using __return = std::invoke_result_t<Function, Args &&...>;
using task_t = std::packaged_task<__return()>;
__time_point time_point = std::chrono::steady_clock::now() + duration;
__time_point time_point;
if constexpr (std::is_floating_point_v<X>) {
time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
}
else {
time_point = std::chrono::steady_clock::now() + duration;
}
auto bind = [task = std::forward<Function>(newTask), tuple_args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
return std::apply(task, std::move(tuple_args));

View File

@@ -26,14 +26,19 @@ public:
return;
}
_status = status_t { std::forward<Args>(args)... };
if constexpr (std::is_same_v<std::optional<T>, status_t>) {
_status = std::make_optional<T>(std::forward<Args>(args)...);
}
else {
_status = status_t { std::forward<Args>(args)... };
}
_cv.notify_all();
}
// pop and view shoud not be used interchangebly
status_t pop() {
std::unique_lock ul{_lock};
std::unique_lock ul{ _lock };
if (!_continue) {
return util::false_v<status_t>;
@@ -55,7 +60,7 @@ public:
// pop and view shoud not be used interchangebly
template<class Rep, class Period>
status_t pop(std::chrono::duration<Rep, Period> delay) {
std::unique_lock ul{_lock};
std::unique_lock ul{ _lock };
if (!_continue) {
return util::false_v<status_t>;
@@ -74,7 +79,7 @@ public:
// pop and view shoud not be used interchangebly
const status_t &view() {
std::unique_lock ul{_lock};
std::unique_lock ul { _lock };
if (!_continue) {
return util::false_v<status_t>;
@@ -98,7 +103,7 @@ public:
}
void stop() {
std::lock_guard lg{_lock};
std::lock_guard lg{ _lock };
_continue = false;
@@ -106,7 +111,7 @@ public:
}
void reset() {
std::lock_guard lg{_lock};
std::lock_guard lg{ _lock };
_continue = true;
@@ -118,8 +123,8 @@ public:
}
private:
bool _continue{true};
status_t _status;
bool _continue { true };
status_t _status { util::false_v<status_t> };
std::condition_variable _cv;
std::mutex _lock;
@@ -130,14 +135,20 @@ class queue_t {
using status_t = util::optional_t<T>;
public:
queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {}
template<class ...Args>
void raise(Args &&... args) {
std::lock_guard lg{_lock};
std::lock_guard ul { _lock };
if(!_continue) {
return;
}
if(_queue.size() == _max_elements) {
_queue.clear();
}
_queue.emplace_back(std::forward<Args>(args)...);
_cv.notify_all();
@@ -151,7 +162,7 @@ public:
template<class Rep, class Period>
status_t pop(std::chrono::duration<Rep, Period> delay) {
std::unique_lock ul{_lock};
std::unique_lock ul { _lock };
if (!_continue) {
return util::false_v<status_t>;
@@ -170,7 +181,7 @@ public:
}
status_t pop() {
std::unique_lock ul{_lock};
std::unique_lock ul { _lock };
if (!_continue) {
return util::false_v<status_t>;
@@ -191,11 +202,12 @@ public:
}
std::vector<T> &unsafe() {
std::lock_guard { _lock };
return _queue;
}
void stop() {
std::lock_guard lg{_lock};
std::lock_guard lg { _lock };
_continue = false;
@@ -208,10 +220,12 @@ public:
private:
bool _continue{true};
bool _continue { true };
std::uint32_t _max_elements;
std::mutex _lock;
std::condition_variable _cv;
std::vector<T> _queue;
};
@@ -274,9 +288,8 @@ public:
void release() {
std::lock_guard lg { owner->_lock };
auto c = owner->_count.fetch_sub(1, std::memory_order_acquire);
if(c - 1 == 0) {
if(!--owner->_count) {
owner->_destruct(*get());
(*this)->~element_type();
}
@@ -296,16 +309,17 @@ public:
template<class FC, class FD>
shared_t(FC && fc, FD &&fd) : _construct { std::forward<FC>(fc) }, _destruct { std::forward<FD>(fd) } {}
[[nodiscard]] ptr_t ref() {
auto c = _count.fetch_add(1, std::memory_order_acquire);
if(!c) {
std::lock_guard lg { _lock };
std::lock_guard lg { _lock };
if(!_count) {
new(_object_buf.data()) element_type;
if(_construct(*reinterpret_cast<element_type*>(_object_buf.data()))) {
return ptr_t { nullptr };
}
}
++_count;
return ptr_t { this };
}
private:
@@ -314,7 +328,7 @@ private:
std::array<std::uint8_t, sizeof(element_type)> _object_buf;
std::atomic<std::uint32_t> _count;
std::uint32_t _count;
std::mutex _lock;
};

View File

@@ -76,36 +76,8 @@ struct __either<false, X, Y> {
template<bool V, class X, class Y>
using either_t = typename __either<V, X, Y>::type;
template<class T, class V = void>
struct __false_v;
template<class T>
struct __false_v<T, std::enable_if_t<instantiation_of_v<std::optional, T>>> {
static constexpr std::nullopt_t value = std::nullopt;
};
template<class T>
struct __false_v<T, std::enable_if_t<
(std::is_pointer_v<T> || instantiation_of_v<std::unique_ptr, T> || instantiation_of_v<std::shared_ptr, T>)
>> {
static constexpr std::nullptr_t value = nullptr;
};
template<class T>
struct __false_v<T, std::enable_if_t<std::is_same_v<T, bool>>> {
static constexpr bool value = false;
};
template<class T>
static constexpr auto false_v = __false_v<T>::value;
template<class T>
using optional_t = either_t<
(std::is_same_v<T, bool> ||
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
std::is_pointer_v<T>),
T, std::optional<T>>;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class T>
class FailGuard {
@@ -359,98 +331,6 @@ auto enm(T& val) -> std::underlying_type_t<T>& {
return *reinterpret_cast<std::underlying_type_t<T>*>(&val);
}
template<class ReturnType, class ...Args>
struct Function {
typedef ReturnType (*type)(Args...);
};
template<class T, class ReturnType, typename Function<ReturnType, T>::type function>
struct Destroy {
typedef T pointer;
void operator()(pointer p) {
function(p);
}
};
template<class T, typename Function<void, T*>::type function>
using safe_ptr = std::unique_ptr<T, Destroy<T*, void, function>>;
// You cannot specialize an alias
template<class T, class ReturnType, typename Function<ReturnType, T*>::type function>
using safe_ptr_v2 = std::unique_ptr<T, Destroy<T*, ReturnType, function>>;
template<class T>
void c_free(T *p) {
free(p);
}
template<class T>
using c_ptr = safe_ptr<T, c_free<T>>;
template<class T>
class FakeContainer {
typedef T pointer;
pointer _begin;
pointer _end;
public:
FakeContainer(pointer begin, pointer end) : _begin(begin), _end(end) {}
pointer begin() { return _begin; }
pointer end() { return _end; }
const pointer begin() const { return _begin; }
const pointer end() const { return _end; }
const pointer cbegin() const { return _begin; }
const pointer cend() const { return _end; }
pointer data() { return begin(); }
const pointer data() const { return cbegin(); }
std::size_t size() const { return std::distance(begin(), end()); }
};
template<class T>
FakeContainer<T> toContainer(T begin, T end) {
return { begin, end };
}
template<class T>
FakeContainer<T> toContainer(T begin, std::size_t end) {
return { begin, begin + end };
}
template<class T>
FakeContainer<T*> toContainer(T * const begin) {
T *end = begin;
auto default_val = T();
while(*end != default_val) {
++end;
}
return toContainer(begin, end);
}
template<class T, class H>
struct _init_helper;
template<template<class...> class T, class H, class... Args>
struct _init_helper<T<Args...>, H> {
using type = T<Args...>;
static type move(Args&&... args, H&&) {
return std::make_tuple(std::move(args)...);
}
static type copy(const Args&... args, const H&) {
return std::make_tuple(args...);
}
};
inline std::int64_t from_chars(const char *begin, const char *end) {
std::int64_t res {};
std::int64_t mul = 1;
@@ -496,18 +376,280 @@ public:
}
};
// Compared to std::unique_ptr, it adds the ability to get the address of the pointer itself
template <typename T, typename D = std::default_delete<T>>
class uniq_ptr {
public:
using element_type = T;
using pointer = element_type*;
using deleter_type = D;
constexpr uniq_ptr() noexcept : _p { nullptr } {}
constexpr uniq_ptr(std::nullptr_t) noexcept : _p { nullptr } {}
uniq_ptr(const uniq_ptr &other) noexcept = delete;
uniq_ptr &operator=(const uniq_ptr &other) noexcept = delete;
template<class V>
uniq_ptr(V *p) noexcept : _p { p } {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<element_type, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
}
template<class V>
uniq_ptr(std::unique_ptr<V, deleter_type> &&uniq) noexcept : _p { uniq.release() } {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
}
template<class V>
uniq_ptr(uniq_ptr<V, deleter_type> &&other) noexcept : _p { other.release() } {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
}
template<class V>
uniq_ptr &operator=(uniq_ptr<V, deleter_type> &&other) noexcept {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
reset(other.release());
return *this;
}
template<class V>
uniq_ptr &operator=(std::unique_ptr<V, deleter_type> &&uniq) noexcept {
static_assert(std::is_same_v<element_type, void> || std::is_same_v<T, V> || std::is_base_of_v<element_type, V>, "element_type must be base class of V");
reset(uniq.release());
return *this;
}
~uniq_ptr() {
reset();
}
void reset(pointer p = pointer()) {
if(_p) {
_deleter(_p);
}
_p = p;
}
pointer release() {
auto tmp = _p;
_p = nullptr;
return tmp;
}
pointer get() {
return _p;
}
const pointer get() const {
return _p;
}
const std::add_lvalue_reference_t<element_type> operator*() const {
return *_p;
}
std::add_lvalue_reference_t<element_type> operator*() {
return *_p;
}
const pointer operator->() const {
return _p;
}
pointer operator->() {
return _p;
}
pointer *operator&() const {
return &_p;
}
pointer *operator&() {
return &_p;
}
deleter_type& get_deleter() {
return _deleter;
}
const deleter_type& get_deleter() const {
return _deleter;
}
explicit operator bool() const {
return _p != nullptr;
}
protected:
pointer _p;
deleter_type _deleter;
};
template<class T1, class D1, class T2, class D2>
bool operator==(const uniq_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const uniq_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
return x.get() != y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator==(const std::unique_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const std::unique_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
return x.get() != y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator==(const uniq_ptr<T1, D1>& x, const std::unique_ptr<T1, D1>& y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const uniq_ptr<T1, D1>& x, const std::unique_ptr<T1, D1>& y) {
return x.get() != y.get();
}
template<class T, class D>
bool operator==(const uniq_ptr<T, D>& x, std::nullptr_t) {
return !(bool)x;
}
template<class T, class D>
bool operator!=(const uniq_ptr<T, D>& x, std::nullptr_t) {
return (bool)x;
}
template<class T, class D>
bool operator==(std::nullptr_t, const uniq_ptr<T, D>& y) {
return !(bool)y;
}
template<class T, class D>
bool operator!=(std::nullptr_t, const uniq_ptr<T, D>& y) {
return (bool)y;
}
template<class T>
class wrap_ptr {
public:
using element_type = T;
using pointer = element_type*;
using reference = element_type&;
wrap_ptr() : _own_ptr { false }, _p { nullptr } {}
wrap_ptr(pointer p) : _own_ptr { false }, _p { p } {}
wrap_ptr(std::unique_ptr<element_type> &&uniq_p) : _own_ptr { true }, _p { uniq_p.release() } {}
wrap_ptr(wrap_ptr &&other) : _own_ptr { other._own_ptr }, _p { other._p } {
other._own_ptr = false;
}
wrap_ptr &operator=(wrap_ptr &&other) noexcept {
if(_own_ptr) {
delete _p;
}
_p = other._p;
_own_ptr = other._own_ptr;
other._own_ptr = false;
return *this;
}
template<class V>
wrap_ptr &operator=(std::unique_ptr<V> &&uniq_ptr) {
static_assert(std::is_base_of_v<element_type, V>, "element_type must be base class of V");
_own_ptr = true;
_p = uniq_ptr.release();
return *this;
}
wrap_ptr &operator=(pointer p) {
if(_own_ptr) {
delete _p;
}
_p = p;
_own_ptr = false;
return *this;
}
~wrap_ptr() {
if(_own_ptr) {
delete _p;
}
_own_ptr = false;
}
const reference operator*() const {
return *_p;
}
reference operator*() {
return *_p;
}
const pointer operator->() const {
return _p;
}
pointer operator->() {
return _p;
}
private:
bool _own_ptr;
pointer _p;
};
template<class T, class V = void>
struct __false_v;
template<class T>
struct __false_v<T, std::enable_if_t<instantiation_of_v<std::optional, T>>> {
static constexpr std::nullopt_t value = std::nullopt;
};
template<class T>
struct __false_v<T, std::enable_if_t<
(
std::is_pointer_v<T> ||
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
instantiation_of_v<uniq_ptr, T>
)
>> {
static constexpr std::nullptr_t value = nullptr;
};
template<class T>
struct __false_v<T, std::enable_if_t<std::is_same_v<T, bool>>> {
static constexpr bool value = false;
};
template<class T>
static constexpr auto false_v = __false_v<T>::value;
template<class T>
using optional_t = either_t<
(std::is_same_v<T, bool> ||
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
instantiation_of_v<uniq_ptr, T> ||
std::is_pointer_v<T>),
T, std::optional<T>>;
template<class T>
class buffer_t {
public:
buffer_t() : _els { 0 } {};
buffer_t(buffer_t&&) noexcept = default;
buffer_t &operator=(buffer_t&& other) noexcept {
std::swap(_els, other._els);
_buf = std::move(other._buf);
return *this;
};
buffer_t &operator=(buffer_t&& other) noexcept = default;
explicit buffer_t(size_t elements) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {}
explicit buffer_t(size_t elements, const T &t) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {
@@ -561,6 +703,35 @@ T either(std::optional<T> &&l, T &&r) {
return std::forward<T>(r);
}
template<class ReturnType, class ...Args>
struct Function {
typedef ReturnType (*type)(Args...);
};
template<class T, class ReturnType, typename Function<ReturnType, T>::type function>
struct Destroy {
typedef T pointer;
void operator()(pointer p) {
function(p);
}
};
template<class T, typename Function<void, T*>::type function>
using safe_ptr = uniq_ptr<T, Destroy<T*, void, function>>;
// You cannot specialize an alias
template<class T, class ReturnType, typename Function<ReturnType, T*>::type function>
using safe_ptr_v2 = uniq_ptr<T, Destroy<T*, ReturnType, function>>;
template<class T>
void c_free(T *p) {
free(p);
}
template<class T>
using c_ptr = safe_ptr<T, c_free<T>>;
namespace endian {
template<class T = void>
struct endianness {

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
#define SUNSHINE_VIDEO_H
#include "thread_safe.h"
#include "input.h"
#include "platform/common.h"
extern "C" {
@@ -14,16 +15,27 @@ extern "C" {
struct AVPacket;
namespace video {
void free_packet(AVPacket *packet);
struct packet_raw_t : public AVPacket {
template<class P>
explicit packet_raw_t(P *user_data) : channel_data { user_data } {
av_init_packet(this);
void init_packet() {
pts = AV_NOPTS_VALUE;
dts = AV_NOPTS_VALUE;
pos = -1;
duration = 0;
flags = 0;
stream_index = 0;
buf = nullptr;
side_data = nullptr;
side_data_elems = 0;
}
explicit packet_raw_t(std::nullptr_t null) : channel_data { nullptr } {
av_init_packet(this);
template<class P>
explicit packet_raw_t(P *user_data) : channel_data { user_data } {
init_packet();
}
explicit packet_raw_t(std::nullptr_t) : channel_data { nullptr } {
init_packet();
}
~packet_raw_t() {
@@ -56,6 +68,8 @@ void capture(
idr_event_t idr_events,
config_t config,
void *channel_data);
int init();
}
#endif //SUNSHINE_VIDEO_H