mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 18:36:34 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 381e8bcfaa | |||
| 1050978246 | |||
| 9e48e58221 | |||
| 5d313b509e | |||
| 022b2202f6 | |||
| 2e9a1cfbba | |||
| 1d84c8f9ce | |||
| 92cd8648fa | |||
| 41cc9a3e80 | |||
| b97c902d10 | |||
| 25309f21ee | |||
| 1ba8da9780 | |||
| 66bc335d3f | |||
| e834f375fb | |||
| 9c41972b65 | |||
| 41c30e9cfd | |||
| 377b086882 | |||
| ade2ef3a15 | |||
| 6f428eb316 | |||
| 2970ad662c | |||
| 1dfe49e765 | |||
| 7a93a72710 | |||
| 661c8260e5 | |||
| 13c2da07e8 | |||
| 71b214ca43 | |||
| 513942c888 | |||
| 67df04e0a2 | |||
| 7b45f0d899 | |||
| 0f661e467e | |||
| 0232d8027c | |||
| 3a0377851d | |||
| a93bad4cf3 | |||
| 88c3828ad3 | |||
| c19853f03f | |||
| 1b7e103ef6 | |||
| 900d59b3ac | |||
| 37a9256587 | |||
| 127b5501d9 | |||
| a081a9f5c4 | |||
| fe8c2ceab9 | |||
| 1be5b4787f | |||
| bb88d6f828 | |||
| a7030641f3 | |||
| 208de3dae9 | |||
| e81db118d5 | |||
| 488d8e5fc2 | |||
| 0049b36471 | |||
| 66b4b70023 | |||
| 438ae6a761 | |||
| 0cfb440cf6 | |||
| a613280bbd | |||
| 6112c81db7 | |||
| 3f26a4fd21 | |||
| bb5b003dd5 | |||
| 99777c8e82 | |||
| 4daaa1f089 | |||
| 0828cc3f83 | |||
| 790835f6c6 | |||
| fe3784454a | |||
| b336bf2fcb | |||
| 87be37293e | |||
| 7abcfc0390 | |||
| 020e2069cb | |||
| 83dd07469e | |||
| 0a883ab651 | |||
| 7b39b93bb2 | |||
| 30496c79ab |
+4
-3
@@ -1,8 +1,9 @@
|
||||
build
|
||||
cmake-build-*
|
||||
cmake-build*
|
||||
.DS_Store
|
||||
|
||||
.vscode
|
||||
.vs
|
||||
*.swp
|
||||
*.kdev4
|
||||
|
||||
.idea
|
||||
.idea
|
||||
@@ -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
|
||||
|
||||
+54
-48
@@ -1,61 +1,46 @@
|
||||
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/input.cpp
|
||||
sunshine/platform/windows/display.h
|
||||
@@ -68,13 +53,38 @@ if(WIN32)
|
||||
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
|
||||
)
|
||||
|
||||
@@ -84,6 +94,7 @@ 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/display.cpp
|
||||
sunshine/platform/linux/input.cpp)
|
||||
@@ -94,6 +105,7 @@ else()
|
||||
xcb
|
||||
xcb-shm
|
||||
xcb-xfixes
|
||||
Xrandr
|
||||
${X11_LIBRARIES}
|
||||
evdev
|
||||
pulse
|
||||
@@ -178,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
|
||||
|
||||
@@ -12,48 +12,60 @@ Sunshine is a Gamestream host for Moonlight
|
||||
|
||||
### Requirements:
|
||||
Ubuntu 20.04:
|
||||
|
||||
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
|
||||
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`: It is suggested to use the `-j C#` flags with this command, `C#` being the number of cores your PC has
|
||||
- `make -j ${nproc}`
|
||||
|
||||
|
||||
### 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`
|
||||
- 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
|
||||
- `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
|
||||
* 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
|
||||
|
||||
|
||||
- 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:
|
||||
|
||||
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
|
||||
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 --recurse-submodules`
|
||||
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recursive`
|
||||
- `cd sunshine && mkdir build && cd build`
|
||||
- `cmake -G"Unix Makefiles" ..`
|
||||
- `make`
|
||||
@@ -61,17 +73,6 @@ sunshine needs access to uinput to create mouse and gamepad events:
|
||||
### 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 -G"Unix Makefiles" ..`
|
||||
- `make`
|
||||
|
||||
|
||||
|
||||
# Common
|
||||
@@ -116,15 +117,20 @@ sunshine needs access to uinput to create mouse and gamepad events:
|
||||
"cmd":"command to open app",
|
||||
"prep-cmd":[
|
||||
{
|
||||
"do":"somecommand",
|
||||
"undo":"undothatcommand"
|
||||
"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
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
image:
|
||||
- Ubuntu
|
||||
- Ubuntu2004
|
||||
- Visual Studio 2019
|
||||
|
||||
environment:
|
||||
@@ -8,8 +8,8 @@ environment:
|
||||
- BUILD_TYPE: Release
|
||||
|
||||
install:
|
||||
- sh: sudo apt update
|
||||
- sh: sudo apt install -y build-essential 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
|
||||
- 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:
|
||||
@@ -20,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%
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"name":"Steam BigPicture",
|
||||
|
||||
"output":"steam.txt",
|
||||
"cmd":"steam -bigpicture"
|
||||
"detached":["setsid steam steam://open/bigpicture"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
"name":"Steam BigPicture",
|
||||
|
||||
"output":"steam.txt",
|
||||
"prep-cmd":[
|
||||
{"do":"steam \"steam://open/bigpicture\""}
|
||||
]
|
||||
"detached":["steam steam://open/bigpicture"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+28
-1
@@ -93,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
|
||||
@@ -121,11 +124,12 @@
|
||||
# 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 = 0
|
||||
# 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
|
||||
@@ -170,6 +174,29 @@
|
||||
##########################
|
||||
# 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
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ Package: sunshine
|
||||
Architecture: amd64
|
||||
Maintainer: @loki
|
||||
Priority: optional
|
||||
Version: 0.2.1
|
||||
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
|
||||
|
||||
+1
-1
Submodule moonlight-common-c updated: cfeb0ffd90...5d09d43b08
-1
Submodule pre-compiled deleted from afd9a9bbfc
+1
-6
@@ -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
|
||||
|
||||
+66
-2
@@ -85,6 +85,56 @@ int coder_from_view(const std::string_view &coder) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -103,9 +153,15 @@ video_t video {
|
||||
-1
|
||||
}, // nv
|
||||
|
||||
{
|
||||
amd::balanced,
|
||||
std::nullopt,
|
||||
-1
|
||||
}, // amd
|
||||
|
||||
{}, // encoder
|
||||
{}, // adapter_name
|
||||
{} // output_name
|
||||
{}, // output_name
|
||||
};
|
||||
|
||||
audio_t audio {};
|
||||
@@ -332,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;
|
||||
@@ -357,8 +416,13 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
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.preset, nv::rc_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);
|
||||
|
||||
+8
-1
@@ -26,6 +26,12 @@ struct video_t {
|
||||
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;
|
||||
@@ -71,7 +77,8 @@ 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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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) {
|
||||
|
||||
+151
-17
@@ -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,11 @@ 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 {};
|
||||
@@ -78,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) {
|
||||
@@ -138,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);
|
||||
@@ -162,23 +188,107 @@ 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;
|
||||
}
|
||||
|
||||
platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED);
|
||||
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) {
|
||||
@@ -223,10 +333,10 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
|
||||
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) {
|
||||
@@ -374,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);
|
||||
@@ -384,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);
|
||||
@@ -402,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();
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -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
|
||||
|
||||
+6
-2
@@ -15,7 +15,6 @@
|
||||
#include <boost/log/attributes/clock.hpp>
|
||||
|
||||
#include "video.h"
|
||||
#include "input.h"
|
||||
#include "nvhttp.h"
|
||||
#include "rtsp.h"
|
||||
#include "config.h"
|
||||
@@ -138,7 +137,10 @@ 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;
|
||||
@@ -150,6 +152,8 @@ int main(int argc, char *argv[]) {
|
||||
stream::rtpThread(shutdown_event);
|
||||
|
||||
httpThread.join();
|
||||
task_pool.stop();
|
||||
task_pool.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+21
-3
@@ -360,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;
|
||||
@@ -842,8 +846,22 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::thread ssl { &https_server_t::accept_and_run, &https_server };
|
||||
std::thread tcp { &http_server_t::accept_and_run, &http_server };
|
||||
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();
|
||||
|
||||
@@ -43,6 +43,33 @@ enum class pix_fmt_e {
|
||||
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;
|
||||
@@ -95,6 +122,7 @@ enum class capture_e : int {
|
||||
|
||||
class display_t {
|
||||
public:
|
||||
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;
|
||||
|
||||
@@ -106,6 +134,9 @@ public:
|
||||
|
||||
virtual ~display_t() = default;
|
||||
|
||||
// Offsets for when streaming a specific monitor. By default, they are 0.
|
||||
int offset_x, offset_y;
|
||||
|
||||
int width, height;
|
||||
};
|
||||
|
||||
@@ -131,6 +162,7 @@ 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);
|
||||
|
||||
+179
-119
@@ -14,6 +14,7 @@
|
||||
#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>
|
||||
@@ -26,31 +27,35 @@
|
||||
#include "sunshine/config.h"
|
||||
#include "sunshine/main.h"
|
||||
|
||||
namespace platf {
|
||||
namespace platf
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
void freeImage(XImage *);
|
||||
void freeX(XFixesCursorImage *);
|
||||
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>;
|
||||
|
||||
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(int id) : id { id } {}
|
||||
shm_id_t(shm_id_t &&other) noexcept : id(other.id) {
|
||||
other.id = -1;
|
||||
}
|
||||
|
||||
~shm_id_t() {
|
||||
if(id != -1) {
|
||||
if (id != -1) {
|
||||
shmctl(id, IPC_RMID, nullptr);
|
||||
id = -1;
|
||||
}
|
||||
@@ -60,38 +65,37 @@ public:
|
||||
|
||||
class shm_data_t {
|
||||
public:
|
||||
shm_data_t() : data {(void*)-1 } {}
|
||||
shm_data_t(void *data) : data {data } {}
|
||||
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) {
|
||||
if((std::uintptr_t) data != -1) {
|
||||
shmdt(data);
|
||||
data = (void*)-1;
|
||||
}
|
||||
}
|
||||
|
||||
void *data;
|
||||
};
|
||||
|
||||
struct x11_img_t : public img_t {
|
||||
struct x11_img_t: public img_t {
|
||||
ximg_t img;
|
||||
};
|
||||
|
||||
struct shm_img_t : public img_t {
|
||||
struct shm_img_t: public img_t {
|
||||
~shm_img_t() override {
|
||||
delete[] data;
|
||||
data = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
void blend_cursor(Display *display, img_t &img) {
|
||||
void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
|
||||
xcursor_t overlay { XFixesGetCursorImage(display) };
|
||||
|
||||
if(!overlay) {
|
||||
if (!overlay) {
|
||||
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
|
||||
return;
|
||||
}
|
||||
@@ -99,86 +103,138 @@ void blend_cursor(Display *display, img_t &img) {
|
||||
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 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 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 pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x];
|
||||
|
||||
auto colors_in = (uint8_t*)pixels_begin;
|
||||
std::for_each(overlay_begin, overlay_end,[&](long pixel) {
|
||||
int *pixel_p = (int*) &pixel;
|
||||
|
||||
auto alpha = (*(uint*)pixel_p) >> 24u;
|
||||
if(alpha == 255) {
|
||||
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;
|
||||
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 {} {
|
||||
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(fatal) << "Could not open x11 display"sv;
|
||||
log_flush();
|
||||
std::abort();
|
||||
BOOST_LOG(error) << "Could not open X11 display"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
xwindow = DefaultRootWindow(xdisplay.get());
|
||||
|
||||
refresh();
|
||||
|
||||
width = xattr.width;
|
||||
height = xattr.height;
|
||||
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);
|
||||
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
|
||||
}
|
||||
|
||||
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override {
|
||||
refresh();
|
||||
|
||||
if(width != xattr.width || height != xattr.height) {
|
||||
//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) };
|
||||
|
||||
XImage *img { XGetImage(
|
||||
xdisplay.get(),
|
||||
xwindow,
|
||||
0, 0,
|
||||
xattr.width, xattr.height,
|
||||
AllPlanes, ZPixmap)
|
||||
};
|
||||
|
||||
auto img_out = (x11_img_t*)img_out_base;
|
||||
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->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);
|
||||
if (cursor) {
|
||||
blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
@@ -192,13 +248,9 @@ struct x11_attr_t : public display_t {
|
||||
snapshot(img, 0s, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
xdisplay_t xdisplay;
|
||||
Window xwindow;
|
||||
XWindowAttributes xattr;
|
||||
};
|
||||
|
||||
struct shm_attr_t : public x11_attr_t {
|
||||
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;
|
||||
@@ -209,13 +261,14 @@ struct shm_attr_t : public x11_attr_t {
|
||||
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;
|
||||
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) } {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -224,39 +277,33 @@ struct shm_attr_t : public x11_attr_t {
|
||||
}
|
||||
|
||||
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override {
|
||||
if(width != xattr.width || height != xattr.height) {
|
||||
//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);
|
||||
|
||||
auto img_cookie = xcb_shm_get_image_unchecked(
|
||||
xcb.get(),
|
||||
display->root,
|
||||
0, 0,
|
||||
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;
|
||||
}
|
||||
|
||||
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::copy_n((std::uint8_t*)data.data, frame_size(), img->data);
|
||||
|
||||
if(cursor) {
|
||||
blend_cursor(shm_xdisplay.get(), *img);
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<shm_img_t>();
|
||||
img->width = width;
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->pixel_pitch = 4;
|
||||
img->row_pitch = img->pixel_pitch * width;
|
||||
@@ -270,6 +317,10 @@ struct shm_attr_t : public x11_attr_t {
|
||||
}
|
||||
|
||||
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())) {
|
||||
@@ -295,15 +346,12 @@ struct shm_attr_t : public x11_attr_t {
|
||||
xcb_shm_attach(xcb.get(), seg, shm_id.id, false);
|
||||
data.data = shmat(shm_id.id, nullptr, 0);
|
||||
|
||||
if ((uintptr_t)data.data == -1) {
|
||||
if((uintptr_t)data.data == -1) {
|
||||
BOOST_LOG(error) << "shmat failed"sv;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
width = display->width_in_pixels;
|
||||
height = display->height_in_pixels;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -312,17 +360,19 @@ struct shm_attr_t : public x11_attr_t {
|
||||
}
|
||||
};
|
||||
|
||||
struct mic_attr_t : public mic_t {
|
||||
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 {} {}
|
||||
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)) {
|
||||
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;
|
||||
@@ -332,28 +382,32 @@ struct mic_attr_t : public mic_t {
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<display_t> shm_display() {
|
||||
auto shm = std::make_shared<shm_attr_t>();
|
||||
|
||||
if(shm->init()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return shm;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto shm_disp = shm_display();
|
||||
// Attempt to use shared memory X11 to avoid copying the frame
|
||||
auto shm_disp = std::make_shared<shm_attr_t>();
|
||||
|
||||
if(!shm_disp) {
|
||||
return std::make_shared<x11_attr_t>();
|
||||
auto status = shm_disp->init();
|
||||
if(status > 0) {
|
||||
// x11_attr_t::init() failed, don't bother trying again.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return shm_disp;
|
||||
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) {
|
||||
@@ -362,15 +416,16 @@ std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
|
||||
int status;
|
||||
|
||||
const char *audio_sink = "@DEFAULT_MONITOR@";
|
||||
if(!config::audio.sink.empty()) {
|
||||
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)
|
||||
);
|
||||
pa_simple_new(nullptr, "sunshine",
|
||||
pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
|
||||
"sunshine-record", &mic->ss, nullptr, nullptr, &status));
|
||||
|
||||
if(!mic->mic) {
|
||||
if (!mic->mic) {
|
||||
auto err_str = pa_strerror(status);
|
||||
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
|
||||
|
||||
@@ -393,12 +448,14 @@ 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_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);
|
||||
if (family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data,
|
||||
INET_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
return std::string { data };
|
||||
@@ -410,30 +467,33 @@ std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_
|
||||
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;
|
||||
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;
|
||||
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 } };
|
||||
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)) {
|
||||
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()) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -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>(); }
|
||||
}
|
||||
|
||||
@@ -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,6 +307,12 @@ public:
|
||||
}
|
||||
|
||||
namespace platf {
|
||||
|
||||
// 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>();
|
||||
|
||||
@@ -330,6 +324,9 @@ std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
|
||||
}
|
||||
|
||||
std::unique_ptr<deinit_t> init() {
|
||||
if(dxgi::init()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<platf::audio::co_init_t>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ 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>>;
|
||||
|
||||
@@ -84,6 +85,19 @@ public:
|
||||
|
||||
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 {
|
||||
|
||||
@@ -90,16 +90,10 @@ int display_base_t::init() {
|
||||
FreeLibrary(user32);
|
||||
});
|
||||
*/
|
||||
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);
|
||||
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;
|
||||
@@ -110,7 +104,8 @@ int display_base_t::init() {
|
||||
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) {
|
||||
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;
|
||||
@@ -120,8 +115,9 @@ int display_base_t::init() {
|
||||
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_t output_tmp { output_p };
|
||||
|
||||
DXGI_OUTPUT_DESC desc;
|
||||
output_tmp->GetDesc(&desc);
|
||||
@@ -133,8 +129,10 @@ int display_base_t::init() {
|
||||
if(desc.AttachedToDesktop) {
|
||||
output = std::move(output_tmp);
|
||||
|
||||
width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
|
||||
height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
|
||||
offset_x = desc.DesktopCoordinates.left;
|
||||
offset_y = desc.DesktopCoordinates.top;
|
||||
width = desc.DesktopCoordinates.right - offset_x;
|
||||
height = desc.DesktopCoordinates.bottom - offset_y;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,8 +148,6 @@ int display_base_t::init() {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -164,7 +160,6 @@ int display_base_t::init() {
|
||||
status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -175,14 +170,12 @@ int display_base_t::init() {
|
||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
|
||||
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
D3D11_SDK_VERSION,
|
||||
&device_p,
|
||||
&device,
|
||||
&feature_level,
|
||||
&device_ctx_p);
|
||||
&device_ctx);
|
||||
|
||||
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() << ']';
|
||||
|
||||
@@ -206,12 +199,40 @@ int display_base_t::init() {
|
||||
|
||||
// Bump up thread priority
|
||||
{
|
||||
dxgi::dxgi_t::pointer dxgi_p {};
|
||||
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p);
|
||||
dxgi::dxgi_t dxgi { dxgi_p };
|
||||
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(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
BOOST_LOG(warning) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -220,25 +241,24 @@ int display_base_t::init() {
|
||||
|
||||
// Try to reduce latency
|
||||
{
|
||||
dxgi::dxgi1_t::pointer dxgi_p {};
|
||||
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p);
|
||||
dxgi::dxgi1_t dxgi { dxgi_p };
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
dxgi->SetMaximumFrameLatency(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::pointer output1_p {};
|
||||
status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1_p);
|
||||
dxgi::output1_t output1 {output1_p };
|
||||
|
||||
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;
|
||||
@@ -246,10 +266,8 @@ int display_base_t::init() {
|
||||
|
||||
// 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);
|
||||
status = output1->DuplicateOutput((IUnknown*)device.get(), &dup.dup);
|
||||
if(SUCCEEDED(status)) {
|
||||
dup.reset(dup_p);
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
|
||||
@@ -203,9 +203,8 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
|
||||
// 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};
|
||||
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() << ']';
|
||||
@@ -279,10 +278,7 @@ int display_ram_t::init() {
|
||||
t.Format = format;
|
||||
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
|
||||
dxgi::texture2d_t::pointer tex_p {};
|
||||
auto status = device->CreateTexture2D(&t, nullptr, &tex_p);
|
||||
|
||||
texture.reset(tex_p);
|
||||
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() << ']';
|
||||
|
||||
@@ -1,14 +1,131 @@
|
||||
#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 {
|
||||
std::shared_ptr<platf::display_t> display;
|
||||
shader_res_t input_res;
|
||||
texture2d_t texture;
|
||||
std::shared_ptr<platf::display_t> display;
|
||||
|
||||
~img_d3d_t() override = default;
|
||||
};
|
||||
@@ -97,6 +214,41 @@ util::buffer_t<std::uint8_t> make_cursor_image(util::buffer_t<std::uint8_t> &&im
|
||||
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 } {}
|
||||
@@ -109,81 +261,118 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
LONG x = ((double)rel_x) * out_width / (double)in_width;
|
||||
LONG y = ((double)rel_y) * out_height / (double)in_height;
|
||||
auto x = ((float)rel_x);
|
||||
auto y = ((float)rel_y);
|
||||
|
||||
// Ensure it's within bounds
|
||||
auto left_out = std::min<LONG>(out_width, std::max<LONG>(0, x));
|
||||
auto top_out = std::min<LONG>(out_height, std::max<LONG>(0, y));
|
||||
auto right_out = std::max<LONG>(0, std::min<LONG>(out_width, x + cursor_scaled_width));
|
||||
auto bottom_out = std::max<LONG>(0, std::min<LONG>(out_height, y + cursor_scaled_height));
|
||||
|
||||
auto left_in = std::max<LONG>(0, -rel_x);
|
||||
auto top_in = std::max<LONG>(0, -rel_y);
|
||||
auto right_in = std::min<LONG>(in_width - rel_x, cursor_width);
|
||||
auto bottom_in = std::min<LONG>(in_height - rel_y, cursor_height);
|
||||
|
||||
RECT rect_in { left_in, top_in, right_in, bottom_in };
|
||||
RECT rect_out { left_out, top_out, right_out, bottom_out };
|
||||
|
||||
ctx->VideoProcessorSetStreamSourceRect(processor.get(), 1, TRUE, &rect_in);
|
||||
ctx->VideoProcessorSetStreamDestRect(processor.get(), 1, TRUE, &rect_out);
|
||||
cursor_view.TopLeftX = x;
|
||||
cursor_view.TopLeftY = y;
|
||||
}
|
||||
|
||||
int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) {
|
||||
D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = { 0, (D3D11_VPIV_DIMENSION)D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } };
|
||||
auto device = (device_t::pointer)data;
|
||||
|
||||
video::processor_in_t::pointer processor_in_p;
|
||||
auto status = device->CreateVideoProcessorInputView(texture, processor_e.get(), &input_desc, &processor_in_p);
|
||||
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 VideoProcessorInputView [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
cursor_in.reset(processor_in_p);
|
||||
|
||||
cursor_width = width;
|
||||
cursor_height = height;
|
||||
cursor_scaled_width = ((double)width) / in_width * out_width;
|
||||
cursor_scaled_height = ((double)height) / in_height * out_height;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int convert(platf::img_t &img_base) override {
|
||||
auto &img = (img_d3d_t&)img_base;
|
||||
|
||||
auto it = texture_to_processor_in.find(img.texture.get());
|
||||
if(it == std::end(texture_to_processor_in)) {
|
||||
D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = { 0, (D3D11_VPIV_DIMENSION)D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } };
|
||||
if(!img.input_res) {
|
||||
auto device = (device_t::pointer)data;
|
||||
|
||||
video::processor_in_t::pointer processor_in_p;
|
||||
auto status = device->CreateVideoProcessorInputView(img.texture.get(), processor_e.get(), &input_desc, &processor_in_p);
|
||||
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 VideoProcessorInputView [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
it = texture_to_processor_in.emplace(img.texture.get(), processor_in_p).first;
|
||||
}
|
||||
auto &processor_in = it->second;
|
||||
|
||||
D3D11_VIDEO_PROCESSOR_STREAM stream[] {
|
||||
{ TRUE, 0, 0, 0, 0, nullptr, processor_in.get(), nullptr },
|
||||
{ TRUE, 0, 0, 0, 0, nullptr, cursor_in.get(), nullptr }
|
||||
};
|
||||
auto input_res_p = img.input_res.get();
|
||||
|
||||
auto status = ctx->VideoProcessorBlt(processor.get(), processor_out.get(), 0, cursor_visible ? 2 : 1, stream);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed size and color conversion [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
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 {
|
||||
colorspace |= (color_range >> 4);
|
||||
ctx->VideoProcessorSetOutputColorSpace(processor.get(), (D3D11_VIDEO_PROCESSOR_COLOR_SPACE*)&colorspace);
|
||||
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(
|
||||
@@ -193,53 +382,82 @@ public:
|
||||
) {
|
||||
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;
|
||||
this->in_width = in_width;
|
||||
this->in_height = in_height;
|
||||
|
||||
video::device_t::pointer vdevice_p;
|
||||
status = device_p->QueryInterface(IID_ID3D11VideoDevice, (void**)&vdevice_p);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query ID3D11VideoDevice interface [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
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;
|
||||
}
|
||||
device.reset(vdevice_p);
|
||||
|
||||
video::ctx_t::pointer ctx_p;
|
||||
status = device_ctx_p->QueryInterface(IID_ID3D11VideoContext, (void**)&ctx_p);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query ID3D11VideoContext interface [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
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;
|
||||
}
|
||||
ctx.reset(ctx_p);
|
||||
|
||||
D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc {
|
||||
D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE,
|
||||
{ 1, 1 }, (UINT)in_width, (UINT)in_height,
|
||||
{ 1, 1 }, (UINT)out_width, (UINT)out_height,
|
||||
D3D11_VIDEO_USAGE_OPTIMAL_QUALITY
|
||||
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
|
||||
};
|
||||
|
||||
video::processor_enum_t::pointer vp_e_p;
|
||||
status = device->CreateVideoProcessorEnumerator(&contentDesc, &vp_e_p);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create video processor enumerator [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
processor_e.reset(vp_e_p);
|
||||
|
||||
video::processor_t::pointer processor_p;
|
||||
status = device->CreateVideoProcessor(processor_e.get(), 0, &processor_p);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create video processor [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
processor.reset(processor_p);
|
||||
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;
|
||||
@@ -249,37 +467,61 @@ public:
|
||||
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 | D3D11_BIND_VIDEO_ENCODER;
|
||||
t.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
dxgi::texture2d_t::pointer tex_p {};
|
||||
status = device_p->CreateTexture2D(&t, nullptr, &tex_p);
|
||||
status = device_p->CreateTexture2D(&t, nullptr, &img.texture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create video output texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
img.texture.reset(tex_p);
|
||||
img.display = std::move(display);
|
||||
img.width = out_width;
|
||||
img.height = out_height;
|
||||
img.data = (std::uint8_t*)tex_p;
|
||||
img.data = (std::uint8_t*)img.texture.get();
|
||||
img.row_pitch = out_width;
|
||||
img.pixel_pitch = 1;
|
||||
|
||||
D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc { D3D11_VPOV_DIMENSION_TEXTURE2D, 0 };
|
||||
video::processor_out_t::pointer processor_out_p;
|
||||
status = device->CreateVideoProcessorOutputView(img.texture.get(), processor_e.get(), &output_desc, &processor_out_p);
|
||||
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 VideoProcessorOutputView [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
processor_out.reset(processor_out_p);
|
||||
|
||||
// Tell video processor alpha values need to be enabled
|
||||
ctx->VideoProcessorSetStreamAlpha(processor.get(), 1, TRUE, 1.0f);
|
||||
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());
|
||||
|
||||
device_p->AddRef();
|
||||
data = device_p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -293,25 +535,104 @@ public:
|
||||
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;
|
||||
video::device_t device;
|
||||
video::ctx_t ctx;
|
||||
video::processor_enum_t processor_e;
|
||||
video::processor_t processor;
|
||||
video::processor_out_t processor_out;
|
||||
std::unordered_map<texture2d_t::pointer, video::processor_in_t> texture_to_processor_in;
|
||||
|
||||
video::processor_in_t cursor_in;
|
||||
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;
|
||||
|
||||
LONG cursor_width, cursor_height;
|
||||
LONG cursor_scaled_width, cursor_scaled_height;
|
||||
float out_width, out_height;
|
||||
|
||||
LONG in_width, in_height;
|
||||
double 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;
|
||||
};
|
||||
|
||||
@@ -368,18 +689,17 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
t.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
dxgi::texture2d_t::pointer tex_p {};
|
||||
auto status = device->CreateTexture2D(&t, &data, &tex_p);
|
||||
texture2d_t texture;
|
||||
auto status = device->CreateTexture2D(&t, &data, &texture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
texture2d_t texture { tex_p };
|
||||
|
||||
for(auto *hwdevice : hwdevices) {
|
||||
if(hwdevice->set_cursor_texture(tex_p, t.Width, t.Height)) {
|
||||
if(hwdevice->set_cursor_texture(texture.get(), t.Width, t.Height)) {
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
@@ -396,15 +716,14 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec
|
||||
}
|
||||
|
||||
if(frame_update_flag) {
|
||||
texture2d_t::pointer src_p {};
|
||||
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p);
|
||||
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;
|
||||
}
|
||||
|
||||
texture2d_t src { src_p };
|
||||
device_ctx->CopyResource(img->texture.get(), src.get());
|
||||
}
|
||||
|
||||
@@ -422,21 +741,19 @@ std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = format;
|
||||
t.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
dxgi::texture2d_t::pointer tex_p {};
|
||||
auto status = device->CreateTexture2D(&t, nullptr, &tex_p);
|
||||
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*)tex_p;
|
||||
img->data = (std::uint8_t*)img->texture.get();
|
||||
img->row_pitch = 0;
|
||||
img->pixel_pitch = 4;
|
||||
img->width = 0;
|
||||
img->height = 0;
|
||||
img->texture.reset(tex_p);
|
||||
img->display = shared_from_this();
|
||||
|
||||
return img;
|
||||
@@ -449,8 +766,7 @@ int display_vram_t::dummy_img(platf::img_t *img_base) {
|
||||
auto dummy_data = std::make_unique<int[]>(width * height);
|
||||
D3D11_SUBRESOURCE_DATA data {
|
||||
dummy_data.get(),
|
||||
(UINT)img->row_pitch,
|
||||
0
|
||||
(UINT)img->row_pitch
|
||||
};
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
@@ -461,17 +777,17 @@ int display_vram_t::dummy_img(platf::img_t *img_base) {
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = format;
|
||||
t.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
dxgi::texture2d_t::pointer tex_p {};
|
||||
auto status = device->CreateTexture2D(&t, &data, &tex_p);
|
||||
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->data = (std::uint8_t*)tex_p;
|
||||
img->texture.reset(tex_p);
|
||||
img->texture = std::move(tex);
|
||||
img->data = (std::uint8_t*)img->texture.get();
|
||||
img->height = height;
|
||||
img->width = width;
|
||||
img->pixel_pitch = 4;
|
||||
@@ -480,8 +796,8 @@ int display_vram_t::dummy_img(platf::img_t *img_base) {
|
||||
}
|
||||
|
||||
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 && pix_fmt != platf::pix_fmt_e::p010) {
|
||||
BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << (int)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;
|
||||
}
|
||||
@@ -508,4 +824,44 @@ std::shared_ptr<platf::hwdevice_t> display_vram_t::make_hwdevice(int width, int
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock2.h>
|
||||
@@ -17,6 +18,14 @@ 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,10 +270,7 @@ 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) {
|
||||
@@ -282,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) {
|
||||
@@ -327,6 +358,24 @@ 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;
|
||||
|
||||
|
||||
+30
-5
@@ -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;
|
||||
}
|
||||
|
||||
@@ -251,12 +265,12 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
|
||||
proc::ctx_t ctx;
|
||||
|
||||
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;
|
||||
|
||||
if(prep_nodes_opt) {
|
||||
auto &prep_nodes = *prep_nodes_opt;
|
||||
|
||||
@@ -274,6 +288,16 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
|
||||
}
|
||||
}
|
||||
|
||||
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>()));
|
||||
}
|
||||
}
|
||||
|
||||
if(output) {
|
||||
ctx.output = parse_env_val(this_env, *output);
|
||||
}
|
||||
@@ -284,6 +308,7 @@ 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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-1
@@ -884,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) {
|
||||
@@ -901,6 +900,9 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
+223
-60
@@ -76,37 +76,6 @@ 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...>;
|
||||
|
||||
@@ -362,35 +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>>;
|
||||
|
||||
inline std::int64_t from_chars(const char *begin, const char *end) {
|
||||
std::int64_t res {};
|
||||
std::int64_t mul = 1;
|
||||
@@ -436,6 +376,163 @@ 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 {
|
||||
@@ -510,6 +607,43 @@ private:
|
||||
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:
|
||||
@@ -569,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 {
|
||||
|
||||
+117
-16
@@ -38,10 +38,6 @@ void free_buffer(AVBufferRef *ref) {
|
||||
av_buffer_unref(&ref);
|
||||
}
|
||||
|
||||
void free_packet(AVPacket *packet) {
|
||||
av_packet_free(&packet);
|
||||
}
|
||||
|
||||
namespace nv {
|
||||
|
||||
enum class profile_h264_e : int {
|
||||
@@ -68,8 +64,8 @@ platf::dev_type_e map_dev_type(AVHWDeviceType type);
|
||||
platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt);
|
||||
|
||||
void sw_img_to_frame(const platf::img_t &img, frame_t &frame);
|
||||
void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame);
|
||||
util::Either<buffer_t, int> nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
||||
void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame);
|
||||
util::Either<buffer_t, int> dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx);
|
||||
|
||||
util::Either<buffer_t, int> make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx);
|
||||
int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format);
|
||||
@@ -181,7 +177,7 @@ public:
|
||||
session_t(ctx_t &&ctx, frame_t &&frame, util::wrap_ptr<platf::hwdevice_t> &&device) :
|
||||
ctx { std::move(ctx) }, frame { std::move(frame) }, device { std::move(device) } {}
|
||||
|
||||
session_t(session_t &&other) :
|
||||
session_t(session_t &&other) noexcept :
|
||||
ctx { std::move(other.ctx) }, frame { std::move(other.frame) }, device { std::move(other.device) } {}
|
||||
|
||||
// Ensure objects are destroyed in the correct order
|
||||
@@ -281,8 +277,42 @@ static encoder_t nvenc {
|
||||
false,
|
||||
true,
|
||||
|
||||
nv_d3d_img_to_frame,
|
||||
nv_d3d_make_hwdevice_ctx
|
||||
dxgi_img_to_frame,
|
||||
dxgi_make_hwdevice_ctx
|
||||
};
|
||||
|
||||
static encoder_t amdvce {
|
||||
"amdvce"sv,
|
||||
{ FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN },
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_PIX_FMT_D3D11,
|
||||
AV_PIX_FMT_NV12, AV_PIX_FMT_P010,
|
||||
{
|
||||
{
|
||||
{ "header_insertion_mode"s, "idr"s },
|
||||
{ "gops_per_idr"s, 30 },
|
||||
{ "usage"s, "ultralowlatency"s },
|
||||
{ "quality"s, &config::video.amd.quality },
|
||||
{ "rc"s, &config::video.amd.rc }
|
||||
},
|
||||
std::nullopt, std::make_optional<encoder_t::option_t>({"qp"s, &config::video.qp}),
|
||||
"hevc_amf"s,
|
||||
},
|
||||
{
|
||||
{
|
||||
{ "usage"s, "ultralowlatency"s },
|
||||
{ "quality"s, &config::video.amd.quality },
|
||||
{ "rc"s, &config::video.amd.rc },
|
||||
{"log_to_dbg"s,"1"s},
|
||||
},
|
||||
std::nullopt, std::make_optional<encoder_t::option_t>({"qp"s, &config::video.qp}),
|
||||
"h264_amf"s
|
||||
},
|
||||
false,
|
||||
true,
|
||||
|
||||
dxgi_img_to_frame,
|
||||
dxgi_make_hwdevice_ctx
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -323,6 +353,7 @@ static encoder_t software {
|
||||
static std::vector<encoder_t> encoders {
|
||||
#ifdef _WIN32
|
||||
nvenc,
|
||||
amdvce,
|
||||
#endif
|
||||
software
|
||||
};
|
||||
@@ -416,7 +447,14 @@ void captureThread(
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
|
||||
reset_display(disp, encoder.dev_type);
|
||||
while(capture_ctx_queue->running()) {
|
||||
reset_display(disp, encoder.dev_type);
|
||||
|
||||
if(disp) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
if(!disp) {
|
||||
return;
|
||||
}
|
||||
@@ -562,6 +600,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
case 0:
|
||||
default:
|
||||
// Rec. 601
|
||||
BOOST_LOG(info) << "Color coding [Rec. 601]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_SMPTE170M;
|
||||
ctx->color_trc = AVCOL_TRC_SMPTE170M;
|
||||
ctx->colorspace = AVCOL_SPC_SMPTE170M;
|
||||
@@ -570,6 +609,7 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
|
||||
case 1:
|
||||
// Rec. 709
|
||||
BOOST_LOG(info) << "Color coding [Rec. 709]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_BT709;
|
||||
ctx->color_trc = AVCOL_TRC_BT709;
|
||||
ctx->colorspace = AVCOL_SPC_BT709;
|
||||
@@ -578,12 +618,14 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
|
||||
|
||||
case 2:
|
||||
// Rec. 2020
|
||||
BOOST_LOG(info) << "Color coding [Rec. 2020]"sv;
|
||||
ctx->color_primaries = AVCOL_PRI_BT2020;
|
||||
ctx->color_trc = AVCOL_TRC_BT2020_10;
|
||||
ctx->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
sws_color_space = SWS_CS_BT2020;
|
||||
break;
|
||||
}
|
||||
BOOST_LOG(info) << "Color range: ["sv << ((config.encoderCscMode & 0x1) ? "JPEG"sv : "MPEG"sv) << ']';
|
||||
|
||||
AVPixelFormat sw_fmt;
|
||||
if(config.dynamicRange == 0) {
|
||||
@@ -720,7 +762,7 @@ void encode_run(
|
||||
|
||||
if(idr_events->peek()) {
|
||||
session->frame->pict_type = AV_PICTURE_TYPE_I;
|
||||
|
||||
session->frame->key_frame = 1;
|
||||
auto event = idr_events->pop();
|
||||
if(!event) {
|
||||
return;
|
||||
@@ -732,6 +774,7 @@ void encode_run(
|
||||
}
|
||||
else if(frame_nr == key_frame_nr) {
|
||||
session->frame->pict_type = AV_PICTURE_TYPE_I;
|
||||
session->frame->key_frame = 1;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_until(next_frame);
|
||||
@@ -758,6 +801,7 @@ void encode_run(
|
||||
}
|
||||
|
||||
session->frame->pict_type = AV_PICTURE_TYPE_NONE;
|
||||
session->frame->key_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,7 +835,16 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
|
||||
const auto &encoder = encoders.front();
|
||||
|
||||
std::shared_ptr<platf::display_t> disp;
|
||||
reset_display(disp, encoder.dev_type);
|
||||
|
||||
while(encode_session_ctx_queue.running()) {
|
||||
reset_display(disp, encoder.dev_type);
|
||||
if(disp) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
|
||||
if(!disp) {
|
||||
return encode_e::error;
|
||||
}
|
||||
@@ -803,6 +856,9 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
|
||||
return encode_e::error;
|
||||
}
|
||||
|
||||
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||
input::touch_port_event->raise(disp->offset_x, disp->offset_y, disp->width, disp->height);
|
||||
|
||||
std::vector<sync_session_t> synced_sessions;
|
||||
for(auto &ctx : synced_session_ctxs) {
|
||||
auto synced_session = make_synced_session(disp.get(), encoder, *img, *ctx);
|
||||
@@ -870,6 +926,7 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
|
||||
|
||||
if(ctx->idr_events->peek()) {
|
||||
pos->session.frame->pict_type = AV_PICTURE_TYPE_I;
|
||||
pos->session.frame->key_frame = 1;
|
||||
|
||||
auto event = ctx->idr_events->pop();
|
||||
auto end = event->second;
|
||||
@@ -879,6 +936,7 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
|
||||
}
|
||||
else if(ctx->frame_nr == ctx->key_frame_nr) {
|
||||
pos->session.frame->pict_type = AV_PICTURE_TYPE_I;
|
||||
pos->session.frame->key_frame = 1;
|
||||
}
|
||||
|
||||
if(img_tmp) {
|
||||
@@ -917,6 +975,7 @@ encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synce
|
||||
}
|
||||
|
||||
pos->session.frame->pict_type = AV_PICTURE_TYPE_NONE;
|
||||
pos->session.frame->key_frame = 0;
|
||||
|
||||
++pos;
|
||||
})
|
||||
@@ -1007,8 +1066,12 @@ void capture_async(
|
||||
if(display->dummy_img(dummy_img.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
images->raise(std::move(dummy_img));
|
||||
|
||||
// absolute mouse coordinates require that the dimensions of the screen are known
|
||||
input::touch_port_event->raise(display->offset_x, display->offset_y, display->width, display->height);
|
||||
|
||||
encode_run(
|
||||
frame_nr, key_frame_nr,
|
||||
shutdown_event,
|
||||
@@ -1083,6 +1146,11 @@ bool validate_config(std::shared_ptr<platf::display_t> &disp, const encoder_t &e
|
||||
bool validate_encoder(encoder_t &encoder) {
|
||||
std::shared_ptr<platf::display_t> disp;
|
||||
|
||||
BOOST_LOG(info) << "Trying encoder ["sv << encoder.name << ']';
|
||||
auto fg = util::fail_guard([&]() {
|
||||
BOOST_LOG(info) << "Encoder ["sv << encoder.name << "] failed"sv;
|
||||
});
|
||||
|
||||
auto force_hevc = config::video.hevc_mode >= 2;
|
||||
auto test_hevc = force_hevc || (config::video.hevc_mode == 0 && encoder.hevc_mode);
|
||||
|
||||
@@ -1122,7 +1190,7 @@ bool validate_encoder(encoder_t &encoder) {
|
||||
encoder.hevc[encoder_t::PASSED] = test_hevc;
|
||||
|
||||
std::vector<std::pair<encoder_t::flag_e, config_t>> configs {
|
||||
{ encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 1, 1, 1 } }
|
||||
{ encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } }
|
||||
};
|
||||
for(auto &[flag, config] : configs) {
|
||||
auto h264 = config;
|
||||
@@ -1137,10 +1205,21 @@ bool validate_encoder(encoder_t &encoder) {
|
||||
}
|
||||
}
|
||||
|
||||
fg.disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
int init() {
|
||||
// video depends on input for input::touch_port_event
|
||||
input::init();
|
||||
|
||||
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv;
|
||||
BOOST_LOG(info) << "// //"sv;
|
||||
BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. //"sv;
|
||||
BOOST_LOG(info) << "// You can safely ignore those errors. //"sv;
|
||||
BOOST_LOG(info) << "// //"sv;
|
||||
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////////"sv;
|
||||
|
||||
KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), {
|
||||
if(
|
||||
(!config::video.encoder.empty() && pos->name != config::video.encoder) ||
|
||||
@@ -1166,6 +1245,14 @@ int init() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
BOOST_LOG(info);
|
||||
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////"sv;
|
||||
BOOST_LOG(info) << "// //"sv;
|
||||
BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant //"sv;
|
||||
BOOST_LOG(info) << "// //"sv;
|
||||
BOOST_LOG(info) << "//////////////////////////////////////////////////////////////"sv;
|
||||
BOOST_LOG(info);
|
||||
|
||||
auto &encoder = encoders.front();
|
||||
if(encoder.hevc[encoder_t::PASSED]) {
|
||||
BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']';
|
||||
@@ -1226,7 +1313,16 @@ int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) {
|
||||
void sw_img_to_frame(const platf::img_t &img, frame_t &frame) {}
|
||||
|
||||
#ifdef _WIN32
|
||||
void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) {
|
||||
}
|
||||
|
||||
// Ugly, but need to declare for wio
|
||||
namespace platf::dxgi {
|
||||
void lock(void *hwdevice);
|
||||
void unlock(void *hwdevice);
|
||||
}
|
||||
void do_nothing(void*) {}
|
||||
namespace video {
|
||||
void dxgi_img_to_frame(const platf::img_t &img, frame_t &frame) {
|
||||
if(img.data == frame->data[0]) {
|
||||
return;
|
||||
}
|
||||
@@ -1249,20 +1345,25 @@ void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) {
|
||||
frame->width = img.width;
|
||||
}
|
||||
|
||||
util::Either<buffer_t, int> nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) {
|
||||
util::Either<buffer_t, int> dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) {
|
||||
buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) };
|
||||
auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx;
|
||||
|
||||
std::fill_n((std::uint8_t*)ctx, sizeof(AVD3D11VADeviceContext), 0);
|
||||
|
||||
auto device = (ID3D11Device*)hwdevice_ctx->data;
|
||||
|
||||
device->AddRef();
|
||||
ctx->device = device;
|
||||
|
||||
ctx->lock_ctx = (void*)1;
|
||||
ctx->lock = do_nothing;
|
||||
ctx->unlock = do_nothing;
|
||||
|
||||
auto err = av_hwdevice_ctx_init(ctx_buf.get());
|
||||
if(err) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
|
||||
BOOST_LOG(error) << "Failed to create FFMpeg nvenc: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
BOOST_LOG(error) << "Failed to create FFMpeg hardware device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
+18
-6
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user